代码之家  ›  专栏  ›  技术社区  ›  Kyle Cronin

Python中的闭包

  •  7
  • Kyle Cronin  · 技术社区  · 15 年前

    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
    >>> 
    

    6 回复  |  直到 9 年前
        1
  •  8
  •   sykora    15 年前

    问题在于您的范围,而不是闭包。如果你想重读一些书,那么你可以试试 http://www.python.org/dev/peps/pep-3104/ .

    如果不是这样,下面是简单的解释:

    global get . global get

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

    nonlocal 关键字正是您所需要的,而不是 全球的 .

    nonlocal get
    ...
    

    在Python2.x中,我刚刚删除了 全球获取 oldget 引用,它工作正常。

        2
  •  8
  •   Roger Pate Roger Pate    15 年前
    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,而不是闭包的链接列表。

        3
  •  1
  •   Jonas Kölker    15 年前

    你想把 global get 每一个 功能(除 get

    这个 def get 是对名称的赋值 ,所以您希望在此之前声明为全局。

    全球获取 在mfun和vset中使它们工作。我不能指出使这成为必要的范围规则,但它是有效的;-)

    你的妻子也很口齿不清……:)

        4
  •  1
  •   sth Wojciech Parzych    15 年前

    Get 不是全局的,而是局部的,这就是为什么 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
    

    这样,您不需要为捕获的变量名赋值,但仍然可以得到您想要的。

        5
  •  0
  •   Florian Mayer    15 年前

    可能是因为你想要 全球的 在它不是一个全球性的时候获取?

    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)
    
        6
  •  0
  •   ooboo    15 年前

    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]