在函数中创建类并访问在包含函数的作用域中定义的函数

时间:2022-10-27 07:43:55

Edit:

编辑:

See my full answer at the bottom of this question.

请在问题的最后看到我的完整答案。

tl;dr answer: Python has statically nested scopes. The static aspect can interact with the implicit variable declarations, yielding non-obvious results.

Python具有静态嵌套的作用域。静态方面可以与隐式变量声明交互,产生不明显的结果。

(This can be especially surprising because of the language's generally dynamic nature).

(这可能会特别令人惊讶,因为语言通常是动态的)。

I thought I had a pretty good handle on Python's scoping rules, but this problem has me thoroughly stymied, and my google-fu has failed me (not that I'm surprised - look at the question title ;)

我认为我对Python的范围规则有很好的理解,但是这个问题彻底地阻碍了我,我的google-fu让我失败了(不是我感到惊讶——看看问题标题;)

I'm going to start with a few examples that work as expected, but feel free to skip to example 4 for the juicy part.

我将从几个示例开始,这些示例如预期的那样有效,但是请跳过示例4,以了解更多的内容。

Example 1.

例1。

>>> x = 3
>>> class MyClass(object):
...     x = x
... 
>>> MyClass.x
3

Straightforward enough: during class definition we're able to access the variables defined in the outer (in this case global) scope.

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

Example 2.

例2。

>>> def mymethod(self):
...     return self.x
... 
>>> x = 3
>>> class MyClass(object):
...     x = x
...     mymethod = mymethod
...
>>> MyClass().mymethod()
3

Again (ignoring for the moment why one might want to do this), there's nothing unexpected here: we can access functions in the outer scope.

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

Note: as Frédéric pointed out below, this function doesn't seem to work. See Example 5 (and beyond) instead.

注意:正如弗雷德里克在下面指出的,这个函数似乎不起作用。请参见示例5(以及其他)。

Example 3.

例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

This is essentially the same as example 1: we're accessing the outer scope from within the class definition, just this time that scope isn't global, thanks to myfunc().

这与示例1本质上是相同的:我们正在从类定义中访问外部范围,但是这次由于myfunc(),该范围不是全局的。

Edit 5: As @user3022222 pointed out below, I botched this example in my original posting. I believe this fails because only functions (not other code blocks, like this class definition) can access variables in the enclosing scope. For non-function code blocks, only local, global and built-in variables are accessible. A more thorough explanation is available in this question

编辑5:正如@user3022222在下面指出的,我在我最初的帖子中搞砸了这个例子。我认为这是失败的,因为只有函数(而不是其他代码块,比如这个类定义)可以访问封闭范围中的变量。对于非函数代码块,只能访问本地、全局和内置变量。在这个问题上有一个更全面的解释

One more:

一个:

Example 4.

例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

Um...excuse me?

嗯…原谅我吗?

What makes this any different from example 2?

这与例2有什么不同?

I'm completely befuddled. Please sort me out. Thanks!

我完全糊涂。请我出去。谢谢!

P.S. on the off-chance that this isn't just a problem with my understanding, I've tried this on Python 2.5.2 and Python 2.6.2. Unfortunately those are all I have access to at the moment, but they both exhibit the same behaviour.

顺便说一下,我在Python 2.5.2和Python 2.6.2中已经尝试过了,因为我不太可能仅仅是理解上的问题。不幸的是,这些都是我目前所能接触到的,但它们都表现出相同的行为。

Edit According to http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces: at any time during execution, there are at least three nested scopes whose namespaces are directly accessible:

根据http://docs.python.org/tutorial/class.html #python-scope -and-namespaces编辑:在执行过程中,至少有三个嵌套的作用域,它们的名称空间可以直接访问:

  • the innermost scope, which is searched first, contains the local names
  • 最内部的作用域(首先搜索)包含本地名称
  • the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contains non-local, but also non-global names
  • 从最近的封闭范围开始搜索的任何封闭函数的作用域都包含非本地的,但也包含非全局名称。
  • the next-to-last scope contains the current module’s global names
  • 倒数第二个作用域包含当前模块的全局名称。
  • the outermost scope (searched last) is the namespace containing built-in names
  • 最外层的范围(最后搜索)是包含内置名称的名称空间

#4. seems to be a counter-example to the second of these.

# 4。这似乎是第二个例子的反例。

Edit 2

编辑2

Example 5.

例5。

>>> def fun1():
...     x = 3
...     def fun2():
...         print x
...     return fun2
... 
>>> fun1()()
3

Edit 3

编辑3

As @Frédéric pointed out the assignment of to a variable of the same name as it has in the outer scope seems to "mask" the outer variable, preventing the assignment from functioning.

正如@Frederic所指出的那样,将一个变量赋值给一个与它在外部作用域中具有相同名称的变量,似乎会“掩盖”外部变量,从而阻止赋值工作。

So this modified version of Example 4 works:

所以这个例子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()

However this doesn't:

然而这并不是:

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

I still don't fully understand why this masking occurs: shouldn't the name binding occur when the assignment happens?

我仍然不完全理解为什么会出现这种屏蔽:当分配发生时,名称绑定不应该发生吗?

This example at least provides some hint (and a more useful error message):

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

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

So it appears that the local variable is defined at function creation (which succeeds), resulting in the local name being "reserved" and thus masking the outer-scope name when the function is called.

因此,本地变量似乎是在函数创建(它成功)时定义的,从而导致本地名称被“保留”,从而在调用函数时屏蔽外部作用域名称。

Interesting.

有趣。

Thanks Frédéric for the answer(s)!

弗雷德里克,谢谢你的回答!

For reference, from the python docs:

参考python文档:

It is important to realize that scopes are determined textually: the global scope of a function defined in a module is that module’s namespace, no matter from where or by what alias the function is called. On the other hand, the actual search for names is done dynamically, at run time — however, the language definition is evolving towards static name resolution, at “compile” time, so don’t rely on dynamic name resolution! (In fact, local variables are already determined statically.)

重要的是要意识到作用域是由文本决定的:在模块中定义的函数的全局作用域是模块的名称空间,无论从何处或通过什么别名调用该函数。另一方面,对名称的实际搜索是在运行时动态完成的——然而,在“编译”时,语言定义朝着静态名称解析演变,所以不要依赖动态名称解析!(事实上,局部变量已经被静态地确定。)

Edit 4

编辑4

The Real Answer

This seemingly confusing behaviour is caused by Python's statically nested scopes as defined in PEP 227. It actually has nothing to do with PEP 3104.

这种看似混乱的行为是由Python在PEP 227中定义的静态嵌套作用域引起的。它实际上和PEP 3104没有任何关系。

From PEP 227:

从PEP 227:

The name resolution rules are typical for statically scoped languages [...] [except] variables are not declared. If a name binding operation occurs anywhere in a function, then that name is treated as local to the function and all references refer to the local binding. If a reference occurs before the name is bound, a NameError is raised.

名称解析规则是静态范围语言的典型规则[…[除了]变量没有声明。如果名称绑定操作发生在函数的任何位置,那么该名称将被视为函数的本地名称,所有引用都引用本地绑定。如果在绑定名称之前发生引用,则会引发NameError。

[...]

[…]

An example from Tim Peters demonstrates the potential pitfalls of nested scopes in the absence of declarations:

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

The call to g() will refer to the variable i bound in f() by the for loop. If g() is called before the loop is executed, a NameError will be raised.

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

Lets run two simpler versions of Tim's example:

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

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

when g() doesn't find i in its inner scope, it dynamically searches outwards, finding the i in f's scope, which has been bound to 3 through the i = x assignment.

当g()在其内部范围内找不到i时,它会动态地向外搜索,在f的范围内找到i,通过i = x赋值,i被绑定到3。

But changing the order the final two statements in f causes an error:

但是在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

Remembering that PEP 227 said "The name resolution rules are typical for statically scoped languages", lets look at the (semi-)equivalent C version offer:

记住,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);
}

compile and run:

编译并运行:

$ gcc nested.c -o nested
$ ./nested 
134520820
3

So while C will happily use an unbound variable (using whatever happens to have been stored there before: 134520820, in this case), Python (thankfully) refuses.

因此,虽然C会很高兴地使用一个未绑定的变量(使用之前存储在那里的任何东西:在本例中为134520820),但是Python(谢天谢地)拒绝了。

As an interesting side-note, statically nested scopes enable what Alex Martelli has called "the single most important optimization the Python compiler does: a function's local variables are not kept in a dict, they're in a tight vector of values, and each local variable access uses the index in that vector, not a name lookup."

作为一个有趣的附注,静态嵌套范围使马特利亚历克斯有所谓“唯一最重要的优化Python编译器:一个函数的局部变量并不保存在一个东西,他们在一个紧向量的值,和每个局部变量访问使用索引向量,而不是一个名字查找”。

2 个解决方案

#1


19  

That's an artifact of Python's name resolution rules: you only have access to the global and the local scopes, but not to the scopes in-between, e.g. not to your immediate outer scope.

这是Python的名称解析规则的产物:您只能访问全局和本地范围,而不能访问中间的范围,例如,不能访问您当前的外部范围。

EDIT: The above was poorly worded, you do have access to the variables defined in outer scopes, but by doing x = x or mymethod = mymethod from a non-global namespace, you're actually masking the outer variable with the one you're defining locally.

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

In example 2, your immediate outer scope is the global scope, so MyClass can see mymethod, but in example 4 your immediate outer scope is my_defining_func(), so it can't, because the outer definition of mymethod is already masked by its local definition.

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

See PEP 3104 for more details about nonlocal name resolution.

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

Also note that, for the reasons explained above, I can't get example 3 to run under either Python 2.6.5 or 3.1.2:

还要注意的是,由于上述原因,我无法得到在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

But the following would work:

但以下方法是有效的:

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

#2


5  

This post is a few years old, but it is among the rare ones to discuss the important problem of scope and static binding in Python. However, there is an important misunderstanding of the author for example 3 that might confuse readers. (do not take as granted that the other ones are all correct, it is just that I only looked at the issues raised by example 3 in details). Let me clarify what happened.

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

In example 3

在示例3

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

>>> myfunc().x

must return an error, unlike what the author of the post said. I believe that he missed the error because in example 1 x was assigned to 3 in the global scope. Thus a wrong understanding of what happened.

必须返回一个错误,不像文章的作者说的那样。我认为他漏掉了这个错误,因为在例1 x在全局作用域中被赋给了3。因此,对所发生的事情的理解是错误的。

The explanation is extensively described in this post How references to variables are resolved in Python

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

#1


19  

That's an artifact of Python's name resolution rules: you only have access to the global and the local scopes, but not to the scopes in-between, e.g. not to your immediate outer scope.

这是Python的名称解析规则的产物:您只能访问全局和本地范围,而不能访问中间的范围,例如,不能访问您当前的外部范围。

EDIT: The above was poorly worded, you do have access to the variables defined in outer scopes, but by doing x = x or mymethod = mymethod from a non-global namespace, you're actually masking the outer variable with the one you're defining locally.

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

In example 2, your immediate outer scope is the global scope, so MyClass can see mymethod, but in example 4 your immediate outer scope is my_defining_func(), so it can't, because the outer definition of mymethod is already masked by its local definition.

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

See PEP 3104 for more details about nonlocal name resolution.

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

Also note that, for the reasons explained above, I can't get example 3 to run under either Python 2.6.5 or 3.1.2:

还要注意的是,由于上述原因,我无法得到在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

But the following would work:

但以下方法是有效的:

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

#2


5  

This post is a few years old, but it is among the rare ones to discuss the important problem of scope and static binding in Python. However, there is an important misunderstanding of the author for example 3 that might confuse readers. (do not take as granted that the other ones are all correct, it is just that I only looked at the issues raised by example 3 in details). Let me clarify what happened.

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

In example 3

在示例3

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

>>> myfunc().x

must return an error, unlike what the author of the post said. I believe that he missed the error because in example 1 x was assigned to 3 in the global scope. Thus a wrong understanding of what happened.

必须返回一个错误,不像文章的作者说的那样。我认为他漏掉了这个错误,因为在例1 x在全局作用域中被赋给了3。因此,对所发生的事情的理解是错误的。

The explanation is extensively described in this post How references to variables are resolved in Python

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