为什么map在Python 3中返回地图对象而不是列表?


82

我对了解Python 3.x新语言设计感兴趣。

我在Python 2.7中确实喜欢这个函数map

Python 2.7.12
In[2]: map(lambda x: x+1, [1,2,3])
Out[2]: [2, 3, 4]

但是,在Python 3.x中,情况发生了变化:

Python 3.5.1
In[2]: map(lambda x: x+1, [1,2,3])
Out[2]: <map at 0x4218390>

我了解操作方法,但是找不到原因。语言设计师为什么要做出这种选择,我认为这会带来很多痛苦。这是否会困扰开发人员坚持列出理解?

IMO,列表自然可以认为是函子;有人以某种方式想到了我:

fmap :: (a -> b) -> f a -> f b

2
基本原理应与我们为什么使用生成器而不是列表推导的原因相同。通过使用惰性评估,我们不需要在内存中保留大量内容。在此处检查已接受的答案:stackoverflow.com/questions/1303347/…–
Moberg

8
您能解释一下为什么这会给您带来“极大的痛苦”吗?
RemcoGerlich '16

3
我认为这是因为多年的使用表明,最常见的用法map只是对结果进行迭代。在不需要时建立清单效率低下,因此开发人员决定map懒惰。这里有很多可以提高性能的东西,而不会丢失很多(如果您需要一个列表,只需索取一个... list(map(...)))。
mgilson

3
好的,我发现有趣的是,与其保留Functor模式并提供List的惰性版本,他们不知何故决定在映射列表时强制对列表进行惰性评估。我本来希望有权做出自己的选择,也就是Generator->
map-

4
@NoIdeaHowToFixThis,实际上取决于您,如果您需要整个列表,只需将其转换为列表,就像地狱一样容易
Netwave

Answers:


37

我想,为什么地图仍然存在的原因发电机表情也是存在的,是它可以采取多个迭代论点,都环绕在并传递给函数:

>>> list(map(min, [1,2,3,4], [0,10,0,10]))
[0,2,0,4]

这比使用zip稍微容易一些:

>>> list(min(x, y) for x, y in zip([1,2,3,4], [0,10,0,10]))

否则,它根本不会在生成器表达式上添加任何内容。


1
我认为,如果我们希望强调列表理解力更Python化,并且语言设计师想强调这一点,那么我认为这是最现场的答案。@vishes_shell在某种程度上没有足够集中在语言设计上。
NoIdeaHowToFixThis16年

2
如果两个列表的长度不相等,则在Python 2和3中产生不同的结果。c = list(map(max, [1,2,3,4], [0,10,0,10, 99]))在python 2和python 3中进行尝试
。– cdarke

1
以下是从python3完全删除地图的原始计划的参考:artima.com/weblogs/viewpost.jsp?thread=98196
Bernhard,

嗯,当我在列表中包装地图时,我得到了一个包含1个元素列表的列表。
awiebe

24

由于它返回一个迭代器,因此省略了将完整大小列表存储在内存中的操作。这样您以后就可以轻松地遍历它,而不会给内存造成任何痛苦。在满足您的条件之前,您甚至可能不需要完整的列表,但只需要一部分即可。

您会发现此文档很有用,迭代器很棒。

表示数据流的对象。重复调用迭代器的__next__()方法(或将其传递给内置函数next())将返回流中的后续项。如果没有更多可用数据,则会StopIteration引发异常。此时,迭代器对象已用尽,对它的__next__()方法的任何进一步调用都将StopIteration再次引发。迭代器必须具有一种__iter__()返回迭代器对象本身的方法,因此每个迭代器也是可迭代的,并且可以在接受其他可迭代的大多数地方使用。一个值得注意的例外是尝试多次迭代遍历的代码。list每次将容器对象传递给时,容器对象(例如)都会产生一个新的迭代器。iter()函数或在for循环中使用它。使用迭代器尝试执行此操作将仅返回上一次迭代过程中使用的相同的耗尽迭代器对象,使其看起来像一个空容器。


14

Guido在这里回答这个问题:“因为创建列表只是浪费”。

他还说正确的转换是使用规则for循环。

map()从2转换为3可能不仅仅是在其list( )周围贴上a的简单情况。圭多还说:

“如果输入序列的长度不相等,map()则将在最短的序列的终止处停止。要与map()Python 2.x完全兼容,请将序列也包装在中itertools.zip_longest(),例如

map(func, *sequences)

变成

list(map(func, itertools.zip_longest(*sequences)))


3
Guido注释是针对map()函数的副作用而调用的,而不是用作函子的。
abukaj

4
的转换zip_longest是错误的。您必须使用itertools.starmap它来等效:list(starmap(func, zip_longest(*sequences)))。那是因为zip_longest产生元组,所以func会收到一个n-uple参数,而不是n调用时的唯一参数map(func, *sequences)
巴库里

12

在Python 3的许多功能(而不仅仅是map但是ziprange和其他人)返回一个迭代器,而不是完整的列表。您可能想要一个迭代器(例如,避免将整个列表保存在内存中),或者您想要一个列表(例如,能够建立索引)。

但是,我认为更改Python 3的主要原因是,使用list(some_iterator)反向等效项将迭代器转换为列表很简单,但iter(some_list)由于已建立完整列表并将其保存在内存中,因此无法达到预期的结果。

例如,在Python 3中list(range(n))效果很好,因为构建range对象并将其转换为列表的花费很少。但是,在Python 2iter(range(n))中不会保存任何内存,因为完整列表是在构建range()迭代器之前构建的。

因此,在Python 2中,需要单独的函数来创建迭代器而不是列表,例如imapfor ,for ,for map(尽管它们并不太等效)。相比之下,Python 3只需要一个函数即可,如果需要,调用将创建完整列表。xrangerangeizipziplist()


Python 2.7中的AFAIK也可以通过itertools返回迭代器来实现。另外,我不会将迭代器视为延迟列表,因为列表可以多次迭代并可以随机访问。
abukaj '16

@abukaj好的,谢谢,我已编辑我的答案以使其更加清晰
Chris_Rands 16-10-13

@IgorRivin是什么意思?Python 3map对象确实有一个next()方法。range我知道Python 3范围对象不是严格的迭代器
Chris_Rands

我的Anaconda发行版python 3.6.2中的@Chris_Rands foo = map(lambda x: x, [1, 2, 3])返回一个map对象foo。这样做foo.next()会返回错误:'map' object has no attribute 'next'
Igor Rivin

1
@IgorRivin:以和开头的方法__保留给Python;如果没有这种保留,您将难以区分next只是方法(不是真正的迭代器)的事物和是迭代器的事物。在实践中,您应该跳过这些方法,而只使用next()函数(例如next(foo)),该函数可以在2.6及更高版本的每个Python版本上正常工作。len(foo)即使foo.__len__()可以正常使用,也使用相同的方式;dunder方法通常不应直接调用,而应作为其他操作的一部分隐式调用。
ShadowRanger
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.