Lista perezosa de Python
lazylist (5)
Me gustaría crear mi propia colección que tenga todos los atributos de la lista de Python y también sepa cómo guardarla / cargarla en / desde una base de datos. También quiero hacer que la carga sea implícita y floja, ya que no ocurre en el momento de la creación de la lista, sino que espera hasta que se use por primera vez.
¿Existe un único método __xxx__
que pueda anular para cargar la lista en el primer uso de cualquier propiedad de la lista (como len
, getitem
, iter
... etc) sin tener que sobrescribirlos a todos?
No, no hay.
No exactamente. Para emular cosas que no sean listas, existe __getattribute__
, pero desafortunadamente Python no considera que operadores como x[y]
o x(y)
sean exactamente iguales a x.__getitem__(y)
o x.__call__(y)
. Los operadores así son atributos de la clase, no atributos de la instancia, como se puede ver aquí:
>>> 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
Sin embargo, puede aprovechar la naturaleza dinámica de Python para eliminar efectivamente esa distinción. Si su principal preocupación es evitarse escribiendo y producir menos código que necesite ser actualizado, puede hacer algo como esto:
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)
Probablemente no desee usar dir()
en la vida real, pero una lista fija adecuada de cadenas podría funcionar como un sustituto.
Ni uno solo , pero 5 es suficiente:
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)
La forma correcta de comprobar si algo implementa toda la interfaz de la lista es comprobar si se trata de una subclase de MutableSequence
. Los ABC encontrados en el módulo de collections
, de los cuales MutableSequence
es uno, están ahí por dos razones:
para permitirle crear sus propias clases emulando los tipos de contenedores internos para que se puedan usar en cualquier lugar donde se encuentre un built-in normal.
usar como argumento para
isinstance
yissubclass
para verificar que un objeto implemente la funcionalidad necesaria:
>>> isinstance([], MutableSequence)
True
>>> issubclass(list, MutableSequence)
True
Nuestra clase Monitored
funciona así:
>>> 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
No hay un solo método Tienes que redefinir bastantes. MutableSequence parece ser la forma moderna de hacerlo. Aquí hay una versión que funciona con 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