nonlocal keyword in Python 2.x
我正试图在python 2.6中实现一个闭包,我需要访问一个非局部变量,但这个关键字在python2.x中似乎不可用。在这些版本的python中,如何访问闭包中的非局部变量?
内部函数可以读取2.x中的非局部变量,而不是重新绑定它们。这很烦人,但你可以绕过它。只需创建一个字典,并将数据作为元素存储在其中。内部函数不能改变非局部变量引用的对象。
要使用维基百科的例子:
1 2 3 4 5 6 7 8 9 | def outer(): d = {'y' : 0} def inner(): d['y'] += 1 return d['y'] return inner f = outer() print(f(), f(), f()) #prints 1 2 3 |
下面的解决方案是由Elias Zamaria给出的答案所启发的,但与此相反,该答案确实正确地处理了外部函数的多个调用。"变量"
1 2 3 4 5 6 7 8 9 10 | def outer(): def inner(): inner.y += 1 return inner.y inner.y = 0 return inner f = outer() g = outer() print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2) |
与字典不同的是,非本地类的混乱程度更小。修改@chrisb的示例:
1 2 3 4 5 6 7 | def outer(): class context: y = 0 def inner(): context.y += 1 return context.y return inner |
然后
1 2 3 4 5 | f = outer() assert f() == 1 assert f() == 2 assert f() == 3 assert f() == 4 |
每个outer()调用都创建一个名为context的新的、不同的类(不仅仅是一个新实例)。因此它避免了@nathaniel对共享上下文的注意。
1 2 3 4 5 | g = outer() assert g() == 1 assert g() == 2 assert f() == 5 |
我认为这里的关键是你所说的"进入"。读取闭包范围之外的变量应该没有问题,例如,
1 2 3 4 5 6 | x = 3 def outer(): def inner(): print x inner() outer() |
应按预期工作(打印3)。但是,覆盖x的值不起作用,例如,
1 2 3 4 5 6 7 | x = 3 def outer(): def inner(): x = 5 inner() outer() print x |
仍将打印3。根据我对PEP-3104的理解,这就是非本地关键字要涵盖的内容。正如PEP中提到的,您可以使用类来完成相同的事情(有点混乱):
1 2 3 4 5 6 7 8 9 | class Namespace(object): pass ns = Namespace() ns.x = 3 def outer(): def inner(): ns.x = 5 inner() outer() print ns.x |
在python 2中,还有另一种实现非局部变量的方法,以防由于任何原因,这里的任何答案都是不可取的:
1 2 3 4 5 6 7 8 9 | def outer(): outer.y = 0 def inner(): outer.y += 1 return outer.y return inner f = outer() print(f(), f(), f()) #prints 1 2 3 |
在变量的赋值语句中使用函数名是多余的,但在我看来,它比将变量放入字典更简单、更清晰。这个值从一个呼叫到另一个呼叫都会被记住,就像克里斯B的回答一样。
以下是Alois Mahdal在评论另一个答案时提出的建议:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Nonlocal(object): """ Helper to implement nonlocal names in Python 2.x""" def __init__(self, **kwargs): self.__dict__.update(kwargs) def outer(): nl = Nonlocal(y=0) def inner(): nl.y += 1 return nl.y return inner f = outer() print(f(), f(), f()) # -> (1 2 3) |
更新
最近回顾这一点后,当装饰师意识到将它作为一个整体来实现会使它更通用和有用时(尽管这样做可能会在某种程度上降低它的可读性),我被它是怎样的感觉所震惊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # Implemented as a decorator. class Nonlocal(object): """ Decorator class to help implement nonlocal names in Python 2.x""" def __init__(self, **kwargs): self._vars = kwargs def __call__(self, func): for k, v in self._vars.items(): setattr(func, k, v) return func @Nonlocal(y=0) def outer(): def inner(): outer.y += 1 return outer.y return inner f = outer() print(f(), f(), f()) # -> (1 2 3) |
请注意,这两个版本都适用于python 2和3。
另一种方法(虽然太冗长):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import ctypes def outer(): y = 0 def inner(): ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1)) return y return inner x = outer() x() >> 1 x() >> 2 y = outer() y() >> 1 x() >> 3 |
python的作用域规则中有一个缺点——赋值使变量成为其立即封闭的函数作用域的局部变量。对于全局变量,您可以使用
解决方案是引入一个对象,该对象在两个作用域之间共享,该对象包含可变变量,但本身通过一个未赋值的变量引用。
1 2 3 4 5 | def outer(v): def inner(container = [v]): container[0] += 1 return container[0] return inner |
另一种选择是一些范围黑客:
1 2 3 4 5 | def outer(v): def inner(varname = 'v', scope = locals()): scope[varname] += 1 return scope[varname] return inner |
您可能会想出一些技巧,将参数的名称传递给
将上面的Martineau Elegant解决方案扩展到一个实用且稍微不那么优雅的用例,我得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class nonlocals(object): """ Helper to implement nonlocal names in Python 2.x. Usage example: def outer(): nl = nonlocals( n=0, m=1 ) def inner(): nl.n += 1 inner() # will increment nl.n or... sums = nonlocals( { k:v for k,v in locals().iteritems() if k.startswith('tot_') } ) """ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __init__(self, a_dict): self.__dict__.update(a_dict) |
使用全局变量
1 2 3 4 5 6 7 8 9 10 11 | def outer(): global y # import1 y = 0 def inner(): global y # import2 - requires import1 y += 1 return y return inner f = outer() print(f(), f(), f()) #prints 1 2 3 |
我个人不喜欢全局变量。但是,我的建议是基于https://stackoverflow.com/a/19877437/1083704答案
1 2 3 4 5 6 7 | def report(): class Rank: def __init__(self): report.ranks += 1 rank = Rank() report.ranks = 0 report() |
当用户每次需要调用