2010-12-29 9 views
7

ho qualche modo di costruire una struttura di dati (da alcuni contenuto del file, per esempio):come caricare pigro una struttura di dati (python)

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

modo che io possa fare le cose come

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

Nell'esempio precedente, ho perso un sacco di tempo leggendo lo kitties.csv e creando una struttura dati che non ho mai usato. Vorrei evitare questo spreco senza controllare costantemente if not kitties ogni volta che voglio fare qualcosa. Mi piacerebbe essere in grado di fare

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

Quindi, se non sempre cerco di fare qualsiasi cosa con kitties, loadfile("kitties.csv") non viene mai chiamato.

C'è qualche modo standard per farlo?

Dopo aver giocato un po 'con esso, ho prodotto la seguente soluzione, che sembra funzionare correttamente ed è piuttosto breve. Ci sono delle alternative? Ci sono degli svantaggi nell'usare questo approccio che dovrei tenere a mente?

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) 

Quale potrebbe essere ancora migliore è che se qualcosa di simile ha funzionato:

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) 

Ma questo non funziona perché self è locale. In realtà finisce per chiamare loadfile ogni volta che fai qualcosa.

+1

Si consiglia di proxy '__nonzero__' e altri metodi. – ephemient

+0

@ephemient: sembra che una chiamata a '__nonzero__' passerà attraverso' __getattr__', quindi qual è il problema? –

+0

Suppongo che sia un po 'meglio dire "se self.F non è None' piuttosto che' if not self.F' poiché 'loadfile (FILE)' * potrebbe * essere 0 o la stringa vuota. –

risposta

4

Il modulo csv nello stdlibrary Python non caricherà i dati finché non si inizia a scorrere su di esso, quindi in effetti è pigro.

Modifica: Se è necessario leggere l'intero file per creare la struttura dati, disporre di un oggetto di carico Lazy complesso che genera proxy è eccessivo. Basta fare questo:

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 classe di cui sopra si può fare questo:

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

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

anche

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

Se si dispone di un sacco dei dati, e non fare ho davvero bisogno di caricare tutti i dati che potresti anche implementare qualcosa come la classe che leggerà il file finché non troverà il cagnolino chiamato "Froufrou" e poi si fermerà, ma a quel punto è probabile che er per attaccare i dati in un database una volta per tutte e accedervi da lì.

+0

In realtà non mi occupo di file 'csv'; era solo a scopo illustrativo. Ma questo è buono a sapersi. Inoltre, 'loadfile' dovrà eseguire un'iterazione sui dati se si vuole costruire una struttura di dati non standard interessante, quindi la pigrizia di' csv' potrebbe non essere sufficiente. –

+0

Se ho capito bene, il tuo 'self._data' è esattamente analogo al mio' self.F' e il tuo '_build_data' è esattamente analogo al mio' loadfile'. La cosa bella dell'uso di '__getattr__' è che non devi scrivere' .get_data() 'ovunque; puoi interagire con il wrapper * esattamente * come faresti direttamente con la struttura dati. –

+0

Sì, ma poi hai bisogno di più di "__getattr__", in realtà, devi scrivere un proxy completo e non ne vale la pena. Semplice = buono. E non devi scrivere get_data() ovunque. Scrivi semplicemente data = thewrapper.get_data(), quindi lo scrivi solo una volta per ogni funzione in cui è necessario accedere ai dati. –

1

if not self.F non porta a un'altra chiamata a __getattr__, mettendoti in un ciclo infinito? Credo che il tuo approccio ha un senso, ma per essere al sicuro, mi piacerebbe fare quella linea in:

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

Inoltre, si potrebbe fare loadfile un metodo della classe, a seconda di quello che stai facendo.

+2

'__getattr__' viene chiamato solo se il metodo non viene trovato (http://docs.python.org/py3k/reference/datamodel.html#object.__getattr__), quindi non penso che questo sia un problema. –

+1

@Anton: Grazie, non lo sapevo prima. Lascerò questa risposta in modo che altre persone possano vedere quelle informazioni. –

0

Ecco un trucco che rende la soluzione "ancora migliore" funzionante, ma penso che sia abbastanza fastidioso che sia probabilmente meglio usare solo la prima soluzione.L'idea è quella di eseguire la fase self = loadfile(self.FILE) passando il nome della variabile come un attributo:

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) 

allora si può fare

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

Dopo aver chiamare qualsiasi metodo su kitties (a parte kitties.FILE o kitties.var) , diventerà completamente indistinguibile da quello che avresti ottenuto con kitties = loadfile("kitties.csv"). In particolare, non sarà più un'istanza di lazyload e kitties.FILE e kitties.var non esisterà più.

+0

So che probabilmente stavi semplicemente buttando in giro idee, ma questo genere di cose dovrebbe essere scoraggiato. A prescindere dalla cattiveria che hai segnalato tu stesso, questo funzionerà solo finché l'oggetto avrà solo bisogno di essere chiamato da un singolo nome globale. Assegnalo a un altro nome e lavoraci, e i gattini vengono ricaricati e sostituiti dopo ogni accesso agli attributi. Passalo a una funzione e ogni utilizzo dell'oggetto nella funzione farà sì che venga ricaricato. – randomhuman

0

Se è necessario utilizzare puppies[32], è necessario definire anche il metodo __getitem__ poiché __getattr__ non rileva tale comportamento.

a implementare carico pigro per le mie esigenze, v'è non adattato codice:

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) 

(metodo master è oggetto di registro che caricare i dati quando ho eseguito è get())

opere questa implementazione ok per isinstance() e str() e json.dumps() con esso

+0

Per quanto ne so, questa affermazione non è corretta: * "Se hai bisogno di usare' puppies [32] 'devi anche definire il metodo' __getitem__' perché '__getattr__' non cattura questo comportamento." * Ho effettivamente provato il codice nella domanda e * funziona *. Tuttavia, è vero che '__getattribute__' non cattura' __getitem__'. –

1

Se si è veramente preoccupati dell'istruzione if, si ha un oggetto Stateful 10.

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 

Questa classe delega il lavoro per un oggetto process ovvero un Load o un oggetto DoneLoading. L'oggetto Load verrà effettivamente caricato. Il DoneLoading non verrà caricato.

Si noti che non ci sono istruzioni 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

Non capisco questa risposta (ancora?). Sembra che una volta caricato l'oggetto, gli unici metodi disponibili sono 'get' e' put'. Ma ci sono molti altri modi in cui potrei voler interagire con la struttura dei dati. Ad esempio, 'puppies [34]' e 'len (puppies)' lanciano un 'AttributeError' usando questo metodo, giusto? –

+0

@Anton Geraschenko: "Ma ci sono molti altri modi in cui potrei voler interagire con la struttura dei dati." Buona. Scrivi altri metodi. Potevo solo pensare a due. Puoi pensare di più. Scrivi di più –

+0

Grazie per aver chiarito. Ora capisco la risposta, ma è strettamente peggiore della soluzione che ho proposto nella domanda, che rende * ogni * metodo disponibile per * qualsiasi * struttura di dati immaginabile. '__getattr__' è un metodo catch-all che rende superflui lunghi elenchi di definizioni del metodo quasi identiche. –

1

Ecco una soluzione che utilizza un decoratore di classe di rinviare l'inizializzazione fino a quando la prima volta che un oggetto viene utilizzato:

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 

L'output:

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

ho cercato di ripristinare in realtà il metodi magici originali dopo l'inizializzazione, ma non stava funzionando. Potrebbe essere necessario indirizzare metodi magici aggiuntivi, non ho indagato su ogni scenario.

Si noti che kittens.initialized restituirà sempre True perché avvia l'inizializzazione se non è già stata eseguita. Ovviamente sarebbe possibile aggiungere un'esenzione per questo attributo in modo che restituisca False se non è stata eseguita alcuna altra operazione sull'oggetto, o il controllo potrebbe essere modificato all'equivalente di una chiamata hasattr e l'attributo inizializzato potrebbe essere eliminato dopo l'inizializzazione.

Problemi correlati