Answers:
内部函数可以读取 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
def inner(): print d; d = {'y': 1}
。在这里,print d
读取外部,d
从而d
在内部范围内创建非局部变量。
X = 1
只是将名称X
与特定对象(int
带有值1
)绑定在一起。 X = 1; Y = X
将两个名称绑定到相同的确切对象。无论如何,有些对象是可变的,您可以更改它们的值。
以下解决方案的灵感来自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)
inc()
和一个dec()
从外部返回的值,表示增加和减少共享计数器。然后,您必须确定将当前计数器值附加到哪个函数,并从另一个函数引用该函数。看起来有些奇怪且不对称。例如dec()
一行inc.value -= 1
。
与字典相比,非本地类的混乱程度更低。修改@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()调用都会创建一个称为上下文的新的独特类(不仅仅是一个新实例)。因此,它避免了@Nathaniel提防共享上下文。
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
__slots__ = ()
和创建对象而不是使用类,例如context.z = 3
会引发一个AttributeError
。所有类都是可能的,除非它们从未定义插槽的类继承。
我认为这里的关键是“访问”的含义。读取闭包范围之外的变量应该没有问题,例如,
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
def ns(): pass
后跟ns.x = 3
。它虽然不漂亮,但在我看来却不那么难看。
class Namespace: x = 3
呢
ns
是一个全局对象,这就是为什么您可以ns.x
在print
语句末尾在模块级别引用的原因。
如果由于任何原因,此处的任何答案都不理想,则可以使用另一种方法在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
在变量的赋值语句中使用函数名称是多余的,但对我来说,比将变量放入字典中看起来更简单和简洁。就像克里斯·B(Chris B.)的回答一样,一个电话会记住另一个电话的价值。
f = outer()
,然后再执行g = outer()
,则f
的计数器将被重置。这是因为他们都共享一个单一 outer.y
变量,而不是每个都具有自己独立的一个。尽管此代码看起来比Chris B的回答更具美感,但如果您想outer
多次调用,他的方法似乎是模仿词汇作用域的唯一方法。
outer.y
不涉及函数调用(实例)本地的任何内容outer()
,而是分配给函数对象的属性,该属性绑定到outer
其包围范围中的名称。因此,一个同样可以使用了,以书面形式outer.y
,任何其他名称代替outer
,只要它是已知在范围进行约束。这样对吗?
outer.y
使用名称inner.y
(因为inner
绑定在call内outer()
,这正是我们想要的作用域),但是将在内部定义inner.y = 0
之后的初始化(因为对象在创建其属性时必须存在),但是当然在?return inner
以下是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)
更新资料
在最近回顾了这一点之后,我对它的装饰风格感到震惊-当我想到将其实现为装饰器将使它更加通用和有用时(尽管这样做无疑会在某种程度上降低其可读性)。
# 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中使用。
python的作用域规则中有一个缺陷-赋值使变量在其立即包含的函数作用域内是局部的。对于全局变量,您可以使用global
关键字解决。
解决方案是引入一个在两个作用域之间共享的对象,该对象包含可变变量,但本身通过未分配的变量引用。
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
另一种选择是一些示波器黑客:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
您可能可以找出一些技巧来将参数的名称获取到outer
,然后将其作为varname传递,但是在不依赖名称的情况下,outer
您需要使用Y组合器。
nonlocal
。locals()
创造了一本字典outer()
在时间s当地人inner()
的定义,但改变这种字典里不会改变v
在outer()
。当您有更多内部函数想要共享一个封闭的over变量时,这将不再起作用。说一个inc()
,dec()
然后递增和递减一个共享计数器。
nonlocal
是python 3功能。
nonlocal
在Python 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
将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
。我的改进消除了从用户初始化函数变量的需要。
inner
,但不能为其分配实例,但是可以修改其键和值。这样可以避免使用全局变量。