编辑:
  
  请参阅此问题底部的我的完整答案。
  
  tl; dr回答:Python具有静态嵌套的作用域。静态
  Aspect可以与隐式变量声明进行交互,从而产生非显而易见的结果。
  
  (由于该语言通常具有动态特性,所以这尤其令人惊讶)。


我以为我对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


再次(暂时忽略为什么要这样做),这里没有什么意外的:我们可以访问外部作用域中的函数。

注意:正如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


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

编辑5:作为@user3022222 pointed out below,我在原始帖子中弄了这个例子。我相信这会失败,因为只有函数(其他代码块(如此类定义)无法访问封闭范围内的变量)。对于非功能代码块,只能访问局部变量,全局变量和内置变量。 this question中提供了更详尽的解释

多一个:

范例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上进行了尝试。不幸的是,这些都是我目前可以使用的,但是它们都表现出相同的行为。

编辑
根据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


编辑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>


这样看来,局部变量是在函数创建时定义的(成功),导致局部名称被“保留”,从而在调用函数时掩盖了外部作用域的名称。

有趣。

感谢Frédéric的答案!

供参考,来自the python docs


  重要的是要意识到范围
  由文字决定:全球
  在一个函数中定义的功能范围
  module是该模块的名称空间,否
  从哪里或通过什么别名的问题
  函数被调用。另一方面,
  实际搜索姓名已完成
  在运行时动态地-但是,
  语言定义正在演变
  朝向静态名称解析
  “编译”时间,所以不要依赖
  动态名称解析! (事实上​​,
  局部变量已经确定
  静态地。)


编辑4

真正的答案

这种看似混乱的行为是由Python的statically nested scopes as defined in PEP 227引起的。实际上,它与PEP 3104无关。

从PEP 227:


  名称解析规则是典型的
  用于静态范围的语言[...]
  [except]没有声明变量。
  如果发生名称绑定操作
  函数中的任何位置,然后使用该名称
  被视为功能的局部
  所有参考文献均指当地
  捆绑。如果之前有参考
  名称是绑定的,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()

  
  对g()的调用将引用由for绑定到f()中的变量i
  环。如果在执行循环之前调用了g(),则NameError将
  被提高。


让我们运行Tim示例的两个简单版本:

>>> i = 6
>>> def f(x):
...     def g():
...             print i
...     # ...
...     # later
...     # ...
...     i = x
...     g()
...
>>> f(3)
3


g()在其内部范围内找不到i时,它将动态向外搜索,在i范围内找到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编译器进行的Alex Martelli has called最重要的单个优化:函数的局部变量不保存在dict中,它们在紧密的值向量中,每个局部变量访问使用该向量中的索引,而不是名称查找。”

最佳答案

这是Python名称解析规则的产物:您只能访问全局和局部作用域,而不能访问它们之间的作用域,例如不在您的直接外部范围内。

编辑:上面的措辞很差,您确实可以访问在外部作用域中定义的变量,但是通过从非全局名称空间执行x = xmymethod = mymethod,您实际上是在用您的变量掩盖外部变量重新定义本地。

在示例2中,您的直接外部作用域是全局作用域,因此MyClass可以看到mymethod,但是在示例4中,您的直接外部作用域是my_defining_func(),所以它不能,因为mymethod的外部定义已被其本地定义掩盖。

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

另请注意,由于上述原因,我无法让示例3在Python 2.6.5或3.1.2下运行:

>>> 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


但是以下方法会起作用:

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

关于python - 在函数内创建类并访问包含函数范围内定义的函数,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56105768/

10-10 21:51
查看更多