2009-02-03 11 views
6

Próbowałam nauczyć się Pythona, a ja jestem entuzjastą za pomocą zamknięć w Pythonie, miewam problemy z uzyskaniem kodu do pracy prawidłowo:Zamknięcia w Pythonie

def memoize(fn): 
    def get(key): 
     return (False,) 

    def vset(key, value): 
     global get 
     oldget = get 
     def newget(ky): 
      if key==ky: return (True, value) 
      return oldget(ky) 
     get = newget 

    def mfun(*args): 
     cache = get(args) 
     if (cache[0]): return cache[1] 

     val = apply(fn, args) 
     vset(args, val) 
     return val 

    return mfun 

def fib(x): 
    if x<2: return x 
    return fib(x-1)+fib(x-2) 

def fibm(x): 
    if x<2: return x 
    return fibm(x-1)+fibm(x-2) 

fibm = memoize(fibm) 

Zasadniczo, co to ma zrobić, to użyć zamknięć, aby utrzymać zapamiętany stan funkcji. Zdaję sobie sprawę, że prawdopodobnie jest o wiele szybszy, łatwiejszy do odczytania i ogólnie bardziej "Pythoniczny" sposób na wdrożenie tego; jednak moim celem jest dokładne zrozumienie, w jaki sposób zamknięcia działają w języku Python i jak różnią się one od Lispa, więc nie interesują mnie alternatywne rozwiązania, tylko dlaczego mój kod nie działa i co mogę zrobić (jeśli w ogóle), aby naprawić to.

Problem używam do jest, gdy próbuję użyć fibm - Python twierdzi, że get nie jest zdefiniowana:

Python 2.6.1 (r261:67515, Feb 1 2009, 11:39:55) 
[GCC 4.0.1 (Apple Inc. build 5488)] on darwin 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import memoize 
>>> memoize.fibm(35) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "memoize.py", line 14, in mfun 
    cache = get(args) 
NameError: global name 'get' is not defined 
>>> 

Widząc jak jestem nowy w Pythonie, ja nie wiem, czy Zrobiłem coś złego, lub jeśli jest to tylko ograniczenie tego języka. Mam nadzieję, że to ta pierwsza. :-)

Odpowiedz

7

Problem jest w scopingu, a nie w swoim zamknięć . Jeśli masz ochotę na ciężką lekturę, możesz spróbować http://www.python.org/dev/peps/pep-3104/.

Jeśli to nie jest przypadek, tutaj jest proste wyjaśnienie:

Problem jest w rachunku global get. global odnosi się do najbardziej oddalonego zakresu, a ponieważ nie ma żadnej globalnej funkcji, wyrzuca.

To, czego potrzebujesz, to specyfikator dostępu do zmiennych w otaczającym zakresie, a nie zasięg globalny.

W pytonie 3.0, tak jak przetestowałem, słowo kluczowe nonlocal jest dokładnie tym, czego potrzebujesz, w miejsce global.

nonlocal get 
... 

w Pythonie 2.x, po prostu usunął global get i odniesień oldget i działa prawidłowo.

+0

Jest to wada w języku. Miałem nadzieję, że tego nie powiesz. :-(Możliwym obejściem jest przechowywanie wartości w zmiennej strukturze (takiej jak lista) i modyfikowanie tego, ale to jest dość hackowskie. Potrzebowałem wersji 2.X do kompatybilności, ale być może będę musiał zrobić skok do 3000. –

+1

Istnieje obejście tego problemu, dość proste, o ile nie zakłóci to twojej koncepcji czystości - po prostu spraw, aby get() stał się funkcją globalną To powiedziawszy, muszę powiedzieć, że twoja metoda zapamiętywania wydaje mi się zbyt skomplikowana. :) – sykora

+0

Jak to działa poprawnie? Usuwa tylko linie "global get" i "oldget = get" i nigdy nie używa zapisanej wartości. –

0

Chcesz umieścić global get na początku dla każdej funkcji (z wyjątkiem samego siebie get).

the def get to przypisanie do nazwy get, więc chcesz, aby przed tym ogłoszeniem został uznany za globalny.

Wstawianie global get w mfun i vset powoduje ich działanie. Nie mogę wskazać na zasady dotyczące zakresu sprawia, że ​​jest to konieczne, ale działa ;-)

Twoje Wagoniki są dość lispy zbyt ... :)

+0

Hmm .. na moim komputerze, wprowadzenie globalnego dostać się zdobyć i Vset nie działa. Dodanie go do początku samego memoize działało, ale to powoduje, że każde wywołanie memoize używa tej samej funkcji get (nie jest pożądane). –

+0

ah tak. Uwaga dla siebie: pić kawę, obudź się, * następnie * odpowiedz ;-) –

0

Prawdopodobnie dlatego, że chcesz uzyskać globalny, gdy nie jest globalny? Nawiasem mówiąc, zastosowanie jest przestarzałe, zamiast tego użyj fn (* args).

def memoize(fn): 
    def get(key): 
     return (False,) 

    def vset(key, value): 
     def newget(ky): 
      if key==ky: return (True, value) 
      return get(ky) 
     get = newget 

    def mfun(*args): 
     cache = get(args) 
     if (cache[0]): return cache[1] 

     val = fn(*args) 
     vset(args, val) 
     return val 

    return mfun 

def fib(x): 
    if x<2: return x 
    return fib(x-1)+fib(x-2) 

def fibm(x): 
    if x<2: return x 
    return fibm(x-1)+fibm(x-2) 

fibm = memoize(fibm) 
+0

Dzięki za podpowiedź o zastosowaniu. Jednak uruchomienie zmodyfikowanego kodu powyżej działa w tym sensie, że daje poprawną odpowiedź, ale w ogóle nie korzysta z tej funkcji. (możesz to zweryfikować, uruchamiając coś takiego jak fibm (35), które, jeśli zostanie zapamiętane, powinno być bliskie natychmiastowego: –

+0

) <- close paren ;-) –

+0

x = y przypisuje do (lub tworzy) zmienną lokalną x, chyba że gdzieś w tej funkcji znajduje się stwierdzenie globalne. –

7
def memoize(fn): 
    get = [lambda key: (False, None)] 

    def vset(args): 
    value = fn(*args) 
    oldget = get[0] 
    def newget(key): 
     if args == key: 
     return (True, value) 
     return oldget(key) 
    get[0] = newget 
    return value 

    def mfun(*args): 
    found, value = get[0](args) 
    if found: 
     return value 
    return vset(args) 

    return mfun 

CALLS = 0 

def fib(x): 
    global CALLS 
    CALLS += 1 
    if x<2: return x 
    return fib(x-1)+fib(x-2) 

@memoize 
def fibm(x): 
    global CALLS 
    CALLS += 1 
    if x<2: return x 
    return fibm(x-1)+fibm(x-2) 

CALLS = 0 
print "fib(35) is", fib(35), "and took", CALLS, "calls" 
CALLS = 0 
print "fibm(35) is", fibm(35), "and took", CALLS, "calls" 

wyjściowa wynosi:

fib(35) is 9227465 and took 29860703 calls 
fibm(35) is 9227465 and took 36 calls 

podobne do innych odpowiedzi, jednak ten jeden działa. :)

Ważną zmianą w stosunku do kodu w pytaniu jest przypisanie do nie-globalnego, nielokalnego (get); Jednak wprowadziłem pewne ulepszenia, próbując jednocześnie ograniczyć użycie kasa * kaszel ** kaszel * kasa *. Zwykle pamięć podręczna jest dyktowana zamiast połączonej listy zamknięć.

+0

To nie jest idealne, ale sposób obejścia działa wystarczająco dobrze, jak przypuszczam. Dzięki za polerowanie kodu również. –

+0

IMHO, używanie tego kodu do czegoś więcej niż poznawanie sposobu, w jaki Python obsługuje zamknięcia jest skazane. Inne zmiany bardziej popychają Cię w dobrym kierunku niż cokolwiek innego. –

+0

To był główny cel. Gdybym kodował to w Pythonie, zamiast tego używałbym słowników, ale doceniam małe zmiany, takie jak ustawianie wielu wartości w jednej instrukcji i używanie fn (* args) zamiast stosowania. –

1

Get nie jest globalna, ale lokalna dla otaczającej funkcji, dlatego deklaracja global kończy się niepowodzeniem.

Jeśli usuniesz numer global, nadal będzie on działał nieprawidłowo, ponieważ nie można przypisać nazwy przechwyconej zmiennej. Aby obejść ten problem, można użyć obiektu jako zmienna wykonane telefonem zamknięć i nie tylko zmienić właściwości tego obiektu:

class Memo(object): 
    pass 

def memoize(fn): 
    def defaultget(key): 
     return (False,) 

    memo = Memo() 
    memo.get = defaultget 

    def vset(key, value): 
     oldget = memo.get 
     def newget(ky): 
      if key==ky: return (True, value) 
      return oldget(ky) 
     memo.get = newget 

    def mfun(*args): 
     cache = memo.get(args) 
     if cache[0]: return cache[1] 

     val = apply(fn, args) 
     vset(args, val) 
     return val 

    return mfun 

ten sposób nie trzeba przypisać do przechwyconych nazw zmiennych, ale wciąż co chciałeś.

0

Myślę, że najlepszym sposobem byłoby:

class Memoized(object): 
    def __init__(self,func): 
     self.cache = {} 
     self.func = func 
    def __call__(self,*args): 
     if args in self.cache: return cache[args] 
     else: 
      self.cache[args] = self.func(*args) 
      return self.cache[args]