Python中的范围和变量定义

时间:2013-09-13 17:31:09

标签: python scope interpreted-language

我正在阅读一本维基书Python_Programming,我对下面的代码有点困惑:

def foo():
    def bar():
        x=5
        return x+y
    y=10
    return bar()
foo()

好吧,我注意到y是在bar()之外定义的,它在x+y中使用,它是在y之前定义的。我认为类似的代码会导致C中的编译错误,我们必须编写如下内容:

def foo():
    def bar(y):
        x=5
        return x+y
    y=10
    return bar(y)
foo()

如果没有将y定义为bar()中的形式参数,我认为编译器没有问题。 但是我觉得它在Python中完全没问题,这是一种解释型语言,对吗?

与编译语言相比,这在解释语言方面有所不同吗?在解释顶部的代码时,Python使用的实际过程是什么?


编辑1: 我认为下面的答案已经明确表达了这一点,它是关于自由变量和闭包的。以下是一些我认为可以帮助解决这个问题的链接:

SO:python-free-variables-why-does-this-fail
SO:closures-in-python

1 个答案:

答案 0 :(得分:2)

你正在看closure; Python编译器将y中的bar标记为自由变量,因为y未分配给。{/ p>

y foo中的本地,并且因为有一个嵌套作用域使用该名称作为自由变量,y被标记为闭包

当代码运行时,Python在创建y函数时立即为bar()创建一个闭包(每次调用foo()时都会重新创建;只有函数的字节码保持不变,一个附加到foo功能代码的常量。)

只有当bar() 被调用时,Python才需要查找y,这意味着取消引用该闭包,并从那里取消引用y的当前值

您可以通过字节码的内省和反汇编来看到所有这些:

>>> import dis
>>> def foo():
...     def bar(): return y
...     y = 10
...     return bar
... 
>>> foo()
<function foo.<locals>.bar at 0x10d53ce60>
>>> foo()()
10

这构建了一个带闭包的函数,并在不调用的情况下返回它。这允许我们内省bar()创建的foo()函数。

>>> dis.dis(foo)
  2           0 LOAD_CLOSURE             0 (y) 
              3 BUILD_TUPLE              1 
              6 LOAD_CONST               1 (<code object bar at 0x10d5138a0, file "<stdin>", line 2>) 
              9 LOAD_CONST               2 ('foo.<locals>.bar') 
             12 MAKE_CLOSURE             0 
             15 STORE_FAST               0 (bar) 

  3          18 LOAD_CONST               3 (10) 
             21 STORE_DEREF              0 (y) 

  4          24 LOAD_FAST                0 (bar) 
             27 RETURN_VALUE         

注意Python编译器如何在顶部插入LOAD_CLOSURE yMAKE_CLOSUREbar创建带附加闭包的函数对象。

>>> dis.dis(foo())
  2           0 LOAD_DEREF               0 (y) 
              3 RETURN_VALUE         

所有bar()所要做的就是取消引用附加的闭包。

>>> foo.__code__.co_consts
(None, <code object bar at 0x10d5138a0, file "<stdin>", line 2>, 'foo.<locals>.bar', 10)
>>> foo.__code__.co_cellvars
('y',)
>>> foo().__closure__
(<cell at 0x10d5e2c20: int object at 0x10d188940>,)
>>> foo().__closure__[0].cell_contents
10

结果指向y,正如预期的那样,查找内容会为您提供10

编译语言也可以这样做;有人设法将Scheme编译为C,并保留了闭包:Compiling Scheme to C,例如。