python lazylist

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:

  1. 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.

  2. usar como argumento para isinstance y issubclass 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