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

发布时间:2023-03-07 / 作者:清心寡欲
本文介绍了在函数内创建类并访问在包含函数的作用域中定义的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑:

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

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

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

我以为我对 Python 的范围规则有很好的掌握,但是这个问题让我彻底陷入困境,而且我的 google-fu 让我失望了(并不是说我感到惊讶 - 看看问题标题;)

我将从几个按预期工作的示例开始,但请随意跳到示例 4 以了解多汁的部分.

示例 1.

>>>x = 3>>>类我的类(对象):... x = x...>>>我的课堂.x3

简单明了:在类定义期间,我们能够访问在外部(在本例中为全局)作用域中定义的变量.

示例 2.

>>>def mymethod(self):...返回 self.x...>>>x = 3>>>类我的类(对象):... x = x... 我的方法 = 我的方法...>>>MyClass().mymethod()3

再说一次(暂时忽略为什么人们可能想要这样做),这里没有什么出乎意料的:我们可以访问外部作用域中的函数.

注意:正如 Frédéric 在下面指出的那样,此功能似乎不起作用.请参阅示例 5(及其他).

示例 3.

>>>def myfunc():... x = 3...类MyClass(对象):... x = x...返回MyClass...>>>myfunc().x回溯(最近一次调用最后一次):文件",第 1 行,在 中文件",第 3 行,在 myfunc 中文件",第 4 行,在 MyClass 中NameError: 名称 'x' 未定义

这与示例 1 基本相同:我们从类定义内部访问外部作用域,只是这次该作用域不是全局的,感谢 myfunc().

编辑 5:正如 @user3022222 在下面指出的,我在我的原帖.我相信这会失败,因为只有函数(而不是其他代码块,如此类定义)可以访问封闭范围内的变量.对于非功能代码块,只能访问局部、全局和内置变量.这个问题

中有更详尽的解释>

还有一个:

示例 4.

>>>def my_defining_func():... def mymethod(self):...返回 self.y...类MyClass(对象):... 我的方法 = 我的方法... y = 3...返回MyClass...>>>my_defining_func()回溯(最近一次调用最后一次):文件",第 1 行,在 中文件",第 4 行,在 my_defining_func 中文件",第 5 行,在 MyClass 中NameError:未定义名称mymethod"

嗯……对不起?

这与示例 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():... 打印 x...返回乐趣2...>>>乐趣1()()3

编辑 3

正如@Frédéric 指出的那样,分配给与外部作用域中同名的变量似乎掩盖"了外部变量,从而阻止了赋值的运行.

因此示例 4 的修改版本有效:

def my_defining_func():def mymethod_outer(self):返回 self.y类我的类(对象):mymethod = mymethod_outery = 3返回我的课堂my_defining_func()

然而这不是:

def my_defining_func():def mymethod(self):返回 self.y类我的类(对象):mymethod_temp = 我的方法mymethod = mymethod_tempy = 3返回我的课堂my_defining_func()

我仍然不完全理解为什么会发生这种掩码:分配发生时不应该发生名称绑定吗?

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

>>>def my_defining_func():... x = 3...定义 my_inner_func():... x = x...返回 x...返回 my_inner_func...>>>my_defining_func()()回溯(最近一次调用最后一次):文件",第 1 行,在 中文件",第 4 行,在 my_inner_func 中UnboundLocalError:赋值前引用了局部变量x">>>my_defining_func()<函数 my_inner_func 在 0xb755e6f4>

所以看起来局部变量是在函数创建时定义的(成功),导致局部名称被保留",从而在调用函数时屏蔽了外部作用域名称.

有趣.

感谢 Frédéric 的回答!

参考,来自 python 文档:

认识到作用域很重要文本确定:全局定义在一个函数的作用域module 是那个模块的命名空间,没有来自何处或以什么别名函数被调用.另一方面,实际搜索名称已完成动态地,在运行时——然而,语言定义正在演变朝向静态名称解析,在编译"时间,所以不要依赖动态名称解析!(实际上,局部变量已经确定静态.)

编辑 4

真正的答案

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

来自 PEP 227:

名称解析规则是典型的对于静态范围的语言 [...][除外] 变量未声明.如果发生名称绑定操作函数中的任何位置,然后是该名称被视为函数的局部并且所有参考均指当地捆绑.如果引用发生在之前名称已绑定,NameError 是提升.

[...]

Tim Peters 的一个例子展示了没有声明的嵌套作用域:

i = 6定义 f(x):定义 g():打印我# ...# 跳到下一页# ...for i in x: # ah, i * is* f local to f, 所以这就是 g 看到的经过G()

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

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

>>>我 = 6>>>定义 f(x):... def g():...打印我... # ...... # 之后... # ...... i = x... G()...>>>f(3)3

g()在其内部作用域内没有找到i时,动态向外搜索,在中找到if 的作用域,已经通过i = x赋值绑定到3.

但是改变f中最后两个语句的顺序会导致错误:

>>>我 = 6>>>定义 f(x):... def g():...打印我... # ...... # 之后... # ...... G()... i = x # 注意:我交换了位置...>>>f(3)回溯(最近一次调用最后一次):文件",第 1 行,在 中文件",第 7 行,在 f 中文件",第 3 行,gNameError:在封闭范围内赋值之前引用了自由变量i"

记住 PEP 227 说名称解析规则对于静态范围语言是典型的",让我们看看(半)等效的 C 版本提供:

//nested.c#include 国际我= 6;void f(int x){国际我;//<--- 隐含在上面的python代码中无效 g(){printf("%d
",i);}G();我 = x;G();}int main(void){f(3);}

编译运行:

$ gcc nested.c -o 嵌套$ ./嵌套1345208203

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

作为一个有趣的旁注,静态嵌套的作用域可以实现 Alex Martelli 称Python 编译器所做的最重要的优化:函数的局部变量不保存在字典中,它们在值的紧密向量中,并且每个局部变量访问使用该向量中的索引,而不是名称查找."

解决方案

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

以上措辞不好,您确实可以访问在外部作用域中定义的变量,但是通过执行x = x或来自非全局命名空间的 mymethod = mymethod,您实际上是用您在本地定义的变量来屏蔽外部变量.

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

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

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

>>>def myfunc():... x = 3...类MyClass(对象):... x = x...返回MyClass...>>>myfunc().x回溯(最近一次调用最后一次):文件",第 1 行,在 中文件",第 3 行,在 myfunc 中文件",第 4 行,在 MyClass 中NameError: 名称 'x' 未定义

但以下方法可行:

>>>def myfunc():... x = 3...类MyClass(对象):... y = x...返回MyClass...>>>myfunc().y3

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.

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

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.

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

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

Example 3.

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         x = x
...     return MyClass
... 
>>> myfunc().x
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in myfunc
  File "", 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().

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

One more:

Example 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 "", line 1, in 
  File "", line 4, in my_defining_func
  File "", line 5, in MyClass
NameError: name 'mymethod' is not defined

Um...excuse me?

What makes this any different from example 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.

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:

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

Edit 2

Example 5.

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

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

So this modified version of Example 4 works:

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 "", line 1, in 
  File "", line 4, in my_inner_func
UnboundLocalError: local variable 'x' referenced before assignment
>>> my_defining_func()

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:

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

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.

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

[...]

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

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.

Lets run two simpler versions of Tim's example:

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

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

>>> 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 "", line 1, in 
  File "", line 7, in f
  File "", 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:

// nested.c
#include 

int i = 6;
void f(int x){
    int i;  // <--- implicit in the python code above
    void g(){
        printf("%d
",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.

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

解决方案

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.

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.

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.

See PEP 3104 for more details about nonlocal name resolution.

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:

>>> def myfunc():
...     x = 3
...     class MyClass(object):
...         x = x
...     return MyClass
... 
>>> myfunc().x
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in myfunc
  File "", 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

这篇关于在函数内创建类并访问在包含函数的作用域中定义的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持吉威生活!



[英文标题]Creating a class within a function and access a function defined in the containing function's scope


声明:本媒体部分图片、文章来源于网络,版权归原作者所有,如有侵权,请联系QQ:330946442删除。