2014-11-28 10 views
5

Ho un oggetto stato che rappresenta un sistema. Le proprietà all'interno dell'oggetto stato sono popolate da [enormi] file di testo. Poiché non tutti i proprietà si accede ogni volta che un'istanza di stato, è stato creato, ha senso per loro caricare pigramente .:Variabili di caricamento lento utilizzando decoratori sovraccaricati

class State: 
    def import_positions(self): 
     self._positions = {} 
     # Code which populates self._positions 

    @property 
    def positions(self): 
     try: 
      return self._positions 
     except AttributeError: 
      self.import_positions() 
      return self._positions 

    def import_forces(self): 
     self._forces = {} 
     # Code which populates self._forces 

    @property 
    def forces(self): 
     try: 
      return self._forces 
     except AttributeError: 
      self.import_forces() 
      return self._forces 

C'è un sacco di codice standard ripetitivo qui. Inoltre, a volte un import_abc può popolare alcune variabili (ad esempio, importa alcune variabili da un piccolo file di dati se è già aperto).

Ha senso sovraccaricare @property tale che accetta una funzione di "fornire" quella variabile, vale a dire:

class State: 
    def import_positions(self): 
     self._positions = {} 
     # Code which populates self._positions 

    @lazyproperty(import_positions) 
    def positions(self): 
     pass 

    def import_forces(self): 
     self._forces = {} 
     # Code which populates self._forces and self._strain 

    @lazyproperty(import_forces) 
    def forces(self): 
     pass 

    @lazyproperty(import_forces) 
    def strain(self): 
     pass 

Tuttavia, non riesco a trovare un modo per tracciare esattamente quello metodo sono essere chiamato nel decoratore @property. In quanto tale, non so come affrontare l'overloading di @property nella mia @lazyproperty.

Qualche idea?

risposta

3

Forse vuoi qualcosa di simile. È una sorta di semplice funzione di memoizzazione combinata con @property.

def lazyproperty(func): 
    values = {} 
    def wrapper(self): 
     if not self in values: 
      values[self] = func(self) 
     return values[self] 
    wrapper.__name__ = func.__name__ 
    return property(wrapper) 

class State: 
    @lazyproperty 
    def positions(self): 
     print 'loading positions' 
     return {1, 2, 3} 

s = State() 
print s.positions 
print s.positions 

che stampa:

loading positions 
set([1, 2, 3]) 
set([1, 2, 3]) 

Caveat: le voci del dizionario i valori non saranno spazzatura raccolti, quindi non è adatto per i programmi di lunga durata. Se il valore impostato è immutabile in tutte le classi, può essere conservato sull'oggetto funzione stessa per la velocità e la memoria migliore utilizzo:

try: 
    return func.value 
except AttributeError: 
    func.value = func(self) 
    return func.value 
+0

Questo non funzionerà se crei più di una istanza di 'Stato'. Stai memorizzando i dati sul funzione oggetto del metodo, ma il metodo esiste solo una volta a livello di classe, quindi qualunque cosa tu faccia ad esso interesserà tutte le istanze. Se fai un secondo 'State' e ne muti le posizioni, vedrai le modifiche mostrarsi in entrambe le istanze. – BrenBarn

+0

Hai ragione, risolto. – lunixbochs

1

Tuttavia, non riesco a trovare un modo per tracciare esattamente quale metodo sono chiamato nel decoratore @property.

property è in realtà un tipo (se lo si utilizza con la sintassi decoratore non è ortogonale), che implementa il descriptor protocol (https://docs.python.org/2/howto/descriptor.html). Un eccessivamente semplificata (Ho saltato la deleter, doc e un bel paio di altre cose ...) pura implementazione-pitone sarebbe simile a questa:

class property(object): 
    def __init__(self, fget=None, fset=None): 
     self.fget = fget 
     self.fset = fset 

    def setter(self, func): 
     self.fset = func 
     return func 

    def __get__(self, obj, type=None): 
     return self.fget(obj) 

    def __set__(self, obj, value): 
     if self.fset: 
      self.fset(obj, value) 
     else: 
      raise AttributeError("Attribute is read-only") 

Ora sovraccarico property non è necessariamente la soluzione più semplice. In realtà ci sono in realtà un bel paio di implementazioni esistenti là fuori, tra cui "cached_property" di Django (cfr http://ericplumb.com/blog/understanding-djangos-cached_property-decorator.html per più su di esso) e il pacchetto di pydanny "cache-proprietà" (https://pypi.python.org/pypi/cached-property/0.1.5)

2

penso che si può rimuovere ancora più boilerplate da scrivere una classe descrittore personalizzata che decora il metodo caricatore. L'idea è di fare in modo che il descrittore stesso codifichi la logica di caricamento lento, il che significa che l'unica cosa che si definisce in un metodo effettivo è il caricatore stesso (che è l'unica cosa che, apparentemente, deve davvero variare per valori diversi).Ecco un esempio:

class LazyDesc(object): 
    def __init__(self, func): 
     self.loader = func 
     self.secretAttr = '_' + func.__name__ 

    def __get__(self, obj, cls): 
     try: 
      return getattr(obj, self.secretAttr) 
     except AttributeError: 
      print("Lazily loading", self.secretAttr) 
      self.loader(obj) 
      return getattr(obj, self.secretAttr) 

class State(object): 
    @LazyDesc 
    def positions(self): 
     self._positions = {'some': 'positions'} 

    @LazyDesc 
    def forces(self): 
     self._forces = {'some': 'forces'} 

Poi:

>>> x = State() 
>>> x.forces 
Lazily loading _forces 
{'some': 'forces'} 
>>> x.forces 
{'some': 'forces'} 
>>> x.positions 
Lazily loading _positions 
{'some': 'positions'} 
>>> x.positions 
{'some': 'positions'} 

Si noti che il messaggio "lazy loading" è stato stampato solo sulla prima di accesso per ogni attributo. Questa versione crea automaticamente l'attributo "segreto" per contenere i dati reali anteponendo un trattino basso al nome del metodo (cioè, i dati per positions sono memorizzati in _positions. In questo esempio, non c'è setter, quindi non puoi fare x.positions = blah (anche se è ancora possibile mutare le posizioni con x.positions['key'] = val), ma l'approccio potrebbe essere esteso per permettere l'impostazione pure.

la cosa bella di questo approccio è che la logica pigro è trasparente codificato nel descrittore __get__, nel senso che facilmente generalizzabile ad altri tipi di piastre che potresti voler astrarre in modo simile

Problemi correlati