Python 何时检查非局部变量的存在?

Python 何时检查非局部变量的存在?,python,python-3.x,scope,Python,Python 3.x,Scope,我正在学习Python,现在我的主题是作用域和非局部语句。 在某个时候,我以为我已经把一切都弄明白了,但后来非本地人来了,把一切都搞糟了 示例1: print( "let's begin" ) def a(): def b(): nonlocal x x = 20 b() a() 运行它自然会失败。 更有趣的是,打印()不会执行。为什么 我的理解是,在执行print()之前不会执行封闭def a(),而嵌套def b()仅在调用a()时执行。我

我正在学习Python,现在我的主题是作用域和非局部语句。 在某个时候,我以为我已经把一切都弄明白了,但后来非本地人来了,把一切都搞糟了

示例1:

print( "let's begin" )
def a():
    def b():
        nonlocal x
        x = 20
    b()

a()
运行它自然会失败。
更有趣的是,
打印(
)不会执行。为什么

我的理解是,在执行
print()
之前不会执行封闭
def a()
,而嵌套
def b()
仅在调用
a()
时执行。我很困惑

好的,让我们试试第二个例子:

print( "let's begin" )
def a():
    if False: x = 10
    def b():
        nonlocal x
        x = 20
    b()

a()
啊,还有。。。它运行良好。 什么?!这是怎么解决的<函数
a
中的code>x=10永远不会执行

我的理解是,非局部语句在运行时被计算和执行,搜索封闭函数的调用上下文,并将局部名称
x
绑定到某个特定的“外部”
x
。如果外部函数中没有
x
,则引发异常。同样,在运行时

但现在看起来这是在语法分析时完成的,使用了相当愚蠢的检查“查找外部函数以查找
x=blah
,如果有类似的情况-我们很好”,即使
x=blah
从未执行过


有人能解释一下非本地语句是何时以及如何处理的吗?

首先,要知道python会检查模块的语法,如果它检测到无效的东西,就会引发一个
语法错误,从而停止它的运行。您的第一个示例提出了一个
SyntaxError
,但要准确理解原因相当复杂,尽管如果您知道
\uuuuuuuu插槽\uuuuuuuuu
的工作原理,则更容易理解,因此我将首先快速介绍这一点


当一个类定义了
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
时,基本上是说实例应该只具有那些属性,因此每个对象都被分配了内存空间,只为那些属性分配空间

class SlotsTest:
    __slots__ = ["a", "b"]

x = SlotsTest()

x.a = 1 ; x.b = 2
x.c = 3 #AttributeError: 'SlotsTest' object has no attribute 'c'
x.c=3
无法工作的原因是没有内存空间来放置
.c
属性

如果未指定
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

class DictTest:
    pass

y = DictTest()
y.a = 1 ; y.b = 2 ; y.c = 3
print(y.__dict__) #prints {'a': 1, 'b': 2, 'c': 3}

Python函数的工作原理类似于
插槽
。当python检查模块的语法时,它会在每个函数定义中找到所有分配(或试图分配)的变量,并在执行期间构造帧时使用这些变量

使用
非局部x
时,它允许内部函数访问外部函数作用域中的特定变量,但是如果外部函数中没有定义变量,则
非局部x
没有指向的空间。


全局访问不会遇到同样的问题,因为python模块是使用字典来存储其属性的。因此,即使没有对
x

的全局引用,也允许使用
global x
,您可以从
a
范围中看到
b
范围对自由变量(可用于绑定)的了解,如下所示:

import inspect

print( "let's begin" )

def a():
    if False:
        x = 10

    def b():
        print(inspect.currentframe().f_code.co_freevars)
        nonlocal x
        x = 20

    b()

a()
其中:

let's begin
('x',)
如果注释掉
nonlocal
行,并删除
If
语句,其中包含
x
,您将看到
b
可用的自由变量只是
()

让我们看看这会生成什么字节码指令,将
a
的定义放入IPython,然后使用
dis.dis

In [3]: import dis

In [4]: dis.dis(a)
  5           0 LOAD_CLOSURE             0 (x)
              2 BUILD_TUPLE              1
              4 LOAD_CONST               1 (<code object b at 0x7efceaa256f0, file "<ipython-input-1-20ba94fb8214>", line 5>)
              6 LOAD_CONST               2 ('a.<locals>.b')
              8 MAKE_FUNCTION            8
             10 STORE_FAST               0 (b)

 10          12 LOAD_FAST                0 (b)
             14 CALL_FUNCTION            0
             16 POP_TOP
             18 LOAD_CONST               0 (None)
             20 RETURN_VALUE
因此,我们看到它必须从封闭范围的
freevars
中查找
x

提到这一点,其中说:

非局部语句使相应的名称引用最近封闭函数作用域中以前绑定的变量。如果给定名称不存在于任何封闭函数作用域中,则在编译时引发SyntaxError


您可能会发现,放置
非本地
需要变量实际指向一个现有变量,第一个变量无法编译。如果您不知道python已经编译,那么可能值得一读所有语言(汇编除外)都必须在某个时候编译,您不能给出并期望它做合理的事情!区别在于
a+b
而不是编译为“使用
a
b
运行特定函数”,将编译为类似“查找
+
a
的操作,如果未定义,则检查
b
上的反向
+
,如果未定义,则引发错误”我知道这是一个格式不好的答案,我不知道如何解释这一点,而不谈论
插槽
,或者python如何处理堆栈内存,这将更糟糕。它并不像您认为的那样格式不好-它让我对python有了一些了解。所以,基本上,Python脚本执行的第一个阶段是语法分析,当Python机器确定以后实际存在什么时?这不正与整个“动态”范式相反吗?。。那么通过exec()创建的变量呢?不管它是如何解释的,它都需要将代码转换为在某个时候可以执行的代码,按文件而不是按行执行是有意义的,因为如果您有一个长脚本,在它试着运行它之前,很高兴知道您忘记了最后一行的括号。python明确保证globals将像字典一样是动态的(请参见
help(globals)
note),并且不保证局部或闭包是动态的(请参见
help(locals)
note)。至于
exec
,它必须为其本地和全局命名空间使用字典,如果没有提供,它将使用当前的全局字典和一个用于局部变量的新字典,或者如果提供了一个字典,它将用于全局和局部变量。
TARGET(LOAD_CLOSURE) {
    PyObject *cell = freevars[oparg];
    Py_INCREF(cell);
    PUSH(cell);
    DISPATCH();
}