8

我一直在尝试学习 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 新手,我不知道我是否做错了什么,或者这只是语言的限制。我希望是前者。:-)

4

6 回答 6

8

问题在于你的范围,而不是你的闭包。如果您准备阅读大量内容,那么您可以尝试http://www.python.org/dev/peps/pep-3104/

如果不是这种情况,这是简单的解释:

问题出在声明中global getglobal指的是最外层的作用域,并且由于没有任何全局函数get,所以它会抛出。

您需要的是封闭范围内变量的访问说明符,而不是全局范围。

在 python 3.0 中,正如我测试过的,nonlocal关键字正是你需要的,代替global.

nonlocal get
...

在 python 2.x 中,我刚刚删除了global getoldget引用,它可以正常工作。

于 2009-02-03T00:26:25.580 回答
8
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);不过,我也做了一些改进,同时尽量保持你****闭的使用。通常缓存是一个字典而不是闭包的链表。

于 2009-02-03T00:57:24.253 回答
1

您想放在每个global get函数的开头(除了它本身)。get

thedef get是对 name 的赋值get,因此您希望在此之前被声明为全局。

放入mfunglobal get和 vset 使它们工作。我无法指出使这成为必要的范围规则,但它有效;-)

你的缺点也很狡猾...... :)

于 2009-02-03T00:24:59.750 回答
1

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

这样您就不需要分配给捕获的变量名称,但仍然可以获得您想要的。

于 2009-02-03T00:58:45.860 回答
0

可能是因为您想要全局获取而不是全局获取?顺便说一句, 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)
于 2009-02-03T00:27:20.803 回答
0

我认为最好的方法是:

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]
于 2009-09-04T15:39:47.203 回答