在函数内创建一个类,并访问定义在包含函数作用域中的函数。
在函数内创建一个类,并访问定义在包含函数作用域中的函数。
我本以为我对Python的作用域规则有很好的掌握,但这个问题让我彻底困惑了,而且我的谷歌搜索也失败了(我并不奇怪 - 看看问题的标题就知道了 😉
我将从几个按预期工作的示例开始,但请随时跳到第4个示例。
示例1:
直接了当:在类定义期间,我们能够访问在外部(在这种情况下是全局)作用域中定义的变量。
示例2:
再次(暂时忽略为什么要这么做的原因),这里没有什么意外的:我们可以访问外部作用域中的函数。
注意:如下面的Frédéric所指出的,这个函数似乎不起作用。请查看示例5(以及后面的内容)。
示例3:
这本质上与示例1相同:我们从类定义内部访问外部作用域,只是这次该作用域不是全局的,而是由myfunc()定义的。
编辑5:正如下面的@user3022222所指出的,我在原始帖子中搞砸了这个示例。我认为这个失败是因为只有函数(而不是其他代码块,比如这个类定义)才能访问封闭作用域中的变量。对于非函数代码块,只有局部、全局和内置变量是可访问的。有关更详细的解释,请参见此问题
还有一个:
示例4:
嗯...请原谅我?这有什么不同于示例2?
我完全被困惑了。请帮我理清思路。
谢谢!
P.S.偶然的机会是,这不仅仅是我理解上的问题,我已经在Python 2.5.2和Python 2.6.2上尝试过了。不幸的是,这些是我目前可以访问的所有内容,但它们都表现出相同的行为。
编辑:
根据http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces的说法:在执行过程中,至少有三个直接可访问命名空间的嵌套作用域:
最内层的作用域首先被搜索,包含本地名称。
任何封闭函数的作用域,从最近的封闭作用域开始搜索,包含非本地但也非全局的名称。
倒数第二个作用域包含当前模块的全局名称。
最外层的作用域(最后搜索)是包含内置名称的命名空间。
#4似乎是对这些作用域的第二个的反例。
编辑2
示例5。
编辑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()
所以似乎局部变量在函数创建时就被定义了(这是成功的),导致局部名称“保留”,从而在调用函数时遮蔽了外部作用域的名称。
有趣。
感谢Frédéric的答案!
供参考,来自python文档:
重要的是要意识到作用域是文本确定的:在模块中定义的函数的全局作用域是该模块的命名空间,不管该函数从何处或通过什么别名调用。另一方面,实际的名称查找是在运行时动态进行的 - 然而,语言定义正在向着在“编译”时进行静态名称解析的目标发展,因此不要依赖动态名称解析!(事实上,局部变量已经在静态确定)。
编辑4
真正的答案
这种看似令人困惑的行为是由Python的静态嵌套作用域所引起的,这在PEP 227中定义。它实际上与PEP 3104无关。
来自PEP 227:
名称解析规则对于静态作用域的语言来说是典型的[...] [除外]变量没有声明。如果在函数的任何地方发生名称绑定操作,那么该名称将被视为函数的局部变量,所有引用都指向局部绑定。如果在名称绑定之前发生引用,将引发NameError。
[...] Tim Peters的一个示例展示了在没有声明的情况下嵌套作用域的潜在问题:
i = 6
def f(x):
def g():
print i
# ...
# 跳到下一页
# ...
for i in x: # 啊,i是f的局部变量,所以这就是g看到的
pass
g()
调用g()将引用f()中由for循环绑定的变量i。如果在执行循环之前调用g(),将引发NameError。
让我们运行两个更简单版本的Tim的示例:
i = 6
def f(x):
def g():
print i
# ...
# 后来
# ...
i = x
g()
当g()在其内部作用域中找不到i时,它会动态地向外搜索,找到f()作用域中的i,该i通过i = x赋值绑定到3。
但是,改变f中最后两个语句的顺序会导致错误:
i = 6
def f(x):
def g():
print i
# ...
# 后来
# ...
g()
i = x # 注意:我调换了位置
f(3)
记住PEP 227说的“名称解析规则对于静态作用域的语言来说是典型的”,让我们看看(半)等效的C版本:
// nested.c
#include
int i = 6;
void f(int x){
int i; // <--- 在上面的python代码中是隐式的
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(幸运地)拒绝了。
作为一个有趣的副笔记,静态嵌套作用域使得Alex Martelli称之为“Python编译器执行的最重要的优化:函数的局部变量不会被保存在字典中,它们是在一个紧密的值向量中,每个局部变量访问都使用该向量中的索引,而不是名称查找。”