2009-09-22 14 views
27

Sto cercando un modo per caricare pigramente una variabile a livello di modulo.Variabili del modulo Lazy: può essere fatto?

In particolare, ho scritto una piccola libreria Python per parlare con iTunes, e voglio avere una variabile del modulo DOWNLOAD_FOLDER_PATH. Sfortunatamente, iTunes non ti dirà dove si trova la sua cartella di download, quindi ho scritto una funzione che acquisisce il percorso file di alcune tracce di podcast e scala l'albero delle directory fino a trovare la directory "Download".

Questo richiede un secondo o due, quindi mi piacerebbe averlo valutato pigramente, piuttosto che al momento dell'importazione del modulo.

C'è un modo per assegnare pigramente una variabile di modulo quando viene acceduta per la prima volta o devo fare affidamento su una funzione?

risposta

52

Non si può farlo con i moduli, ma si può nascondere una classe "come se" fosse un modulo, per esempio, in itun.py, code ...:

import sys 

class _Sneaky(object): 
    def __init__(self): 
    self.download = None 

    @property 
    def DOWNLOAD_PATH(self): 
    if not self.download: 
     self.download = heavyComputations() 
    return self.download 

    def __getattr__(self, name): 
    return globals()[name] 

# other parts of itun that you WANT to code in 
# module-ish ways 

sys.modules[__name__] = _Sneaky() 

Ora chiunque può import itun. .. e ottieni in effetti l'istanza itun._Sneaky(). Il __getattr__ è lì per farvi accedere qualsiasi altra cosa nel itun.py che può essere più conveniente per voi di codice come un oggetto modulo di livello superiore, che al suo interno _Sneaky! _)

+7

Questo è assolutamente geniale. Un maestro al lavoro. – wbg

0

Se tale variabile è vissuta in una classe piuttosto che in un modulo, è possibile sovraccaricare getattr, o meglio ancora, popolarlo in init.

+0

sì, lo so. Lo volevo in un modulo per il gusto dell'API. In una classe, userei 'property' per farlo, però. Ottimo per il caricamento pigro. – wbg

3

C'è un modo per assegnare pigramente una variabile di modulo quando viene prima consultata o devo fare affidamento su una funzione?

Penso che tu abbia ragione nel dire che una funzione è la migliore soluzione al tuo problema qui. Ti darò un breve esempio per illustrare.

#myfile.py - an example module with some expensive module level code. 

import os 
# expensive operation to crawl up in directory structure 

L'operazione dispendiosa verrà eseguita all'importazione se è a livello di modulo. Non c'è un modo per fermarlo, a parte importare pigramente l'intero modulo !!

#myfile2.py - a module with expensive code placed inside a function. 

import os 

def getdownloadsfolder(curdir=None): 
    """a function that will search upward from the user's current directory 
     to find the 'Downloads' folder.""" 
    # expensive operation now here. 

Seguirai le migliori pratiche utilizzando questo metodo.

+0

Hmmmm. È il modo più ovvio e più semplice per farlo, quindi in linea con lo Zen di Python, ma a me non piace API-wise. – wbg

11

ho usato implementazione Alex' su Python 3.3, ma questo si blocca miseramente: Il codice

def __getattr__(self, name): 
    return globals()[name] 

non è corretto perché un AttributeError dovrebbe essere sollevato, non un KeyError. Questo è schiantato subito sotto Python 3.3, perché un sacco di introspezione è fatto durante l'importazione, alla ricerca di attributi come __path__, __loader__ ecc

Qui è la versione che usiamo oggi nel nostro progetto per consentire le importazioni pigri in un modulo.La __init__ del modulo è ritardata fino a quando il primo accesso attributo che non ha un nome speciale:

""" config.py """ 
# lazy initialization of this module to avoid circular import. 
# the trick is to replace this module by an instance! 
# modelled after a post from Alex Martelli :-) 

Lazy module variables--can it be done?

class _Sneaky(object): 
    def __init__(self, name): 
     self.module = sys.modules[name] 
     sys.modules[name] = self 
     self.initializing = True 

    def __getattr__(self, name): 
     # call module.__init__ after import introspection is done 
     if self.initializing and not name[:2] == '__' == name[-2:]: 
      self.initializing = False 
      __init__(self.module) 
     return getattr(self.module, name) 

_Sneaky(__name__) 

il modulo deve ora definire una funzione init. Questa funzione può essere utilizzata per importare moduli che potrebbero importare stessi:

def __init__(module): 
    ... 
    # do something that imports config.py again 
    ... 

Il codice può essere messo in un altro modulo, e può essere esteso con proprietà come negli esempi precedenti.

Forse è utile per qualcuno.

2

Recentemente ho trovato lo stesso problema e ho trovato un modo per farlo.

class LazyObject(object): 
    def __init__(self): 
     self.initialized = False 
     setattr(self, 'data', None) 

    def init(self, *args): 
     #print 'initializing' 
     pass 

    def __len__(self): return len(self.data) 
    def __repr__(self): return repr(self.data) 

    def __getattribute__(self, key): 
     if object.__getattribute__(self, 'initialized') == False: 
      object.__getattribute__(self, 'init')(self) 
      setattr(self, 'initialized', True) 

     if key == 'data': 
      return object.__getattribute__(self, 'data') 
     else: 
      try: 
       return object.__getattribute__(self, 'data').__getattribute__(key) 
      except AttributeError: 
       return super(LazyObject, self).__getattribute__(key) 

Con questo LazyObject, è possibile definire un metodo init per l'oggetto e l'oggetto verrà inizializzato pigramente, esempio di codice si presenta come:

o = LazyObject() 
def slow_init(self): 
    time.sleep(1) # simulate slow initialization 
    self.data = 'done' 
o.init = slow_init 

l'o oggetto di cui sopra avranno esattamente la stessa metodi qualunque 'done' oggetto hanno, per esempio, si può fare:

# o will be initialized, then apply the `len` method 
assert len(o) == 4 

completa codice con test (lavora a 2.7) si possono trovare qui:

https://gist.github.com/observerss/007fedc5b74c74f3ea08

Problemi correlati