Python列表理解与lambdas

时间:2015-02-01 21:59:16

标签: python lambda

我正在运行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表达式的列表,重复了?

4 个答案:

答案 0 :(得分:22)

问题是classic "gotcha",是 lambda函数中引用的inot 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()