2011-08-22 17 views
6

Mam zestaw tablic, które są bardzo duże i kosztowne do obliczenia, i nie wszystkie będą koniecznie potrzebne moim kodem w danym uruchomieniu. Chciałbym, aby ich deklaracja była opcjonalna, ale idealnie, bez konieczności przepisywania całego mojego kodu.zmienne leniwy Pythona? lub, opóźnione kosztowne obliczenia

Przykład jak to jest teraz:

x = function_that_generates_huge_array_slowly(0) 
y = function_that_generates_huge_array_slowly(1) 

Przykład tego, co chciałbym zrobić:

x = lambda: function_that_generates_huge_array_slowly(0) 
y = lambda: function_that_generates_huge_array_slowly(1) 
z = x * 5 # this doesn't work because lambda is a function 
     # is there something that would make this line behave like 
     # z = x() * 5? 
g = x * 6 

Podczas korzystania lambda jak wyżej osiąga jeden z pożądanych efektów - wyliczenia tablica jest opóźniona, dopóki nie jest potrzebna - jeśli użyjesz zmiennej "x" więcej niż raz, musisz ją wyliczyć za każdym razem. Chciałbym to obliczyć tylko raz.

EDYCJA: EDYCJA: Po kilku dodatkowych poszukiwaniach wygląda na to, że można zrobić to, co chcę (w przybliżeniu) za pomocą "leniwych" atrybutów w klasie (np. http://code.activestate.com/recipes/131495-lazy-attributes/). Nie sądzę, że jest jakiś sposób na zrobienie czegoś podobnego bez stworzenia oddzielnej klasy?

EDIT2: Próbuję zaimplementować niektóre z rozwiązań, ale biegnę do problemu, bo nie rozumieją różnicy między:

class sample(object): 
    def __init__(self): 
     class one(object): 
      def __get__(self, obj, type=None): 
       print "computing ..." 
       obj.one = 1 
       return 1 
     self.one = one() 

i

class sample(object): 
    class one(object): 
     def __get__(self, obj, type=None): 
      print "computing ... " 
      obj.one = 1 
      return 1 
    one = one() 

Myślę, że niektóre warianty na tych jest to, czego szukam, ponieważ drogie zmienne mają być częścią klasy.

+0

http://stackoverflow.com/questions/5078726/setting-a-property-inside-a-python-method jest bardziej użyteczną implementacją lenistwa dla tego co próbuję zrobić – keflavich

Odpowiedz

6

Pierwsza połowa problemu (Ponowne użycie wartości) można łatwo rozwiązać:

class LazyWrapper(object): 
    def __init__(self, func): 
     self.func = func 
     self.value = None 
    def __call__(self): 
     if self.value is None: 
      self.value = self.func() 
     return self.value 

lazy_wrapper = LazyWrapper(lambda: function_that_generates_huge_array_slowly(0)) 

ale nadal trzeba używać go jako lazy_wrapper() nie lasy_wrapper.

Jeśli masz zamiar zostać dostępu do niektórych zmiennych wiele razy, może być szybsze w użyciu:

class LazyWrapper(object): 
    def __init__(self, func): 
     self.func = func 
    def __call__(self): 
     try: 
      return self.value 
     except AttributeError: 
      self.value = self.func() 
      return self.value 

które uczynią pierwsze wywołanie wolniejsze i późniejsze zastosowania szybciej.

Edytuj: Widzę, że znalazłeś podobne rozwiązanie, które wymaga użycia atrybutów na zajęciach. W obu przypadkach musisz przepisać każdy dostęp do zmiennej, więc po prostu wybierz, co chcesz.

Edit 2: Można również zrobić:

class YourClass(object) 
    def __init__(self, func): 
     self.func = func 
    @property 
    def x(self): 
     try: 
      return self.value 
     except AttributeError: 
      self.value = self.func() 
      return self.value 

jeśli chcesz uzyskać dostęp do x jako atrybut instancji. Żadna dodatkowa klasa nie jest potrzebna. Jeśli nie chcesz zmieniać podpisu klasy (wymagając func, możesz zakodować połączenie funkcji do właściwości.

+0

Może, jeśli zamiast '__call__ 'Użyłeś właściwości, możliwe byłoby uniknięcie'() 'i byłoby łatwiej wyszukiwać/zamieniać. To zależy od istniejącego kodu. – 9000

+0

Nie widzę, jak to miało znaczenie w wyszukiwaniu/zamianie? A ty po prostu zastępujesz '()' czymś innym, wydaje się to subiektywnym pytaniem, które wolisz. Moja metoda to tylko najmniejszy sposób implementacji kodu. – agf

+0

Podoba mi się druga LazyWrapper ... Dam ci szansę. Rozwiązanie, które znalazłem, zawierało zbyt wiele warstw abstrakcji, aby w moim kontekście objąć głowę. – keflavich

6

Pisanie klasa jest bardziej wytrzymałe, ale optymalizacja dla uproszczenia (co moim zdaniem prosicie), wymyśliłem następujące rozwiązanie:

cache = {} 

def expensive_calc(factor): 
    print 'calculating...' 
    return [1, 2, 3] * factor 

def lookup(name): 
    return (cache[name] if name in cache 
     else cache.setdefault(name, expensive_calc(2))) 

print 'run one' 
print lookup('x') * 2 

print 'run two' 
print lookup('x') * 2 
+0

To nie jest zły pomysł, ponieważ jest prostszy, ale wymaga więcej przepisywania kodu. Spróbuję najpierw powyższego LazyWrappera. – keflavich

+0

Gringo - Skończyło się na tym, że użyłem twojej sugestii oprócz powyższych (dwa różne przypadki użycia w tym samym kodzie), ale odpowiedź agfa bardziej bezpośrednio odpowiedziała na pytanie, które zadaję. Dzięki! – keflavich

+1

OK, ale prosiłeś o rozwiązanie bezklasowe, dlatego tak się stało. Używanie defaultdict to kolejny pomysł, jaki miałem, przy okazji. –

1

nie można dokonać prostego imię, jak x, aby naprawdę ocenić leniwie. Nazwa jest po prostu wpisem w tablicy mieszającej (na przykład w tym, co zwraca locals() lub). Dopóki nie podłączysz metod dostępu do tych tabel systemowych, nie możesz dołączyć wykonywania kodu do prostego rozpoznawania nazw.

Ale można owijać funkcje w owijarki buforujące na różne sposoby. Jest to sposób OO:

class CachedSlowCalculation(object): 
    cache = {} # our results 

    def __init__(self, func): 
     self.func = func 

    def __call__(self, param): 
     already_known = self.cache.get(param, None) 
     if already_known: 
      return already_known 
     value = self.func(param) 
     self.cache[param] = value 
     return value 

calc = CachedSlowCalculation(function_that_generates_huge_array_slowly) 

z = calc(1) + calc(1)**2 # only calculates things once 

To bezklasowe sposób:

def cached(func): 
    func.__cache = {} # we can attach attrs to objects, functions are objects 
    def wrapped(param): 
     cache = func.__cache 
     already_known = cache.get(param, None) 
     if already_known: 
      return already_known 
     value = func(param) 
     cache[param] = value 
     return value 
    return wrapped 

@cached 
def f(x): 
    print "I'm being called with %r" % x 
    return x + 1 

z = f(9) + f(9)**2 # see f called only once 

W realnym świecie dodasz jakąś logikę, aby zachować pamięć podręczną do rozsądnej wielkości, ewentualnie stosując algorytm LRU .

+0

Co to jest algorytm LRU? Podoba mi się twoje podejście do problemu - jest ono podobne do powyższego Gringo, ale może w niektórych przypadkach bardziej eleganckie - ale mój problem jest dość dobrze rozwiązany przez atrybuty klasowe. Właściwie nie potrzebuję zmiennej globalnej "x" do leniwie ocenianej; Potrzebuję atrybutów klasowych, aby leniwie ocenić. Twoje rozwiązanie jest dobre w poprzednim przypadku. – keflavich

+0

Z ciekawości, choć ... czy locals() dict/hashtable jest klasą, która może mieć nadpisany atrybut? Myślę, że to byłby okropny pomysł, ale czy to w ogóle możliwe? – keflavich

+0

LRU = [Ostatnio używane] (http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used): po utworzeniu lub uzyskaniu dostępu do wpisu w pamięci podręcznej umieszczasz go na początku listy; najmniej ostatnio używany wpis jest odrzucany, jeśli lista zbyt długo się rozwija. Zarówno 'locals()' jak i 'globals()' zwracają bazujący na C dict z slotami metod tylko do odczytu, bez szansy na przesłonięcie. – 9000

Powiązane problemy