Замыкания в Python
Я пытался изучить Python, и хотя я с энтузиазмом отношусь к использованию замыканий в Python, у меня возникли проблемы с получением кода для правильной работы:
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)
в основном, то, что это должно делать, это использовать замыкания для поддержания memoized состояния функции. Я понимаю, что, вероятно, есть много более быстрых, простых для чтения и в целом более "Питонических" способов реализовать это; однако моя цель - понять, как точно работают замыкания в Python и как они отличается от Лиспа, поэтому меня не интересуют альтернативные решения, просто почему мой код не работает и что я могу сделать (если что), чтобы это исправить.
проблема, с которой я сталкиваюсь, когда я пытаюсь использовать fibm
- Python настаивает на том, что get
не определен:
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
>>>
видя, что я новичок в Python, я не знаю, сделал ли я что-то неправильно, или это просто ограничение языка. Надеюсь, первое. :-)
6 ответов
проблема в вашей области, а не в ваших закрытиях. Если вы готовы к тяжелому чтению, то вы можете попробоватьhttp://www.python.org/dev/peps/pep-3104/.
если это не так, вот простое объяснение:
проблема в отчете global get
. global
относится к внешней области, и поскольку нет никакой глобальной функции get
, он бросает.
что вам нужно, это спецификатор доступа для переменных в охватывающая область, а не глобальная область.
в python 3.0, Как я тестировал,nonlocal
ключевое слово-это именно то, что нужно, на месте global
.
nonlocal get
...
в python 2.x, я только что удалил global get
и oldget
ссылки и он работает правильно.
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"
вывод:
fib(35) is 9227465 and took 29860703 calls
fibm(35) is 9227465 and took 36 calls
подобно другим ответам, однако этот работает. :)
важным изменением из кода в вопросе является назначение нелокального нелокального (get); однако я также сделал некоторые улучшения, пытаясь сохранить ваш *
кашель*
сломанные*
кашель*
использовать закрытие. Обычно кэш является dict вместо связанного списка замыканий.
вы хотите поставить global get
в начале ).
на def get
поручение на имя get
, поэтому вы хотите, чтобы вас объявили глобальным до этого.
положить global get
в mfun и vset заставляет их работать. Я не могу указать на правила определения области, которые делают это необходимым, но это работает; -)
ваш Cons-ячейки слишком довольно lispy... :)
Get
не является глобальным, но локальным для окружающей функции, поэтому global
декларация не удается.
если убрать global
, Он все еще терпит неудачу, потому что вы не можете назначить захваченному имени переменной. Чтобы обойти это, вы можете использовать объект в качестве переменной, захваченной вашими закрытиями, а не просто изменять свойства этого объекта:
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
таким образом, вам не нужно назначать захваченные имена переменных, но все равно получить то, что вы хотели.
наверное, потому, что вы хотите глобальные пока это не глобальная? Кстати, apply устарел, вместо этого используйте 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)
Я думаю, что лучшим способом было бы:
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]