Можете ли вы легко создать похожий на список объект в Python, который использует что-то вроде дескриптора для своих элементов? - PullRequest
5 голосов
/ 12 января 2012

Я пытаюсь написать интерфейс, который несколько абстрагирует другой интерфейс.

Нижний интерфейс несколько противоречив в отношении того, что ему требуется: иногда идентификаторы, а иногда имена.Я пытаюсь скрыть детали, подобные этим.

Я хочу создать подобный списку объект, который позволит вам добавлять к нему имена, но внутренне хранить идентификаторы, связанные с этими именами.

Предпочтительно, я хотел бы использовать что-то вроде дескрипторов для атрибутов класса, за исключением того, что они работают вместо элементов списка.То есть функция (например, __get__) вызывается для всего, что добавлено в список, чтобы преобразовать его в идентификаторы, которые я хочу хранить внутри, и другую функцию (например, __set__), которая вместо этого возвращает объекты (которые предоставляют удобные методы).фактических идентификаторов при попытке извлечь элементы из списка.

Чтобы я мог сделать что-то вроде этого:

def get_thing_id_from_name(name):
    # assume that this is more complicated
    return other_api.get_id_from_name_or_whatever(name)

class Thing(object)
    def __init__(self, thing_id):
        self.id = thing_id
        self.name = other_api.get_name_somehow(id)

    def __eq__(self, other):
        if isinstance(other, basestring):
            return self.name == other

        if isinstance(other, Thing):
            return self.thing_id == other.thing_id

        return NotImplemented

tl = ThingList()

tl.append('thing_one')
tl.append('thing_two')
tl[1] = 'thing_three'

print tl[0].id
print tl[0] == 'thing_one'
print tl[1] == Thing(3)

В документации рекомендуется определить 17 методов (не включая конструктор) для объекта, который действует как изменяемая последовательность.Я не думаю, что подкласс list поможет мне вообще.Такое чувство, что я должен быть в состоянии достичь этого, просто определив метод получения и установки где-то.

UserList явно устарел (хотя в python3? Я использую 2.7 хотя).

Есть ли способ достичь этого или что-то подобное, без необходимости переопределять столько функций?

1 Ответ

3 голосов
/ 13 января 2012

Yo не нужно переопределять все методы списка - __setitem__, __init__ и \ append должно быть достаточно - вы можете захотеть использовать insert и некоторые другие.Вы можете написать __setitem__ и __getitem__ для вызова методов __set__ и __get__ в отдельном классе "Thing" точно так же, как это делают дескрипторы.

Вот краткий пример - может быть, что-то вроде того, что вы хотите:

class Thing(object):
    def __init__(self, thing):
        self.value = thing
        self.name = str(thing)
    id = property(lambda s: id(s))
    #...
    def __repr__(self):
        return "I am a %s" %self.name 

class ThingList(list):
    def __init__(self, items):
        for item in items:
            self.append(item)
    def append(self, value):
        list.append(self, Thing(value))
    def __setitem__(self, index, value):
        list.__setitem__(self, index, Thing(value))

Пример:

>>> a = ThingList(range(3))
>>> a.append("three")
>>> a
[I am a 0, I am a 1, I am a 2, I am a three]
>>> a[0].id
35242896
>>> 

- редактировать -

ОП прокомментировал: «Я действительно надеялся, что найдется способ получить всю функциональность из списка - добавление, расширение, срезы и т. Д. И только переопределить поведение элемента get / set».

Да будет так - действительно нужно переопределить все соответствующие методы таким образом.Но если мы хотим избежать большого количества кода, с множеством функций, выполняющих практически одно и то же, новые переопределенные методы могут генерироваться динамически - все, что нам нужно, - это декоратор для преобразования обычных объектов в Thingsдля всех операций, которые устанавливают значения:

class Thing(object):
    # Prevents duplicating the wrapping of objects:
    def __new__(cls, thing):
        if isinstance(thing, cls):
            return thing
        return object.__new__(cls, thing)

    def __init__(self, thing):
        self.value = thing
        self.name = str(thing)
    id = property(lambda s: id(s))
    #...
    def __repr__(self):
        return "I am a %s" %self.name 

def converter(func, cardinality=1):
    def new_func(*args):
        # Pick the last item in the argument list, which
        # for all item setter methods on  a list is the one
        # which actually contains the values
        if cardinality == 1:
            args = args[:-1] + (Thing(args[-1]  ),)
        else:
            args = args[:-1] + ([Thing(item) for item in args[-1]],)
        return func(*args)
    new_func.func_name = func.__name__
    return new_func

my_list_dict = {}

for single_setter in ("__setitem__", "append", "insert"):
    my_list_dict[single_setter] = converter(getattr(list, single_setter), cardinality=1)

for many_setter in ("__setslice__", "__add__", "__iadd__", "__init__", "extend"):
    my_list_dict[many_setter] = converter(getattr(list, many_setter), cardinality="many")

MyList = type("MyList", (list,), my_list_dict)

И это работает так:

>>> a = MyList()
>>> a
[]
>>> a.append(5)
>>> a
[I am a 5]
>>> a + [2,3,4]
[I am a 5, I am a 2, I am a 3, I am a 4]
>>> a.extend(range(4))
>>> a
[I am a 5, I am a 0, I am a 1, I am a 2, I am a 3]
>>> a[1:2] =  range(10,12)
>>> a
[I am a 5, I am a 10, I am a 11, I am a 1, I am a 2, I am a 3]
>>> 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...