[英]Closures in 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)
基本上,这应该做的是使用闭包来维护函数的记忆状态。 我意识到可能有很多更快、更容易阅读并且通常更“Pythonic”的方法来实现这一点; 然而,我的目标是准确了解闭包在 Python 中的工作原理,以及它们与 Lisp 的区别,所以我对替代解决方案不感兴趣,只是为什么我的代码不起作用以及我可以做什么(如果有的话)来修复它。
我遇到的问题是当我尝试使用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 的新手,我不知道我是否做错了什么,或者这是否只是语言的限制。 我希望是前者。 :-)
问题在于你的范围,而不是你的闭包。 如果您想要大量阅读,那么您可以尝试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); 然而,我也做了一些改进,同时试图保持你的*
咳*
破*
咳*
闭包使用。 通常缓存是一个字典而不是闭包的链表。
你想把global get
放在每个函数的开头(除了get
本身)。
def get
是对名称get
的赋值,因此您希望在此之前将 get 声明为全局的。
将global get
放入 mfun 和 vset 使它们工作。 我无法指出使这成为必要的范围规则,但它有效;-)
你的缺点也很狡猾...... :)
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
这样你就不需要分配给捕获的变量名,但仍然得到你想要的。
我认为最好的方法是:
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]
可能是因为您想要全局获取而不是全局获取? 顺便说一句, 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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.