我写了这段代码:
x = 0
def counter():
x = 1
def temp(self):
print x
x += 1
return temp
尝试测试python是词法还是动态范围。我的想法是
y = counter()
y()
应该打印0或1,这会告诉我python是如何作用域的。但是,调用y会抛出一个异常,说x未定义。在理解Python如何工作的过程中,似乎存在一些根本性的缺陷。
有人可以解释这是如何工作的吗?是的,我知道可以使用对象轻松完成。我正试图探索在不使用对象的情况下给出函数状态的想法。我用这种方式编写代码是因为上面翻译成像SCI这样的词汇范围的语言肯定会有效。
答案 0 :(得分:10)
来自the docs:
Python的一个特殊怪癖是 - 如果 没有全球声明生效 - 名称的赋值总是进入 最里面的范围。作业呢 不复制数据 - 它们只是绑定名称 对象。
所以,当Python解析
时 def temp(self):
print x
x += 1
它会看到作业x += 1
,因此决定x
必须位于最里面的范围内。稍后您通过temp(...)
致电y()
- (顺便说一下,self
的定义中应忽略temp
或y()
应该提供一个参数) - Python遇到print x
语句并发现x
尚未在本地(最里面)范围内定义。因此错误,
UnboundLocalError: local variable 'x' referenced before assignment
如果您声明
def temp(self):
global x
然后Python将在全局范围内查找x
(其中x=0
)。
在Python2中,没有办法告诉Python在扩展范围(x
)中查找x=1
。但是在Python3中可以通过声明来实现
def temp(self):
nonlocal x
答案 1 :(得分:6)
Python有两个范围(实际上是三个)以及嵌套本地范围的特殊规则。对于模块级名称,两个范围是 global ,对于函数中的任何内容, local 。您在函数中分配的任何内容都将自动为本地,除非您使用该函数中的global
语句声明其他内容。如果使用未分配给函数中任何位置的名称,则它不是本地名称; Python将(词法)搜索嵌套函数,以查看它们是否具有该名称作为本地名称。如果没有嵌套函数或者其中任何一个名称不是本地函数,则假定该名称是全局的。
(全局命名空间也很特殊,因为它实际上既是模块全局命名空间又是内置命名空间,它隐藏在builtins
或__builtins__
模块中。)
在您的情况下,您有三个 x
变量:一个在模块(全局)范围内,一个在counter
函数中,一个在{{1}中函数 - 因为temp
也是赋值语句。因为分配给名称的这一事实使得它成为函数的本地,所以+=
语句将尝试使用尚未分配的局部变量,这将引发UnboundLocalError。
如果您打算将所有这三个+=
引用用于引用全局变量,则需要在x
和global x
中执行counter
功能。在Python 3.x(但不是2.x)中,有一个temp
声明与nonlocal
非常相似,您可以使用它global
为temp
中的变量赋值,但仅保留全球counter
。
答案 2 :(得分:2)
Python中的闭包不可写,因此您无法以这种方式编写代码。如果你只读取函数内部的变量,你应该是好的。在Python 3中,您可以使用nonlocal
关键字来获取所需的行为。
如果您使用的是Python 2.5或更高版本,您还可以使用yield关键字将上述代码编写为生成器。
答案 3 :(得分:2)
Python文档详细解答了这个问题:http://docs.python.org/reference/executionmodel.html#naming-and-binding
总之,Python有静态作用域规则。如果函数f定义或删除变量名,则变量名引用函数f的闭包中的变量。如果函数f仅使用变量名(无定义或删除),则名称引用f的父作用域中名称的含义。继续前进到父作用域,直到找到定义或达到全局作用域。例如:
def f1(x):
def g(y):
z.foo = y # assigns global z
g(1)
def f2(x):
def g(y):
x.foo = y # assigns f2's variable, because f2 defines x
g(1)
def f3(x):
def g(y):
x = C()
x.foo = y # assigns g's variable, because g defines x
g(1)
global
关键字和(在Python 3中)nonlocal
关键字覆盖默认的范围规则。
当静态解析变量名称时,动态解析变量值。变量的值来自访问变量时该变量的最新定义或删除。在变量所在的函数闭包中查找值。
答案 4 :(得分:1)
感谢您的所有回复。万一有人关心,我有点想出一个解决这个问题的方法。我通过创建一个“scoper”函数来建立一个计数器变量。
>>> def gc():
... def scoper():
... scoper.s = 0
... def rtn():
... scoper.s += 1
... return scoper.s
... return rtn
... return scoper()
以上允许我这样做,模仿正确的闭包:
>>> a = gc()
>>> a()
1
>>> a()
2
>>> a()
3
>>> b = gc()
>>> b()
1
>>> a()
4
>>> b()
2