2010-12-29 9 views
7

tengo alguna manera de construir una estructura de datos (de algunos contenidos de los archivos, por ejemplo):Cómo carga perezosa una estructura de datos (pitón)

def loadfile(FILE): 
    return # some data structure created from the contents of FILE 

por lo que puedo hacer cosas como

puppies = loadfile("puppies.csv") # wait for loadfile to work 
kitties = loadfile("kitties.csv") # wait some more 
print len(puppies) 
print puppies[32] 

En el ejemplo anterior, perdí un montón de tiempo leyendo realmente kitties.csv y creando una estructura de datos que nunca utilicé. Me gustaría evitar ese desperdicio sin consultar constantemente if not kitties cada vez que quiero hacer algo. Me gustaría ser capaz de hacer

puppies = lazyload("puppies.csv") # instant 
kitties = lazyload("kitties.csv") # instant 
print len(puppies)    # wait for loadfile 
print puppies[32] 

Así que si yo no he trato de hacer cualquier cosa con kitties, loadfile("kitties.csv") no es llamado.

¿Hay alguna forma estándar de hacerlo?

Después de jugar con él un poco, produje la siguiente solución, que parece funcionar correctamente y es bastante breve. ¿Hay algunas alternativas? ¿Hay inconvenientes en usar este enfoque que debo tener en cuenta?

class lazyload: 
    def __init__(self,FILE): 
     self.FILE = FILE 
     self.F = None 
    def __getattr__(self,name): 
     if not self.F: 
      print "loading %s" % self.FILE 
      self.F = loadfile(self.FILE) 
     return object.__getattribute__(self.F, name) 

¿Cuál podría ser aún mejor si es algo como esto funcionó:

class lazyload: 
    def __init__(self,FILE): 
     self.FILE = FILE 
    def __getattr__(self,name): 
     self = loadfile(self.FILE) # this never gets called again 
            # since self is no longer a 
            # lazyload instance 
     return object.__getattribute__(self, name) 

Pero esto no funciona debido a self es local. En realidad, termina llamando al loadfile cada vez que hace algo.

+1

Es posible que desee un proxy '__nonzero__' y otros métodos también. – ephemient

+0

@ephemient: parece que una llamada a '__nonzero__' pasará por' __getattr__', ¿cuál es el problema? –

+0

Supongo que es un poco mejor decir 'si self.F no es None' en lugar de' if not self.F' ya que 'loadfile (FILE)' * might * puede ser 0 o la cadena vacía. –

Respuesta

4

El módulo csv en la biblioteca de Python no cargará los datos hasta que comience a iterar sobre él, por lo que de hecho es flojo.

Editar: Si necesita leer todo el archivo para construir la estructura de datos, tener un objeto de carga lenta complejo que proxies cosas es excesivo. Sólo hacer esto:

class Lazywrapper(object): 
    def __init__(self, filename): 
     self.filename = filename 
     self._data = None 

    def get_data(self): 
     if self._data = None: 
      self._build_data() 
     return self._data 

    def _build_data(self): 
     # Now open and iterate over the file to build a datastructure, and 
     # put that datastructure as self._data 

Con la clase anterior se puede hacer esto:

puppies = Lazywrapper("puppies.csv") # Instant 
kitties = Lazywrapper("kitties.csv") # Instant 

print len(puppies.getdata()) # Wait 
print puppies.getdata()[32] # instant 

también

allkitties = kitties.get_data() # wait 
print len(allkitties) 
print kitties[32] 

Si usted tiene un montón de los datos, y no hacer realmente necesita cargar todos los datos, también podría implementar algo así como una clase que leerá el archivo hasta que encuentre al perrito llamado "Froufrou" y luego se detenga, pero en ese punto es probable que er para pegar los datos en una base de datos de una vez por todas y acceder desde allí.

+0

No estoy tratando con archivos 'csv'; era solo para propósitos de ilustración. Pero esto es bueno saberlo. Además, 'loadfile' tendrá que iterar sobre los datos si va a construir una estructura de datos no estándar interesante, por lo que la pereza de' csv' puede no ser suficiente. –

+0

Si entiendo correctamente, su 'self._data' es exactamente análogo a mi' self.F' y su '_build_data' es exactamente análogo a mi' loadfile'. Lo bueno de usar '__getattr__' es que no tienes que escribir' .get_data() 'en todas partes; puede interactuar con el contenedor * exactamente * como lo haría con la estructura de datos directamente. –

+0

Sí, pero luego necesitas más que '__getattr__', realmente, necesitas escribir un proxy completo, y no vale la pena. Simple = bueno Y no tiene que escribir get_data() en todas partes. Simplemente escribe datos = thewrapper.get_data(), por lo que solo lo escribes una vez para cada función en la que necesites acceder a los datos. –

1

¿No sería if not self.F llevar a otra llamada al __getattr__, poniéndolo en un ciclo infinito? Creo que su enfoque tiene sentido, pero para estar en el lado seguro, me gustaría hacer esa línea en:

if name == "F" and not self.F: 

Además, usted podría hacer loadfile un método en la clase, dependiendo de lo que está haciendo.

+2

'__getattr__' solo se invoca si no se encuentra el método (http://docs.python.org/py3k/reference/datamodel.html#object.__getattr__), así que no creo que sea un problema. –

+1

@ Anton: Gracias, no sabía eso antes. Dejaré esta respuesta para que otras personas puedan ver esa información. –

0

Aquí hay un truco que hace que la solución "aún mejor" funcione, pero creo que es lo suficientemente molesto como para que sea mejor usar la primera solución.La idea es ejecutar el paso self = loadfile(self.FILE) pasando el nombre de la variable como un atributo:

class lazyload: 
    def __init__(self,FILE,var): 
     self.FILE = FILE 
     self.var = var 
    def __getattr__(self,name): 
     x = loadfile(self.FILE) 
     globals()[self.var]=x 
     return object.__getattribute__(x, name) 

entonces usted puede hacer

kitties = lazyload("kitties.csv","kitties") 
^        ^
    \        /
    These two better match exactly 

Después de llamar a cualquier método de kitties (aparte de kitties.FILE o kitties.var) , se convertirá en completamente indistinguible de lo que habría obtenido con kitties = loadfile("kitties.csv"). En particular, ya no será una instancia de lazyload y kitties.FILE y kitties.var ya no existirán.

+0

Sé que probablemente solo echas ideas, pero este tipo de cosas realmente deberían desestimarse. Aparte de la maldad que usted mismo señaló, esto solo funcionará siempre que el objeto solo necesite ser referido por un solo nombre global. Asignarlo a otro nombre y trabajar en eso, y los gatitos se vuelven a cargar y reemplazar después de cada acceso de atributo. Pasarlo a una función, y cada uso del objeto en la función hará que se vuelva a cargar. – randomhuman

0

Si necesita usar puppies[32] también necesita definir el método __getitem__ porque __getattr__ no detecta ese comportamiento.

implemento de carga perezosa para mis necesidades, hay no adaptada código:

class lazy_mask(object): 
    '''Fake object, which is substituted in 
    place of masked object''' 

    def __init__(self, master, id): 
    self.master=master 
    self.id=id 
    self._result=None 
    self.master.add(self) 

    def _res(self): 
    '''Run lazy job''' 
    if not self._result: 
     self._result=self.master.get(self.id) 
    return self._result 

    def __getattribute__(self, name): 
    '''proxy all queries to masked object''' 
    name=name.replace('_lazy_mask', '') 
    #print 'attr', name 
    if name in ['_result', '_res', 'master', 'id']:#don't proxy requests for own properties 
     return super(lazy_mask, self).__getattribute__(name) 
    else:#but proxy requests for masked object 
     return self._res().__getattribute__(name) 

    def __getitem__(self, key): 
    '''provide object["key"] access. Else can raise 
    TypeError: 'lazy_mask' object is unsubscriptable''' 
    return self._res().__getitem__(key) 

(maestro es objeto de registro que carga los datos cuando corro es obtener (método))

obras de esta implementación ok para isinstance() y str() y json.dumps() con él

+0

Por lo que puedo decir, esta afirmación es incorrecta: * "Si necesita utilizar' cachorros [32] 'también necesita definir el método' __getitem__' porque '__getattr__' no detecta ese comportamiento." * De hecho he probado el código en la pregunta y * does * work. Sin embargo, es cierto que '__getattribute__' no captura' __getitem__'. –

1

Si realmente está preocupado por la instrucción if, tiene un objeto Stateful.

from collections import MutableMapping 
class LazyLoad(MutableMapping): 
    def __init__(self, source): 
     self.source= source 
     self.process= LoadMe(self) 
     self.data= None 
    def __getitem__(self, key): 
     self.process= self.process.load() 
     return self.data[key] 
    def __setitem__(self, key, value): 
     self.process= self.process.load() 
     self.data[key]= value 
    def __contains__(self, key): 
     self.process= self.process.load() 
     return key in self.data 

Esta clase delegados el trabajo a un objeto process que es bien un Load o un objeto DoneLoading. El objeto Load se cargará realmente. El DoneLoading no se cargará.

Tenga en cuenta que no hay declaraciones if.

class LoadMe(object): 
    def __init__(self, parent): 
     self.parent= parent 
    def load(self): 
     ## Actually load, setting self.parent.data 
     return DoneLoading(self.parent) 

class DoneLoading(object): 
    def __init__(self, parent): 
     self.parent= parent 
    def load(self): 
     return self 
+0

No entiendo esta respuesta (todavía?). Parece que una vez que el objeto está realmente cargado, los únicos métodos disponibles son 'get' y' put'. Pero hay muchas otras formas en las que podría querer interactuar con la estructura de datos. Por ejemplo, 'cachorros [34]' y 'len (cachorros)' arrojarían cada uno un 'AttributeError' usando este método, ¿no? –

+0

@ Anton Geraschenko: "Pero hay muchas otras formas en las que podría querer interactuar con la estructura de datos". Bueno. Escribe más métodos. Solo pude pensar en dos. Puedes pensar en más. Escribe más. –

+0

Gracias por aclarar. Ahora entiendo la respuesta, pero es estrictamente peor que la solución que propuse en la pregunta, que hace que * cada * método esté disponible para * cualquier * estructura de datos imaginables. '__getattr__' es un método catch-all que hace que listas largas de definiciones de métodos casi idénticos sean innecesarias. –

1

he aquí una solución que utiliza un decorador de clase aplazar la inicialización hasta que la primera vez que se utiliza un objeto:

def lazyload(cls): 
    original_init = cls.__init__ 
    original_getattribute = cls.__getattribute__ 

    def newinit(self, *args, **kwargs): 
     # Just cache the arguments for the eventual initialization. 
     self._init_args = args 
     self._init_kwargs = kwargs 
     self.initialized = False 
    newinit.__doc__ = original_init.__doc__ 

    def performinit(self): 
     # We call object's __getattribute__ rather than super(...).__getattribute__ 
     # or original_getattribute so that no custom __getattribute__ implementations 
     # can interfere with what we are doing. 
     original_init(self, 
         *object.__getattribute__(self, "_init_args"), 
         **object.__getattribute__(self, "_init_kwargs")) 
     del self._init_args 
     del self._init_kwargs 
     self.initialized = True 

    def newgetattribute(self, name): 
     if not object.__getattribute__(self, "initialized"): 
      performinit(self) 
     return original_getattribute(self, name) 

    if hasattr(cls, "__getitem__"): 
     original_getitem = cls.__getitem__ 
     def newgetitem(self, key): 
      if not object.__getattribute__(self, "initialized"): 
       performinit(self) 
      return original_getitem(self, key) 
     newgetitem.__doc__ = original_getitem.__doc__ 
     cls.__getitem__ = newgetitem 

    if hasattr(cls, "__len__"): 
     original_len = cls.__len__ 
     def newlen(self): 
      if not object.__getattribute__(self, "initialized"): 
       performinit(self) 
      return original_len(self) 
     newlen.__doc__ = original_len.__doc__ 
     cls.__len__ = newlen 

    cls.__init__ = newinit 
    cls.__getattribute__ = newgetattribute 
    return cls 

@lazyload 
class FileLoader(dict): 
    def __init__(self, filename): 
     self.filename = filename 
     print "Performing expensive load operation" 
     self[32] = "Felix" 
     self[33] = "Eeek" 

kittens = FileLoader("kitties.csv") 
print "kittens is instance of FileLoader: %s" % isinstance(kittens, FileLoader) # Well obviously 
print len(kittens) # Wait 
print kittens[32] # No wait 
print kittens[33] # No wait 
print kittens.filename # Still no wait 
print kittens.filename 

La salida:

kittens is instance of FileLoader: True 
Performing expensive load operation 
2 
Felix 
Eeek 
kitties.csv 
kitties.csv 

Traté de restauración en realidad el métodos mágicos originales después de la inicialización, pero no funcionaba. Puede ser necesario utilizar métodos mágicos adicionales, no investigué todos los escenarios.

Tenga en cuenta que kittens.initialized siempre devolverá True porque inicia la inicialización si aún no se ha realizado. Obviamente, sería posible agregar una exención para este atributo para que devuelva False si no se ha realizado ninguna otra operación en el objeto, o la verificación podría cambiarse al equivalente de una llamada hasattr y el atributo inicializado podría eliminarse después la inicialización

Cuestiones relacionadas