【问题标题】:Why does map return a map object instead of a list in Python 3?为什么 map 在 Python 3 中返回一个地图对象而不是一个列表?
【发布时间】:2016-10-13 08:01:41
【问题描述】:

我有兴趣了解new language design of 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,列表自然可以认为是Functors;我一直被认为是这样思考的:

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

【问题讨论】:

  • 基本原理应该与我们使用生成器而不是列表推导的原因相同。通过使用惰性求值,我们不需要在内存中保留大量内容。在此处查看接受的答案:stackoverflow.com/questions/1303347/…
  • 您能解释一下为什么这会给您带来“极大的痛苦”吗?
  • 我认为这是因为多年的使用表明map 的最常见用法只是对结果进行迭代。在不需要时建立列表效率低下,因此开发人员决定让map 变得懒惰。对于性能,这里有很多收获,而不是失去很多(如果您需要一份清单,只需索取一份……list(map(...)))。
  • 好吧,我发现有趣的是,他们没有保留 Functor 模式并提供惰性版本的 List,而是以某种方式决定在映射时强制对列表进行惰性求值。我宁愿有权做出自己的选择,也就是 Generator -> map -> Generator 或 List -> map -> List(由我决定)
  • @NoIdeaHowToFixThis,其实由你决定,如果你需要整个列表,只需将其转换为一个列表,非常简单

标签: python python-3.x


【解决方案1】:

我认为当generator expressions 也存在时,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]))

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

【讨论】:

  • 我认为,如果我们希望强调列表推导更符合 Python 风格,并且语言设计者想要强调这一点,我认为这是最直接的答案。 @vishes_shell 不知何故没有足够关注语言设计。
  • 在 Python 2 和 3 中产生不同的结果如果两个列表长度不同。在 python 2 和 python 3 中尝试c = list(map(max, [1,2,3,4], [0,10,0,10, 99]))
  • 这是从 python3 中完全删除 map 的原始计划的参考:artima.com/weblogs/viewpost.jsp?thread=98196
  • 嗯,当我将地图包装在列表中时,我得到了一个包含 1 个元素列表的列表。
【解决方案2】:

因为它返回一个迭代器,所以它省略了将完整大小的列表存储在内存中。这样您以后就可以轻松地对其进行迭代,而不会对记忆造成任何痛苦。可能您甚至不需要完整列表,只需要其中的一部分,直到达到您的条件。

你会发现这个docs 很有用,迭代器很棒。

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

【讨论】:

    【解决方案3】:

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

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

    map() 从 2 转换为 3 可能不仅仅是在其周围粘贴 list( ) 的简单案例。 Guido 还说:

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

    map(func, *sequences)
    

    变成

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

    "

    【讨论】:

    • Guido 评论是针对 map() 为函数的副作用而调用,而不是针对它作为仿函数的使用。
    • zip_longest 的转换是错误的。您必须使用 itertools.starmap 才能等效:list(starmap(func, zip_longest(*sequences)))。这是因为zip_longest 产生元组,所以func 将接收单个n-uple 参数,而不是像调用map(func, *sequences) 时那样接收n 不同的参数。
    【解决方案4】:

    在 Python 3 中,许多函数(不仅是 map,还有 ziprange 和其他函数)返回一个迭代器而不是完整列表。您可能需要一个迭代器(例如,避免将整个列表保存在内存中)或者您可能需要一个列表(例如,能够索引)。

    但是,我认为 Python 3 发生变化的关键原因是,虽然使用 list(some_iterator) 将迭代器转换为列表很简单,但反向等效 iter(some_list) 并没有达到预期的结果,因为完整列表具有已经构建并保存在内存中。

    例如,在 Python 3 中 list(range(n)) 工作得很好,因为构建 range 对象然后将其转换为列表的成本很低。但是,在 Python 2 中,iter(range(n)) 不会节省任何内存,因为完整列表是由 range() 在构建迭代器之前构建的。

    因此,在 Python 2 中,需要单独的函数来创建迭代器而不是列表,例如 imap 代表 map(虽然它们是 not quite equivalent),xrange 代表 range,@ 987654335@ 为zip。相比之下,Python 3 只需要一个函数,因为 list() 调用会在需要时创建完整列表。

    【讨论】:

    • itertools 的 Python 2.7 函数中的 AFAIK 也返回迭代器。此外,我不会将迭代器视为惰性列表,因为列表可以多次迭代并随机访问。
    • @abukaj 好的,谢谢,我已经编辑了我的答案以试图更清楚
    • @IgorRivin 你是什么意思? Python 3 map 对象确实有一个 next() 方法。 Python 3 range 范围对象不是我知道的严格迭代器
    • @Chris_Rands 在我的 Anaconda 发行版 python 3.6.2 中,执行 foo = map(lambda x: x, [1, 2, 3]) 返回一个地图对象 foo。执行foo.next() 时返回错误:'map' object has no attribute 'next'
    • @IgorRivin:以__ 开头和结尾的方法保留给Python;如果没有这种保留,您将难以区分 next 只是一种方法(它们不是真正的迭代器)和迭代器的事物。在实践中,您应该跳过这些方法而只使用next() 函数(例如next(foo)),该函数适用于从 2.6 开始的每个 Python 版本。这与您使用 len(foo) 的方式相同,即使 foo.__len__() 可以正常工作; dunder 方法通常被直接调用,而是作为其他操作的一部分隐式调用。
    猜你喜欢
    • 1970-01-01
    • 2012-12-02
    • 1970-01-01
    • 2020-07-04
    • 1970-01-01
    • 2018-05-30
    • 2019-12-20
    • 1970-01-01
    • 2020-10-13
    相关资源
    最近更新 更多