在函数内创建一个类并访问在包含函数范围内定义的函数

Gab*_*ant 62 python scope namespaces

编辑:

请参阅本问题底部的完整答案.

tl;博士回答:Python有静态嵌套的范围.的静态 方面可以与隐变量声明相互作用,产生非显而易见的结果.

(这可能特别令人惊讶,因为语言通常具有动态性质).

我认为我对Python的范围规则有一个很好的处理,但是这个问题让我彻底陷入困境,而且我的google-fu让我失望了(不是我很惊讶 - 看看问题标题;)

我将从一些可以按预期工作的示例开始,但是可以自由地跳到示例4的多汁部分.

例1.

>>> x = 3
>>> class MyClass(object):
...     x = x
... 
>>> MyClass.x
3
Run Code Online (Sandbox Code Playgroud)

足够简单:在类定义期间,我们能够访问外部(在本例中为全局)范围中定义的变量.

例2.

>>> def mymethod(self):
...     return self.x
... 
>>> x = 3
>>> class MyClass(object):
...     x = x
...     mymethod = mymethod
...
>>> MyClass().mymethod()
3
Run Code Online (Sandbox Code Playgroud)

再次(忽略了为什么人们可能想要这样做),这里没有任何意外:我们可以访问外部范围中的函数.

注意:正如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
Run Code Online (Sandbox Code Playgroud)

这与示例1基本相同:我们从类定义中访问外部作用域,只是这次作用域不是全局的,这要归功于myfunc().

编辑5:正如@ 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
Run Code Online (Sandbox Code Playgroud)

嗯...对不起?

是什么让这与示例2有什么不同?

我完全糊涂了.请把我解雇.谢谢!

PS很可能这不仅仅是我理解的问题,我在Python 2.5.2和Python 2.6.2上尝试过这个.不幸的是,这些都是我现在可以访问的,但它们都表现出相同的行为.

编辑 根据http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces:在执行期间的任何时候,至少有三个嵌套的作用域,其名称空间可以直接访问:

  • 最先搜索的最内层作用域包含本地名称
  • 从最近的封闭范围开始搜索的任何封闭函数的范围包含非本地名称,但也包括非全局名称
  • 倒数第二个范围包含当前模块的全局名称
  • 最外面的范围(最后搜索)是包含内置名称的命名空间

#4.似乎是其中第二个的反例.

编辑2

例5.

>>> def fun1():
...     x = 3
...     def fun2():
...         print x
...     return fun2
... 
>>> fun1()()
3
Run Code Online (Sandbox Code Playgroud)

编辑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()
Run Code Online (Sandbox Code Playgroud)

但是,这不是:

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()
Run Code Online (Sandbox Code Playgroud)

我仍然不完全理解为什么会出现这种掩码:当赋值发生时,不应该发生名称绑定吗?

此示例至少提供了一些提示(以及更有用的错误消息):

>>> 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>
Run Code Online (Sandbox Code Playgroud)

因此看起来局部变量是在函数创建时定义的(成功),导致本地名称为"reserved",因此在调用函数时屏蔽外部作用域名称.

有趣.

谢谢Frédéric的回答!

python文档参考:

重要的是要意识到范围是以文本方式确定的:模块中定义的函数的全局范围是模块的命名空间,无论从何处或通过调用函数的别名.另一方面,名称的实际搜索是在运行时动态完成的 - 但是,语言定义在"编译"时朝着静态名称解析发展,所以不要依赖于动态名称解析!(事实上​​,局部变量已经静态确定.)

编辑4

真正的答案

这种看似令人困惑的行为是由PEP 227中定义的Python 静态嵌套作用域引起的.它实际上与PEP 3104无关.

从PEP 227:

名称解析规则对于静态范围的语言是典型的[...] [除]未声明变量.如果名称绑定操作发生在函数中的任何位置,则该名称将被视为函数的本地名称,并且所有引用都引用本地绑定.如果在绑定名称之前发生引用,则引发NameError.

[...]

Tim Peters的一个例子展示了在没有声明的情况下嵌套作用域的潜在缺陷:

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()
Run Code Online (Sandbox Code Playgroud)

对g()的调用将引用for循环中f()中绑定的变量i.如果在执行循环之前调用g(),则会引发NameError.

让我们运行两个更简单的Tim的例子:

>>> i = 6
>>> def f(x):
...     def g():
...             print i
...     # ...
...     # later
...     # ...
...     i = x
...     g()
... 
>>> f(3)
3
Run Code Online (Sandbox Code Playgroud)

当在内部范围内g()找不到i时,它会向外动态搜索,找到已通过赋值绑定的iin f范围.3i = 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
Run Code Online (Sandbox Code Playgroud)

记住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);
}
Run Code Online (Sandbox Code Playgroud)

编译并运行:

$ gcc nested.c -o nested
$ ./nested 
134520820
3
Run Code Online (Sandbox Code Playgroud)

因此,虽然C将很乐意使用未绑定的变量(使用之前存储的任何内容:134520820,在这种情况下),但Python(谢天谢地)拒绝.

作为一个有趣的附注,静态嵌套的范围使Alex Martelli称之为 "Python编译器所做的最重要的优化:函数的局部变量不会保存在dict中,它们处于紧密的值向量中,并且每个局部变量访问使用该向量中的索引,而不是名称查找."

Fré*_*idi 20

这是Python的名称解析规则的工件:您只能访问全局和本地范围,但不能访问中间范围,例如不能访问您的直接外部范围.

编辑:以上是不好措辞,你访问外范围定义的变量,但这么做x = x还是mymethod = mymethod从非全局命名空间,你实际上掩盖与你本地定义一个外部变量.

在示例2中,您的直接外部范围是全局范围,因此MyClass可以看到mymethod,但在示例4中,您的直接外部范围是my_defining_func(),因此它不能,因为外部定义mymethod已经被其本地定义屏蔽.

有关非本地名称解析的更多详细信息,请参阅PEP 3104.

另请注意,由于上面解释的原因,我无法在Python 2.6.5或3.1.2下运行示例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
Run Code Online (Sandbox Code Playgroud)

但以下方法可行:

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         y = x
...     return MyClass
... 
>>> myfunc().y
3
Run Code Online (Sandbox Code Playgroud)


use*_*222 6

这篇文章已有几年历史了,但是在Python中讨论范围和静态绑定的重要问题是很少见的.然而,对于例子3的作者存在一个重要的误解,可能会使读者感到困惑.(不要认为其他的都是正确的,只是我只详细讨论了例3中提出的问题).让我澄清发生了什么.

在例3中

def myfunc():
    x = 3
    class MyClass(object):
        x = x
    return MyClass

>>> myfunc().x
Run Code Online (Sandbox Code Playgroud)

必须返回一个错误,不像帖子的作者所说的那样.我相信他错过了错误,因为示例1 x被分配到3全局范围.因而错误地理解了所发生的事情.

本文将详细介绍 如何在Python中解析对变量的引用