I'm trying to implement a closure in Python 2.6 and I need to access a nonlocal variable but it seems like this keyword is not available in python 2.x. How should one access nonlocal variables in closures in these versions of python?
10 回答
内部函数可以读取2.x 中的非局部变量,只是不能重新绑定它们。这很烦人,但你可以解决它。只需创建一个字典,并将您的数据作为元素存储在其中。不禁止内部函数改变非局部变量引用的对象。
要使用 Wikipedia 中的示例:
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 的答案,但与该答案相反,它确实正确处理了外部函数的多次调用。“变量”inner.y
对于当前的outer
. 只有它不是一个变量,因为这是被禁止的,而是一个对象属性(对象是函数inner
本身)。这很丑(注意属性只能在inner
函数定义后创建)但看起来很有效。
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 的示例:
def outer():
class context:
y = 0
def inner():
context.y += 1
return context.y
return inner
然后
f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4
每个 external() 调用都会创建一个新的、不同的类,称为 context(不仅仅是一个新实例)。所以它避免了@Nathaniel对共享上下文的提防。
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
我认为这里的关键是您所说的“访问”。读取闭包范围之外的变量应该没有问题,例如,
x = 3
def outer():
def inner():
print x
inner()
outer()
应该按预期工作(打印 3)。但是,覆盖 x 的值不起作用,例如,
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
仍会打印 3。根据我对PEP-3104的理解,这就是 nonlocal 关键字的含义。正如 PEP 中提到的,您可以使用一个类来完成同样的事情(有点乱):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
在 Python 2 中还有另一种实现非局部变量的方法,以防这里的任何答案由于某种原因是不可取的:
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
在变量的赋值语句中使用函数的名称是多余的,但对我来说它看起来比将变量放入字典中更简单、更清晰。就像 Chris B. 的回答一样,从一个电话到另一个电话都会记住该值。
这是受到阿洛伊斯·马赫达尔(Alois Mahdal)在对另一个答案的评论中提出的建议的启发:
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)
There is a wart in python's scoping rules - assignment makes a variable local to its immediately enclosing function scope. For a global variable, you would solve this with the global
keyword.
The solution is to introduce an object which is shared between the two scopes, which contains mutable variables, but is itself referenced through a variable which is not assigned.
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
An alternative is some scopes hackery:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
You might be able to figure out some trickery to get the name of the parameter to outer
, and then pass it as varname, but without relying on the name outer
you would like need to use a Y combinator.
另一种方法(虽然它太冗长):
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
将上面的 Martineau 优雅解决方案扩展到一个实用且不太优雅的用例,我得到:
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)
使用全局变量
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答案
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
其中用户需要声明一个全局变量ranks
,每次都需要调用report
. 我的改进消除了从用户初始化函数变量的需要。