我正在尝试在Python 2.6中实现一个闭包,我需要访问一个非局部变量,但似乎这个关键字在python 2.x中不可用。如何在这些python版本中访问闭包中的非局部变量?
答案 0 :(得分:114)
内部函数可以在2.x中读取非局部变量,而不是重新绑定它们。这很烦人,但你可以解决它。只需创建一个字典,并将数据存储在其中。 变异非局部变量引用的对象不禁止内部函数。
使用维基百科的例子:
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
答案 1 :(得分:37)
以下解决方案受到answer by 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)
答案 2 :(得分:28)
与非本地课程相比,不是字典。修改@ ChrisB' example:
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
每个outer()调用都会创建一个名为context的新的不同类(不仅仅是一个新实例)。因此,它避免了@Nathaniel's beware关于共享上下文。
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
答案 3 :(得分:14)
我认为这里的关键是“访问”的意思。读取闭包范围之外的变量应该没有问题,例如,
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的理解,这是非本地关键字的含义。正如PEP中提到的,你可以使用一个类来完成同样的事情(有点混乱):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
答案 4 :(得分:12)
在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.的回答一样。
答案 5 :(得分:10)
以下是Alois Mahdal在comment中就另一个answer提出的建议的启发:
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)
<强>更新强>
最近回顾过这个问题之后,我很惊讶它是如何装饰的 - 当我突然意识到将它作为一个实现它会使它更通用&amp;有用(尽管这样做可以说会在一定程度上降低其可读性)。
# 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。
答案 6 :(得分:3)
python的范围规则中存在一个瑕疵 - 赋值使其变量本地化为其直接封闭的函数范围。对于全局变量,您可以使用global
关键字解决此问题。
解决方案是引入一个在两个作用域之间共享的对象,它包含可变变量,但它本身是通过未分配的变量引用的。
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
另一种选择是一些范围hackery:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
您可以找出一些技巧来获取参数的名称outer
,然后将其作为varname传递,但不依赖于您希望使用的名称outer
Y组合器。
答案 7 :(得分:3)
另一种方法(虽然它太冗长):
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
答案 8 :(得分:0)
将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)
答案 9 :(得分:-3)
使用全局变量
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
。我的改进消除了从用户初始化函数变量的需要。