为什么在 Python 3 中 map 返回一个 map 对象而不是列表?
- 2025-03-20 08:46:00
- admin 原创
- 18
问题描述:
我有兴趣了解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>
我理解“如何”,但我找不到“为什么”的参考。为什么语言设计者会做出这样的选择,在我看来,这会带来很大的痛苦。这是为了让开发人员坚持使用列表推导式吗?
在我看来,列表可以自然地被认为是函子;而且我不知何故一直被认为是这样想的:
fmap :: (a -> b) -> f a -> f b
解决方案 1:
我认为当生成器表达式也存在时 map仍然存在的原因是它可以接受多个迭代器参数,这些参数都循环并传递给函数:
>>> 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]))
否则,它根本不会在生成器表达式上添加任何内容。
解决方案 2:
因为它返回一个迭代器,所以它省去了在内存中存储完整大小列表的过程。这样以后你就可以轻松地对其进行迭代,而不会占用太多内存。甚至可能你不需要完整列表,只需要列表的一部分,直到达到你的条件。
您会发现这个文档很有用,迭代器非常棒。
表示数据流的对象。重复调用迭代器的
__next__()
方法(或将其传递给内置函数next()
)将返回流中的连续项目。当没有更多数据可用时,StopIteration
将引发异常。此时,迭代器对象已耗尽,对其__next__()
方法的任何进一步调用都只会StopIteration
再次引发异常。迭代器必须有一个__iter__()
返回迭代器对象本身的方法,因此每个迭代器也是可迭代的,并可以在接受其他可迭代对象的大多数地方使用。一个值得注意的例外是尝试多次迭代的代码。每次将容器对象(例如list
)传递给函数或在 for 循环中使用它时,都会生成一个全新的迭代器iter()
。使用迭代器尝试此操作只会返回上一次迭代过程中使用的相同耗尽的迭代器对象,使其看起来像一个空容器。
解决方案 3:
Guido在这里回答了这个问题:“因为创建列表只是浪费”。
他还说,正确的转换是使用常规for
循环。
从 2转换map()
为 3 可能不仅仅是坚持list( )
下去那么简单。Guido 还说:
如果输入序列的长度不相等,
map()
将在最短序列的终止处停止。为了与map()
Python 2.x 完全兼容,还可以将序列包装在 中itertools.zip_longest()
,例如map(func, *sequences)
变成
list(map(func, itertools.zip_longest(*sequences)))
解决方案 4:
在 Python 3 中,许多函数(不仅仅是map
,zip
还有range
其他函数)返回迭代器而不是完整列表。您可能需要一个迭代器(例如,为了避免将整个列表保存在内存中),或者您可能需要一个列表(例如,为了能够索引)。
然而,我认为 Python 3 中发生变化的主要原因是,虽然将迭代器转换为列表很简单,但使用list(some_iterator)
反向等效方法iter(some_list)
却无法实现预期的结果,因为完整列表已经构建并保存在内存中。
例如,在 Python 3 中,这样做很好,因为构建对象并将其转换为列表的list(range(n))
成本很低。然而,在 Python 2 中,这样做不会节省任何内存,因为在构建迭代器之前会构建完整的列表。range
`iter(range(n))`range()
因此,在 Python 2 中,需要单独的函数来创建迭代器而不是列表,例如imap
for map
(尽管它们并不完全等同)、xrange
for range
、izip
for zip
。相比之下,Python 3 只需要一个函数,因为list()
调用即可创建完整列表(如果需要)。