0

我正在阅读一本 wiki 书籍 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

4

1 回答 1

2

你正在看一个闭包;Python 编译器将其标记ybar自由变量,因为y未分配给。

y 一个本地 infoo并且因为有一个使用该名称作为自由变量的嵌套范围,所以y被标记为一个闭包。

当代码运行时,Pythony在创建函数时立即创建一个闭包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_CLOSUREfor的。为 .创建带有附加闭包的函数对象。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,并保留了闭包:例如,将Scheme 编译为 C。

于 2013-09-13T17:35:53.747 回答