Python 使用 lambda 的列表理解
Python list comprehension with lambdas
我是 运行 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 表达式的列表,在列表推导中重复了?
一道题,经典
"gotcha", 是
lambda 函数中引用的 i
直到
调用了 lambda 函数。那个时候i
的值就是它的最后一个值
绑定到 for-loop
结束时,即 2
.
如果在 lambda
函数的定义中将 i
绑定到默认值,则每个 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]
供参考:
作为替代解决方案,您可以使用偏函数:
>>> 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
正如 Adam Smith 在下面的评论中所建议的,您可以使用 functools.partial
而不是使用 "nested 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
更'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]
我认为问题的 "why this happens" 方面尚未得到解答。
函数中的名称非本地名称不被视为常量的原因是这些非本地名称将与全局名称的行为相匹配。即创建函数后全局名称的变化在调用函数时观察到。
例如
# 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()
我是 运行 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 表达式的列表,在列表推导中重复了?
一道题,经典
"gotcha", 是
lambda 函数中引用的 i
直到
调用了 lambda 函数。那个时候i
的值就是它的最后一个值
绑定到 for-loop
结束时,即 2
.
如果在 lambda
函数的定义中将 i
绑定到默认值,则每个 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]
供参考:
作为替代解决方案,您可以使用偏函数:
>>> 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
正如 Adam Smith 在下面的评论中所建议的,您可以使用 functools.partial
而不是使用 "nested 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
更'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]
我认为问题的 "why this happens" 方面尚未得到解答。
函数中的名称非本地名称不被视为常量的原因是这些非本地名称将与全局名称的行为相匹配。即创建函数后全局名称的变化在调用函数时观察到。
例如
# 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()