Python 地图与列表;为什么会有不同的行为?

Python 地图与列表;为什么会有不同的行为?,python,python-3.x,functional-programming,Python,Python 3.x,Functional Programming,在为Bayes'Nets程序实现“变量消除”算法的过程中,我遇到了一个意外的错误,它是对一系列对象进行迭代映射变换的结果 为了简单起见,我将在这里使用一段类似的代码: >>> nums = [1, 2, 3] >>> for x in [4, 5, 6]: ... # Uses n if x is odd, uses (n + 10) if x is even ... nums = map( ... lambda n: n if

在为Bayes'Nets程序实现“变量消除”算法的过程中,我遇到了一个意外的错误,它是对一系列对象进行迭代映射变换的结果

为了简单起见,我将在这里使用一段类似的代码:

>>> nums = [1, 2, 3]
>>> for x in [4, 5, 6]:
...     # Uses n if x is odd, uses (n + 10) if x is even
...     nums = map(
...         lambda n: n if x % 2 else n + 10, 
...         nums)
...
>>> list(nums)
[31, 32, 33]
这绝对是错误的结果。由于[4,5,6]包含两个偶数,
10
应最多向每个元素添加两次。我在VE算法中也遇到了意想不到的行为,因此我修改了它,在每次迭代后将
map
迭代器转换为
list

>>> nums = [1, 2, 3]
>>> for x in [4, 5, 6]:
...     # Uses n if x is odd, uses (n + 10) if x is even
...     nums = map(
...         lambda n: n if x % 2 else n + 10,
...         nums)
...     nums = list(nums)
...
>>> list(nums)
[21, 22, 23]
根据我对iterables的理解,这个修改不应该改变任何东西,但它确实改变了。显然,在
列表
-ed版本中,对
非x%2
案例的
n+10
转换应用次数减少了一次


我的Bayes Nets程序在发现这个bug后也工作得很好,但我正在寻找它发生的原因的解释。

答案非常简单:在Python 3中是一个函数,它返回一个iterable对象(在Python 2中它返回一个
列表
)。让我为您的示例添加一些输出:

In [6]: nums = [1, 2, 3]

In [7]: for x in [4, 5, 6]:
   ...:     nums = map(lambda n: n if x % 2 else n + 10, nums)
   ...:     print(x)
   ...:     print(nums)
   ...:     
4
<map object at 0x7ff5e5da6320>
5
<map object at 0x7ff5e5da63c8>
6
<map object at 0x7ff5e5da6400>

In [8]: print(x)
6

In [9]: list(nums)
Out[9]: [31, 32, 33]
因为
map
是惰性的,所以它在调用
list
时进行计算。但是,
x
的值是
6
,这就是为什么它会产生令人困惑的输出。在循环中计算
nums
会产生预期的输出


如果要使用惰性版本,则需要在每个循环中修复
x
。 确实如此:

from functools import partial

def myfilter(n, x):
    return n if x % 2 else n + 10

nums = [1, 2, 3]
for x in [4, 5, 6]:
    f = partial(myfilter, x=x)
    nums = map(f, nums)

>>> list(nums)
[21, 22, 23]

问题与正在创建的lambda函数如何访问
x
变量有关。按照Python作用域的工作方式,lambda函数在被调用时将始终从外部作用域使用最新版本的
x
,而不是定义它们时的值

由于
map
是惰性的,lambda函数直到循环结束后才被调用(当您通过将嵌套的
map
s传递给
list
来使用它们时),因此,它们都使用最后的
x

要使每个lambda函数在定义时保存值
x
,请如下添加
x=x

lambda n, x=x: n if x % 2 else n + 10

这将指定参数及其默认值。默认值将在定义lambda时计算,因此当稍后调用lambda时(没有第二个参数),表达式中的
x
将是保存的默认值。

请不要这样编写代码。它让我的大脑受伤。你的代码没有做你解释它应该做的。如果您希望它在奇数时保留数字,在偶数时添加10,则必须将
nums=map(lambda n:n如果x%2!=0,则为n:n,否则为n+10,nums)
。。。。if函数需要计算某些内容,否则它总是计算为true。顺便说一句,代码的问题不是新行,正如你的编辑建议的那样。我想,kevin抱怨的是左边的点和箭头。这可能是因为在Python3中,
map
是一个迭代器吗?哦,既然你在列表[4,5,6]上迭代,那么函数每次迭代都会覆盖NUM。这意味着您的输出仅与列表中的最后一个元素(6)相关。是的,lambda在
list(nums)
行运行,所有3个元素都看到
x
,然后绑定到6,从而添加10。您可以通过在
列表(NUM)
之前添加
del x
来验证这一点;你会得到一个
名称错误
。啊!这是
x
!非常感谢。我知道迭代器通常是“懒惰”的,但我忽略了懒惰的这一部分。是否有任何方法可以设置lambda表达式,以便在循环的每次迭代期间立即计算变量?(编辑:没关系,明白了!)这似乎是混合功能性编程和命令式编程的危险!“当x值更改为6时进行计算”。相反,它在对map对象调用
list
时进行计算,这发生在
x=6
@cosmicFluke时。除了
map
的懒惰之外,正如Blckknght和Mike的回答所指出的,这也是一种情况。@bereal感谢链接!很高兴知道有一个术语来形容它。我并不奇怪这是一篇“common gotchas”文章中的条目——很容易错过。这就是我后续问题(第一个答案)的答案。谢谢如果在循环的每次迭代中调用
map(f,nums)
上的
list
怎么会懒惰呢?还有一个默认参数hack:
lambda n,x=x:…
,因为默认参数是在函数(lambda)创建期间计算的。@Evert感谢您的提示
list()
。远离的。
from functools import partial

def myfilter(n, x):
    return n if x % 2 else n + 10

nums = [1, 2, 3]
for x in [4, 5, 6]:
    f = partial(myfilter, x=x)
    nums = map(f, nums)

>>> list(nums)
[21, 22, 23]
lambda n, x=x: n if x % 2 else n + 10