4

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

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)
4

3 回答 3

13

您正在函数中设置局部变量。分配给函数中的名称会将其标记为本地名称,除非您特别告诉 Python 编译器。xsetter()

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

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

    return (lambda y: x + y, setter)

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

在 Python 2 中,您不能将 Python 本地标记为这样。您唯一的其他选择是标记xglobal. 您必须使用技巧来更改位于周围范围内的可变对象所包含的值。

例如,函数上的属性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自身。

于 2013-08-16T13:09:51.967 回答
8

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

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

  1. 该变量是一个全局变量,并且已被声明为global x
  2. 变量是函数的局部变量

更具体地说,排除了该变量是封闭函数范围的局部变量

这已在 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 运行时能够在字节码级别处理关闭变量的读写,但是限制在于编译器接受的语法。

您可以看到一个 lisp 编译器直接生成 python 字节码,该编译器在本视频结尾处创建了一个具有读写捕获状态的加法器闭包。编译器可以为 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)
于 2013-08-16T13:11:57.913 回答
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)
于 2013-08-16T13:12:29.187 回答