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

Posted

技术标签:

【中文标题】在函数中创建类并访问在包含函数范围内定义的函数【英文标题】:Creating a class within a function and access a function defined in the containing function's scope 【发布时间】:2011-05-16 20:24:21 【问题描述】:

编辑

在这个问题的底部查看我的完整答案。

tl;dr answer: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()p>

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

认识到范围很重要 由文本确定:全球 a 中定义的函数的范围 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时,它会动态向外搜索,在f的范围内找到i,该范围已通过i = x绑定到3任务。

但是更改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(谢天谢地)拒绝了。

作为一个有趣的旁注,静态嵌套范围实现了 Alex Martelli has called“Python 编译器所做的最重要的优化:函数的局部变量不保存在 dict 中,它们位于紧密的值向量中,并且每个局部变量访问都使用该向量中的索引,而不是名称查找。”

【问题讨论】:

【参考方案1】:

这是 Python 名称解析规则的产物:您只能访问全局和局部范围,但不能访问中间范围,例如不要在你的直接外部范围内。

编辑:以上措辞不佳,您确实可以访问外部范围中定义的变量,但是通过从非-global 命名空间,你实际上是用你在本地定义的那个来掩盖外部变量。

在示例 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

【讨论】:

PEP 3104 仅适用于 Python 3。 @Chris,是的,nonlocal 在 Python 2 中不存在,所以提问者不能使用它。这实际上是我的观点;) 根据docs.python.org/tutorial/…:在执行过程中的任何时候,至少有三个嵌套作用域的命名空间是可以直接访问的: • 最里面的作用域,首先被搜索,包含本地名称 • 作用域从最近的封闭范围开始搜索的任何封闭函数中,包含非本地名称和非全局名称 • 倒数第二个范围包含当前模块的全局名称 • 最外层范围(最后搜索)是包含内置名称的命名空间 @Gabriel,示例 3 在 2.6.5 和 3.1.2 下引发 NameError @Gabriel,在本教程中,the scopes of any enclosing functions 可能指的是这些函数定义的范围,而不是函数本身。【参考方案2】:

这篇文章已经有几年的历史了,但它是少数讨论 Python 中作用域和静态绑定的重要问题的文章之一。但是,对于作者的一个重要误解,例如 3,可能会使读者感到困惑。 (不要想当然地认为其他的都是正确的,只是我只是详细查看了示例3提出的问题)。 让我澄清一下发生了什么。

在示例 3 中

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

>>> myfunc().x

必须返回一个错误,这与帖子作者所说的不同。我相信他错过了这个错误,因为在示例 1 中,x 在全局范围内被分配给了3。因此对发生的事情有错误的理解。

这篇文章中详细描述了解释 How references to variables are resolved in Python

【讨论】:

感谢您指出这一点:我已更新说明以修复您发现的错误

以上是关于在函数中创建类并访问在包含函数范围内定义的函数的主要内容,如果未能解决你的问题,请参考以下文章

无法从另一个函数(在全局范围内定义)访问变量(数组)

Javascript命名空间 - 如何根据命名导出函数范围内定义的函数和变量?

在类的构造函数上使用受保护的访问修饰符

extjs 渲染器范围

作用域变量提升函数提升数据类型

JavaScript作用域