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

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

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

5 ответов

Решение

Нет, нет

Не одного, а 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, Азбука найдена в collections модуль, из которых MutableSequence один, существуют по двум причинам:

  1. чтобы вы могли создавать свои собственные классы, эмулирующие внутренние типы контейнеров, чтобы их можно было использовать везде, где есть обычные встроенные функции.

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

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

наш Monitored класс работает так:

>>> m = Отслеживается ()
>>> m.append(3)
длина: 0
вставить в 0: 3
>>> m.extend((1, 4))
длина: 1
вставить в 1: 1
длина: 2
вставить в 2: 4
>>> мл
[3, 1, 4]
>>> м.ремовье (4)
getitem: 0
getitem: 1
getitem: 2
delitem: 2
>>> m.pop(0)   # после этого, мл == [1]
getitem: 0
delitem: 0
3
>>> m.insert(0, 4)
вставить в 0: 4
>>> m.reverse()   # После реверса, мл == [1, 4]
длина: 2
getitem: 1
getitem: 0
Сетитем 0: 1
Сетитем 1: 4
>>> m.index(4)
getitem: 0
getitem: 1
1

Не совсем. Для подражания вещам, отличным от списков, есть __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() в реальной жизни, но подходящий фиксированный список строк может работать в качестве замены.

__getattribute__

Там нет ни одного метода. Вы должны переопределить их довольно много. 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
Другие вопросы по тегам