何时检查是否存在非局部变量?

时间:2017-12-14 13:42:11

标签: python python-3.x scope

我正在学习Python,现在我正在研究范围和非本地语句。 在某些时候,我以为我想到了这一切,但随后非本地人来了,把一切都打倒了。

示例1:

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

a()

自然运行失败 更有趣的是print()没有被执行。为什么呢?

我的理解是,在执行def a()之前不会执行封闭print(),并且只有在调用def b()时才会执行嵌套a()。我很困惑......

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

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

a()

Aaand ......它运行良好。 Whaaat?这是怎么解决的?函数x = 10中的a永远不会被执行!

我的理解是在运行时评估和执行非本地语句,搜索封闭函数的调用上下文并将本地名称x绑定到某个特定的"外部" x。如果外部函数中没有x - 引发异常。再次,在运行时。

但现在看起来这是在语法分析时完成的,非常愚蠢的检查"查看x = blah的外部函数,如果有类似的东西 - 我们很好"即使x = blah从未执行过......

有人可以解释我何时以及如何处理非本地声明?

2 个答案:

答案 0 :(得分:4)

您可以在b的范围内查看a的范围对自由变量(可用于绑定)的了解,如下所示:

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可用的自由变量仅为{ {1}}。

让我们看看它生成的字节码指令,将()的定义放入IPython然后使用a

dis.dis

那么让我们看一下how LOAD_CLOSURE is processed in ceval.c

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

所以我们看到它必须从封闭范围的TARGET(LOAD_CLOSURE) { PyObject *cell = freevars[oparg]; Py_INCREF(cell); PUSH(cell); DISPATCH(); } 中查找x

提到in the Execution Model documentation,其中说:

  

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

答案 1 :(得分:3)

首先,要了解python将检查模块的语法,如果它检测到无效的东西,它会引发SyntaxError,使其停止运行。你的第一个例子提出了一个SyntaxError,但要明白为什么这很复杂,虽然如果你知道__slots__如何工作会更容易理解,所以我会先快速介绍一下。

当一个类定义__slots__时,它基本上是说实例应该只有那些属性,所以每个对象都只为那些对象分配了空间,尝试分配其他属性会引发错误

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属性。

如果未指定__slots__,则所有实例都使用字典创建以存储实例变量,字典对其包含的值的数量没有任何限制

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函数的工作方式与slots类似。当python检查模块的语法时,它会在每个函数定义中找到分配(或尝试分配)的所有变量,并在执行期间构造帧时使用它。

当你使用nonlocal x时,它给内部函数访问外部函数范围中的特定变量,但如果外部函数中没有定义变量那么 {{ 1}}没有空间指向。

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

的全局引用,也允许global x