Леновый список Python

Я хотел бы создать свою собственную коллекцию, которая имеет все атрибуты списка python, а также знает, как сохранять/загружать себя в/из базы данных. Также я хочу сделать нагрузку неявной и ленивой, так как в ней не происходит в момент создания списка, но ждет, пока его не будет использовать.

Есть ли один метод __xxx__, который я могу переопределить для загрузки списка при первом использовании любого свойства списка (например, len, getitem, iter... и т.д.) без необходимости переопределять их все

Ответы

Ответ 1

Нет, нет.

Ответ 2

Не совсем. Для эмуляции объектов, отличных от списков, существует __getattribute__, но, к сожалению, Python не считает такие операторы, как x[y] или x(y), точно такими же, как x.__getitem__(y) или x.__call__(y). Такие операторы являются атрибутами класса, а не атрибутами экземпляра, как вы можете видеть здесь:

>>> class x(object):
...     def __getattribute__(self, o):
...         print o
... 
>>> x()[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'x' object does not support indexing

Однако вы можете использовать динамическую природу Python для эффективного устранения этого различия. Если ваша главная задача - сохранить ввод текста и создать меньше кода, который нуждается в обслуживании, вы можете сделать что-то вроде этого:

class override(object):
    def __init__(self, methodName):
        self.methodName = methodName

    def __get__(self, oself, cls):
        oself._load(self.methodName)
        return getattr(super(oself.__class__, oself), self.methodName)

class LazyList(list):
    def _load(self, name):
        print 'Loading data for %s...' % (name,)

    for methodName in set(dir(list)) - set(dir(object)):
        locals()[methodName] = override(methodName)

Вероятно, вы не хотите использовать dir() в реальной жизни, но подходящий фиксированный список строк может работать как замена.

Ответ 3

Не один, но 5 достаточно:

from collections import MutableSequence

class Monitored(MutableSequence):
    def __init__(self):
        super(Monitored, self).__init__()
        self._list = []

    def __len__(self):
        r = len(self._list)
        print "len: {0:d}".format(r)
        return r

    def __getitem__(self, index):
        r = self._list[index]
        print "getitem: {0!s}".format(index)
        return r

    def __setitem__(self, index, value):
        print "setitem {0!s}: {1:s}".format(index, repr(value))
        self._list[index] = value

    def __delitem__(self, index):
        print "delitem: {0!s}".format(index)
        del self._list[index]

    def insert(self, index, value):
        print "insert at {0:d}: {1:s}".format(index, repr(value))
        self._list.insert(index, value)

Правильный способ проверки того, что что-то реализует весь интерфейс списка, - проверить, является ли он подклассом MutableSequence. ABC, найденные в модуле collections, из которых MutableSequence являются единичными, существуют по двум причинам:

  • чтобы вы могли создавать собственные классы, имитирующие внутренние типы контейнеров, чтобы они могли использоваться везде, где есть обычный встроенный.

  • использовать в качестве аргумента для isinstance и issubclass для проверки того, что объект реализует необходимые функции:

>>> isinstance([], MutableSequence)
True
>>> issubclass(list, MutableSequence)
True

Наш класс Monitored работает следующим образом:

>>> m = Monitored()
>>> m.append(3)
len: 0
insert at 0: 3
>>> m.extend((1, 4))
len: 1
insert at 1: 1
len: 2
insert at 2: 4
>>> m.l
[3, 1, 4]
>>> m.remove(4)
getitem: 0
getitem: 1
getitem: 2
delitem: 2
>>> m.pop(0)   # after this, m.l == [1]
getitem: 0
delitem: 0
3
>>> m.insert(0, 4)
insert at 0: 4
>>> m.reverse()   # After reversing, m.l == [1, 4]
len: 2
getitem: 1
getitem: 0
setitem 0: 1
setitem 1: 4
>>> m.index(4)
getitem: 0
getitem: 1
1

Ответ 5

Нет ни одного метода. Вы должны переопределить их довольно много. MutableSequence - это современный способ сделать это. Вот версия, которая работает с Python 2.4 +::

class LazyList(list):
    """List populated on first use."""
    def __new__(cls, fill_iter):

        class LazyList(list):
            _fill_iter = None

        _props = (
            '__str__', '__repr__', '__unicode__',
            '__hash__', '__sizeof__', '__cmp__', '__nonzero__',
            '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__',
            'append', 'count', 'index', 'extend', 'insert', 'pop', 'remove',
            'reverse', 'sort', '__add__', '__radd__', '__iadd__', '__mul__',
            '__rmul__', '__imul__', '__contains__', '__len__', '__nonzero__',
            '__getitem__', '__setitem__', '__delitem__', '__iter__',
            '__reversed__', '__getslice__', '__setslice__', '__delslice__')

        def lazy(name):
            def _lazy(self, *args, **kw):
                if self._fill_iter is not None:
                    _fill_lock.acquire()
                    try:
                        if self._fill_iter is not None:
                            list.extend(self, self._fill_iter)
                            self._fill_iter = None
                    finally:
                        _fill_lock.release()
                real = getattr(list, name)
                setattr(self.__class__, name, real)
                return real(self, *args, **kw)
            return _lazy

        for name in _props:
            setattr(LazyList, name, lazy(name))

        new_list = LazyList()
        new_list._fill_iter = fill_iter
        return new_list