当我运行此代码时,我得到了这个结果:
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)
答案 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有一个语法限制,不允许在读/写中捕获变量。
原因是如果在函数中分配变量,则只有两种可能性:
global x
更具体地说,它排除了该变量是一个封闭函数范围的本地
这已经在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)