在函数内创建一个类,并访问定义在包含函数作用域中的函数。

20 浏览
0 Comments

在函数内创建一个类,并访问定义在包含函数作用域中的函数。

我本以为我对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编译器执行的最重要的优化:函数的局部变量不会被保存在字典中,它们是在一个紧密的值向量中,每个局部变量访问都使用该向量中的索引,而不是名称查找。”

0