关闭python?

时间:2013-08-16 13:06:33

标签: python closures python-2.x

当我运行此代码时,我得到了这个结果:

15
15

我希望输出应该是

15
17

但事实并非如此。问题是:为什么?

def make_adder_and_setter(x):
    def setter(n):
        x = n

    return (lambda y: x + y, setter)

myadder, mysetter = make_adder_and_setter(5)
print myadder(10)
mysetter(7)
print myadder(10)

3 个答案:

答案 0 :(得分:13)

您在x功能中设置了本地变量setter()。函数中的名称赋值将其标记为本地,除非您特别告诉Python编译器。

在Python 3中,您可以使用x关键字将nonlocal明确标记为非本地:

def make_adder_and_setter(x):
    def setter(n):
        nonlocal x
        x = n

    return (lambda y: x + y, setter)

现在x被标记为自由变量,并在分配给周围范围时查找。

在Python 2中,你不能标记Python本地。您拥有的唯一选择是将x标记为global。你将不得不求助于改变生活在周围范围内的可变对象所包含的值的技巧。

上的属性 setter函数可以正常工作,例如; setter范围是make_adder_and_setter()范围的本地,该对象的属性对于有权访问setter的任何内容都是可见的:

def make_adder_and_setter(x):
    def setter(n):
        setter.x = n
    setter.x = x

    return (lambda y: setter.x + y, setter)

另一个技巧是使用可变容器,例如列表:

def make_adder_and_setter(x):
    x = [x]
    def setter(n):
        x[0] = n

    return (lambda y: x[0] + y, setter)

在这两种情况下,你再分配给本地名称;第一个示例使用setter对象上的属性分配,第二个示例更改x列表,而不是分配给x本身。

答案 1 :(得分:8)

Python 2.x有一个语法限制,不允许在读/写中捕获变量。

原因是如果在函数中分配变量,则只有两种可能性:

  1. 该变量是全局变量,并已使用global x
  2. 声明
  3. 变量是函数的本地
  4. 更具体地说,它排除了该变量是一个封闭函数范围的本地

    这已经在Python 3.x中被取代,并添加了nonlocal声明。您的代码将在Python 3中按预期工作,方法是将其更改为

    def make_adder_and_setter(x):
        def setter(n):
            nonlocal x
            x = n
    
        return (lambda y: x + y, setter)
    

    python 2.x运行时能够在字节码级别处理读写的闭包变量,但是限制在于编译器接受的语法。

    您可以看到一个直接生成python字节码的lisp编译器,它创建了一个具有读写捕获状态at the end of this video的加法器闭包。编译器可以为Python 2.x,Python 3.x或PyPy生成字节码。

    如果你需要Python 2.x中的封闭式可变状态,一个技巧就是使用一个列表:

    def make_adder_and_setter(x):
        x = [x]
        def setter(n):
            x[0] = n
    
        return (lambda y: x[0] + y, setter)
    

答案 2 :(得分:2)

您的内部def setter(n)函数定义了自己的局部变量x。这隐藏了另一个x变量,该变量是make_adder_and_setter的参数(在范围内打了一个洞)。因此setter函数没有副作用。它只是设置内部局部变量的值并退出。

如果你尝试下面的代码,也许你会很清楚。它完全相同,只使用名称z而不是x。

def make_adder_and_setter(x):
    def setter(n):
        z = n

    return (lambda y: x + y, setter)

myadder, mysetter = make_adder_and_setter(5)
print myadder(10)
mysetter(7)
print myadder(10)