2009-09-22 23 views
27

Próbuję znaleźć sposób, aby leniwie załadować zmienną poziomu modułu.Leniwe zmienne modułu - czy można to zrobić?

W szczególności napisałem małą bibliotekę Pythona, aby porozmawiać z iTunes, i chcę mieć zmienną modułu DOWNLOAD_FOLDER_PATH. Niestety, iTunes nie powie Ci, gdzie znajduje się jego folder pobierania, dlatego napisałem funkcję, która pobiera ścieżkę do pliku kilku ścieżek podcastu i wspina się po drzewie katalogów aż do znalezienia katalogu "Pobrane pliki".

Zajmuje to sekundę lub dwie, więc chciałbym, aby był oceniany leniwie, a nie w czasie importu modułu.

Czy istnieje sposób na leniwe przypisanie zmiennej modułu podczas pierwszego dostępu do niej lub czy będę musiał polegać na funkcji?

Odpowiedz

52

Nie można zrobić z modułów, ale można zamaskować klasę „jak gdyby” był to moduł, na przykład, w itun.py, kod ...:

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() 

Teraz każdy może import itun. .. i uzyskaj w rzeczywistości instancję itun._Sneaky(). __getattr__ jest tam, aby dać Ci dostęp niczego innego w itun.py, które mogą być bardziej wygodne, aby zakodować jako moduł obiektu najwyższego poziomu, niż wewnątrz _Sneaky! _)

+7

To jest absolutnie genialne. Mistrz w pracy. – wbg

0

Jeśli ta zmienna żyła w klasie, a nie w module, to możesz przeciążyć getattr, lub jeszcze lepiej wypełnić go w init.

+0

Tak, wiem. Chciałem go w module ze względu na API. W klasie użyłbym 'property', aby to zrobić. Doskonały do ​​leniwego ładowania. – wbg

3

Czy istnieje sposób na leniwe przypisanie zmiennej modułu, gdy jest ona dostępna po raz pierwszy lub czy będę musiała polegać na funkcji?

Myślę, że masz rację, mówiąc, że funkcja jest najlepszym rozwiązaniem dla twojego problemu tutaj. Podam ci krótki przykład do zilustrowania.

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

import os 
# expensive operation to crawl up in directory structure 

Kosztowna operacja zostanie wykonana przy imporcie, jeśli jest na poziomie modułu. Nie ma sposobu, aby temu zapobiec, nie leniwie importując cały moduł !!

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

Będziesz postępować zgodnie z najlepszą praktyką przy użyciu tej metody.

+0

Hmmmm. Jest to oczywisty i najprostszy sposób, aby to zrobić, więc zgodnie z Zen of Python, ale po prostu nie podoba mi się to pod kątem API. – wbg

11

użyłem realizację Alex”na Python 3.3, ale to wywala marnie: Kod

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

nie jest poprawna, ponieważ AttributeError powinien zostać podniesiony, a nie KeyError. ten rozbił się zaraz pod Pythona 3.3, ponieważ wiele introspekcji odbywa podczas importu, szukając cech jak __path__, __loader__ itp

Oto wersja, że ​​używamy teraz w naszym projekcie, aby umożliwić leniwych importu w module.__init__ modułu jest opóźnione aż do pierwszego dostępu atrybutu że nie ma specjalną nazwę:

""" 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__) 

Moduł teraz musi zdefiniować startowy funkcję. Funkcja ta może być stosowana importować moduły, które mogą się importowania:

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

Kod może być wprowadzane do innego modułu, i może być rozszerzony o właściwościach jak w powyższych przykładach.

Może to jest przydatne dla kogoś.

2

Niedawno natrafiłem na ten sam problem i znalazłem sposób, aby to zrobić.

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) 

Dzięki tej LazyObject można zdefiniować metodę init dla obiektu, a obiekt zostanie zainicjowany leniwie, przykładowy kod wygląda następująco:

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

przedmiotem o powyżej będą miały dokładnie takie same metody, które każdy obiekt 'done' mają, na przykład, możesz zrobić:

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

kompletny Kod z testów (działa w 2.7) można znaleźć tutaj:

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

Powiązane problemy