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
один, существуют по двум причинам:
чтобы вы могли создавать свои собственные классы, эмулирующие внутренние типы контейнеров, чтобы их можно было использовать везде, где есть обычные встроенные функции.
использовать в качестве аргумента для
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()
в реальной жизни, но подходящий фиксированный список строк может работать в качестве замены.
Там нет ни одного метода. Вы должны переопределить их довольно много. 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