我正在运行Python 3.4.2,而且我对代码的行为感到困惑。我试图创建一个可调用多项式函数列表,其程度越来越高:
bases = [lambda x: x**i for i in range(3)]
但出于某种原因它会这样做:
print([b(5) for b in bases])
# [25, 25, 25]
为什么bases
似乎是列表理解中最后一个lambda表达式的列表,重复了?
答案 0 :(得分:22)
问题是classic
"gotcha",是
lambda函数中引用的i
是not looked up until the
lambda function is called。那时,i
的值是它的最后一个值
在for-loop
结束时被绑定,即2
。
如果将i
绑定到lambda
函数定义中的默认值,则每个i
将成为局部变量,并且会对其默认值进行求值并绑定到函数当lambda 定义而不是被调用时。
因此,当调用lambda时,现在在本地范围中查找i
,并使用其默认值:
In [177]: bases = [lambda x, i=i: x**i for i in range(3)]
In [178]: print([b(5) for b in bases])
[1, 5, 25]
供参考:
答案 1 :(得分:4)
更''pythonic'的方法:
使用嵌套函数:
def polyGen(degree):
def degPolynom(n):
return n**degree
return degPolynom
polynoms = [polyGen(i) for i in range(5)]
[pol(5) for pol in polynoms]
输出:
>> [1,5,25,125,625]
答案 2 :(得分:3)
作为替代解决方案,您可以使用部分功能:
>>> bases = [(lambda i: lambda x: x**i)(i) for i in range(3)]
>>> print([b(5) for b in bases])
[1, 5, 25]
这种构造相对于@unutbu给出的经典解决方案的唯一优势就是这样,你不能通过用错误的参数调用你的函数来引入偷偷摸摸的错误:
>>> print([b(5, 8) for b in bases])
# ^^^
# oups
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
TypeError: <lambda>() takes 1 positional argument but 2 were given
正如亚当·斯密在评论中提出的那样,你可以使用functools.partial
来获得同样的好处,而不是使用“嵌套的lambda”:
>>> import functools
>>> bases = [functools.partial(lambda i,x: x**i,i) for i in range(3)]
>>> print([b(5) for b in bases])
[1, 5, 25]
>>> print([b(5, 8) for b in bases])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
TypeError: <lambda>() takes 2 positional arguments but 3 were given
答案 3 :(得分:1)
我认为这个问题的“为什么会发生这种情况”的问题尚未得到解答。
将函数中的非本地名称命名为常量的原因是这些非本地名称将与全局名称的行为匹配。也就是说,在调用函数时会观察到创建函数后对全局名称的更改。
例如
# global context
n = 1
def f():
return n
n = 2
assert f() == 2
# non-local context
def f():
n = 1
def g():
return n
n = 2
assert g() == 2
return g
assert f()() == 2
您可以在全局和非本地上下文中看到,如果更改了名称的值,则该更改将反映在引用该名称的函数的将来调用中。如果对全局和非本地人的处理方式不同,那将会令人困惑。因此,行为是一致的。如果您需要为新函数使名称的当前值保持不变,那么惯用的方法是将函数的创建委托给另一个函数。该函数在创建函数的作用域中创建(没有任何变化),因此名称的值不会改变。
例如
def create_constant_getter(constant):
def constant_getter():
return constant
return constant_getter
getters = [create_constant_getter(n) for n in range(5)]
constants = [f() for f in getters]
assert constants == [0, 1, 2, 3, 4]
最后,作为附录,函数可以修改非本地名称(如果名称被标记为这样),就像它们可以修改全局名称一样。例如
def f():
n = 0
def increment():
nonlocal n
n += 1
return n
return increment
g = f()
assert g() + 1 == g()