Edit:
请参阅我在这个问题底部的完整答案。
tl;博士回答:Python 具有静态嵌套作用域。这static方面可以与隐式变量声明交互,产生不明显的结果。
(这可能特别令人惊讶,因为该语言通常是动态的)。
我以为我对 Python 的作用域规则有很好的掌握,但是这个问题让我彻底陷入困境,而且我的 google-fu 也让我失败了(并不是说我感到惊讶 - 看问题标题;)
我将从几个按预期工作的示例开始,但请随意跳至示例 4 以了解有趣的部分。
示例 1。
>>> x = 3
>>> class MyClass(object):
... x = x
...
>>> MyClass.x
3
足够简单:在类定义期间,我们能够访问外部(在本例中为全局)范围中定义的变量。
示例 2.
>>> def mymethod(self):
... return self.x
...
>>> x = 3
>>> class MyClass(object):
... x = x
... mymethod = mymethod
...
>>> MyClass().mymethod()
3
再次(暂时忽略why有人可能想要这样做),这里没有什么意外的:我们可以访问外部作用域中的函数。
Note:正如 Frédéric 下面指出的,这个功能似乎不起作用。请参阅示例 5(及后续示例)。
示例 3.
>>> def myfunc():
... x = 3
... class MyClass(object):
... x = x
... return MyClass
...
>>> myfunc().x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in myfunc
File "<stdin>", line 4, in MyClass
NameError: name 'x' is not defined
This is essentially the same as example 1: we're accessing the outer scope from within the class definition, just this time that scope isn't global, thanks to myfunc()
.
Edit 5: As @user3022222 指出如下,我在原来的帖子中搞砸了这个例子。我相信这会失败,因为只有函数(而不是其他代码块,如此类定义)可以访问封闭范围内的变量。对于非功能代码块,只能访问局部变量、全局变量和内置变量。更全面的解释可以在这个问题
多一个:
例 4.
>>> def my_defining_func():
... def mymethod(self):
... return self.y
... class MyClass(object):
... mymethod = mymethod
... y = 3
... return MyClass
...
>>> my_defining_func()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in my_defining_func
File "<stdin>", line 5, in MyClass
NameError: name 'mymethod' is not defined
嗯……对不起?
这与示例 2 有何不同?
我完全糊涂了。请帮我安排一下。
谢谢!
附:如果这不仅仅是我的理解问题,我已经在 Python 2.5.2 和 Python 2.6.2 上尝试过了。不幸的是,这些是我目前可以访问的所有内容,但它们都表现出相同的行为。
Edit根据http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces:在执行期间的任何时候,至少有三个嵌套作用域,其命名空间可直接访问:
- 最里面的范围,即
首先搜索,包含本地
名字
- 任何封闭的范围
搜索到的函数
从最近的封闭开始
范围,包含非本地,但也
非全局名称
- 倒数第二个范围包含
当前模块的全局名称
- 最外层范围(最后搜索)
是包含内置的命名空间
名字
#4。似乎是第二个的反例。
Edit 2
实施例5.
>>> def fun1():
... x = 3
... def fun2():
... print x
... return fun2
...
>>> fun1()()
3
Edit 3
正如 @Frédéric 指出的那样,对与外部作用域中同名的变量进行赋值似乎“屏蔽”了外部变量,从而阻止了赋值的功能。
所以示例 4 的修改版本有效:
def my_defining_func():
def mymethod_outer(self):
return self.y
class MyClass(object):
mymethod = mymethod_outer
y = 3
return MyClass
my_defining_func()
然而这并没有:
def my_defining_func():
def mymethod(self):
return self.y
class MyClass(object):
mymethod_temp = mymethod
mymethod = mymethod_temp
y = 3
return MyClass
my_defining_func()
我仍然不完全理解为什么会发生这种屏蔽:赋值发生时名称绑定不应该发生吗?
这个例子至少提供了一些提示(以及更有用的错误消息):
>>> def my_defining_func():
... x = 3
... def my_inner_func():
... x = x
... return x
... return my_inner_func
...
>>> my_defining_func()()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in my_inner_func
UnboundLocalError: local variable 'x' referenced before assignment
>>> my_defining_func()
<function my_inner_func at 0xb755e6f4>
因此,局部变量似乎是在函数创建时定义的(成功),导致局部名称被“保留”,从而在调用函数时屏蔽外部范围名称。
有趣的。
感谢弗雷德里克的回答!
作为参考,来自python 文档:
重要的是要认识到范围
由文本确定:全局
a 中定义的函数范围
module 是该模块的命名空间,不是
无论来自何处或通过什么别名
函数被调用。另一方面,
实际的名称搜索已完成
动态地,在运行时——然而,
语言定义正在演变
面向静态名称解析,位于
“编译”时间,所以不要依赖
动态名称解析! (实际上,
局部变量已经确定
静态地。)
Edit 4
真正的答案
这种看似令人困惑的行为是由 Python 引起的PEP 227 中定义的静态嵌套范围。它实际上与PEP 3104.
来自 PEP 227:
名称解析规则是典型的
对于静态范围的语言 [...]
[例外] 未声明变量。
如果发生名称绑定操作
函数中的任何位置,然后是该名称
被视为函数的局部函数
所有引用均指本地
捆绑。如果引用发生在之前
名称已绑定,NameError 是
上调。
[...]
蒂姆彼得斯的一个例子展示了潜在的陷阱
没有声明的嵌套范围:
i = 6
def f(x):
def g():
print i
# ...
# skip to the next page
# ...
for i in x: # ah, i *is* local to f, so this is what g sees
pass
g()
对 g() 的调用将通过 for 引用 f() 中绑定的变量 i
环形。如果在执行循环之前调用 g(),则会出现 NameError
被抚养。
让我们运行 Tim 示例的两个更简单的版本:
>>> i = 6
>>> def f(x):
... def g():
... print i
... # ...
... # later
... # ...
... i = x
... g()
...
>>> f(3)
3
when g()
没有找到i
在其内部范围内,它动态地向外搜索,找到i
in f
的范围,这已被绑定到3
通过i = x
任务。
但是改变最后两个语句的顺序f
导致错误:
>>> i = 6
>>> def f(x):
... def g():
... print i
... # ...
... # later
... # ...
... g()
... i = x # Note: I've swapped places
...
>>> f(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in f
File "<stdin>", line 3, in g
NameError: free variable 'i' referenced before assignment in enclosing scope
请记住 PEP 227 说过“名称解析规则对于静态作用域语言来说是典型的”,让我们看看(半)等效的 C 版本提供:
// nested.c
#include <stdio.h>
int i = 6;
void f(int x){
int i; // <--- implicit in the python code above
void g(){
printf("%d\n",i);
}
g();
i = x;
g();
}
int main(void){
f(3);
}
编译并运行:
$ gcc nested.c -o nested
$ ./nested
134520820
3
因此,虽然 C 会很乐意使用未绑定的变量(使用之前存储在那里的任何变量:在本例中为 134520820),但 Python(幸运的是)拒绝了。
作为一个有趣的旁注,静态嵌套范围可以实现什么亚历克斯·马尔泰利打来电话“Python 编译器所做的最重要的优化是:函数的局部变量不保存在字典中,它们位于值的紧密向量中,并且每个局部变量访问都使用该向量中的索引,而不是名称查找。”