当前位置: 首页 > 面试题库 >

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

周伟泽
2023-03-14
问题内容

编辑

请参阅此问题底部的我的完整答案。

tl; dr答 :Python具有静态嵌套的作用域。的 静态 方面可以与隐变量声明相互作用,产生非显而易见的结果。

(由于该语言通常具有动态特性,所以这尤其令人惊讶)。

我以为我对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指出的那样
,我在原始帖子中弄虚了这个示例。我相信这会失败,因为只有函数(其他代码块(如此类定义)无法访问封闭范围内的变量)。对于非功能代码块,只能访问局部变量,全局变量和内置变量。这个问题有更详尽的解释)

多一个:

范例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不同?

我完全迷住了。请把我整理一下。谢谢!

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

编辑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的答案!

供参考,来自python docs:

重要的是要认识到范围是由文本确定的:在模块中定义的函数的全局范围是该模块的名称空间,无论从何处调用该函数别名。另一方面,实际的名称搜索是在运行时动态完成的-
但是,语言定义正在“编译”时向静态名称解析发展,所以不要依赖动态名称解析!(实际上,局部变量已经是静态确定的。)

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

对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时,它将动态向外搜索,找到iinf的范围,该范围已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(谢天谢地)拒绝了。

作为一个有趣的旁注,静态嵌套范围实现了AlexMartelli所说的“Python编译器所做的最重要的单个优化:函数的局部变量不保存在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


 类似资料:
  • 我试过这个: 但这扩展了弦中的基因,这不是我想要的。

  • 问题内容: 此代码有效: 这两个测试用例基本相同。但是用这样的数组构造一个字符串很麻烦,而且您无法获得静态分析。所以我当时想做的是这样的: 其中孤立的是一个类似于以下内容的辅助函数: 最大的问题是Function.prototype.toString()给您整个功能。有谁知道从函数的字符串表示形式获取函数体的好方法? 更新:PRoberts在问这是什么目的,目的很简单: 问题答案: 我写了一个版本

  • 问题内容: 我想知道如何访问另一个函数中的一个函数。我看到了这样的代码: 那么,还有另一种方法来调用该 函数吗?我的第二个问题是,为什么在最后一行中我不打电话? 很好的解释深表感谢。 问题答案: 不,您不能直接调用它,因为它是的局部变量。 您需要使用,因为调用时返回了函数对象。要执行此功能对象,您需要 在这里您可以直接调用它,因为您可以访问它,因为它是由函数返回的。返回的对象实际上称为 闭包, 因

  • 问题内容: Python noob在这里。如何在“ fib”功能中掌握“内部”功能? 问题答案: 您不能,除非以某种方式返回,否则不能。 本质上是范围内的局部变量,您不能从外部访问函数的局部变量。(这甚至不会是有意义的,因为该功能正在运行时,除了不存在当地人想想看- 这将是有意义的访问的变量从功能之外吗?)

  • 问题内容: 我正在尝试在Go中的另一个函数中定义一个递归函数,但是我在努力获取正确的语法。我正在寻找这样的东西: 我想将Function2保留在Function1的范围内,因为它正在访问其范围的某些元素。 如何在Go中执行此操作? 非常感谢 问题答案: 如果它在声明它的行中,则无法访问它的内部。原因是您不是在指 函数, 而是指 变量 (类型是函数),并且只有在声明后才能访问它。 引用规格:声明和范

  • 问题内容: 我正在尝试查看是否存在通过外部javascript函数访问控制器内部范围的简单方法(与目标控制器完全无关) 我在其他几个问题上看到 会从DOM元素中检索范围,但是我的尝试目前未产生正确的结果。 这是jsfiddle:http : //jsfiddle.net/sXkjc/5/ 我目前正在经历从纯JS到Angular的过渡。我试图实现这一目标的主要原因是要保持原始库代码尽可能完整。无需我