繁体   English   中英

Python3:内置函数“map”有bug吗?

[英]Python3: Does the built-in function "map" have a bug?

以下是我使用 Python 3.8.1(在 macOS Mojave、10.14.6 以及其他一些平台上的 Python 3.7(或更旧)上)。 我是计算新手,不知道如何要求改进语言,但我想我发现了内置函数map的奇怪行为。

由于代码next(iter(()))引发了StopIteration ,我希望从以下代码中获得StopIteration

tuple(map(next, [iter(())]))

令我惊讶的是,这默默地返回了元组()

因此,当StopIteration next击中iter(())返回的“空”迭代器时,地图对象的解包似乎停止了。 但是,我认为该异常处理StopIteration ,因为在从列表中选择“空”迭代器(将被next击中)之前没有引发StopIteration

  1. 我是否正确理解了这种行为?
  2. 这种行为是否有意为之?
  3. 这会在不久的将来改变吗? 或者我怎样才能得到它?

编辑:如果我以不同的方式解包 map 对象,行为是相似的,例如通过list ,for 循环,在列表中解包,解包函数参数,通过setdict 所以我相信它不是tuple而是map是错误的。

编辑:实际上,在 Python 2 (2.7.10) 中,“相同”代码引发了StopIteration 我认为这是理想的结果(除了这种情况下的map不返回迭代器)。

这不是map错误。 Python 决定依赖异常来控制流是一个丑陋的后果:实际的错误看起来像正常的控制流。

mapiter(()) map调用next时, next会引发StopIteration 这个StopIterationmap.__next__传播到tuple调用中。 这个StopIteration看起来像map.__next__通常会引发来表示map.__next__StopIteration ,所以tuple认为 map 只是没有元素。

这会导致比您看到的更奇怪的后果。 例如, map迭代器不标记本身用尽时,映射函数抛出一个异常,所以你可以继续在迭代它甚至以后:

m = map(next, [iter([]), iter([1])])

print(tuple(m))
print(tuple(m))

输出:

()
(1,)

(CPython map实现实际上并没有办法将自己标记为已耗尽——它依赖于底层迭代器。)

这种 StopIteration 问题很烦人,他们实际上改变了生成器 StopIteration 处理来缓解它。 StopIteration 过去通常从生成器中传播,但是现在,如果 StopIteration 会从生成器中传播出来,它会被 RuntimeError 替换,因此看起来生成器不会正常结束。 但是,这只影响生成器,而不影响其他迭代器,例如map

  1. 我是否正确理解了这种行为?

不完全的。 map接受它的第一个参数,一个函数,并将它应用于某个可迭代对象中的每个项目,它的第二个参数,直到它捕获StopIteration异常。 这是一个内部异常,用于告诉函数它已到达对象的末尾。 如果您手动引发StopIteration ,它会看到并停止,然后才有机会处理列表中的任何(不存在的)对象。

我是这个问题的发帖者,我想在这里总结一下我学到的东西以及我认为剩下的东西。 (我不打算将它作为一个新问题发布。)

在 Python 中,来自迭代器的__next__方法的StopIteration被视为迭代器已到达终点的信号。 (否则,它是错误的信号。)因此,迭代器的__next__方法必须捕获所有不是结束信号的StopIteration

map 对象是用map(func, *iterables)形式的代码创建的,其中func是一个函数,而*iterables代表一个(从 Python 3.8.1 开始)或多个迭代的有限序列。 结果地图对象的__next__进程有(至少)两种可能引发StopIteration的子进程:

  1. 调用序列*iterables __next__之一的__next__方法的过程。
  2. 调用参数func过程。

我从它的文档(或由help(map)显示help(map)理解的map的意图是,来自类型 (2) 的子StopIteration不是 map 对象的结尾。 但是,映射对象的__next__的当前行为是在这种情况下它的进程发出StopIteration (我还没有检查它是否真的捕获了StopIteration 。如果是,那么无论如何它都会再次引发StopIteration 。)这似乎是我问到的问题的原因。

在上面的答案中, user2357112 支持 Monica(让我友好地将名称缩写为“User Primes”)发现了这个丑陋的后果,但回答是 Python 的错,而不是map的错。 不幸的是,我在答案中没有找到对这一结论的令人信服的支持。 我怀疑修复map会更好,但出于性能原因,其他一些人似乎不同意这一点。 我对Python内置函数的实现一无所知,无法判断。 所以这一点留给了我。 尽管如此,User Primes 的回答提供了足够的信息,现在左边的问题对我来说并不重要。 (感谢 user2357112 再次支持 Monica!)

顺便说一下,我试图在 User Primes 的回答的评论中发布的代码如下。 (我认为它会在 PEP 479 之前起作用。)

def map2(function, iterable):
    "This is a 2-argument version for simplicity."
    iterator = iter(iterable)
    while True:
        arg = next(iterator) # StopIteration out here would have been propagated.
        try:
            yield function(arg)
        except StopIteration:
            raise RuntimeError("generator raised StopIteration")

下面是一个略有不同的版本(同样是一个 2 参数版本),这可能更方便(发布后希望得到改进建议!):

import functools
import itertools

class StopIteration1(RuntimeError):
    pass

class map1(map):
    def __new__(cls, func, iterable):
        iterator = iter(iterable)
        self = super().__new__(cls, func, iterator)
        def __next__():
            arg = next(iterator)
            try:
                return func(arg)
            except StopIteration:
                raise StopIteration1(0)
            except StopIteration1 as error:
                raise StopIteration1(int(str(error)) + 1)
        self.__next__ = __next__
        return self
    def __next__(self):
        return self.__next__()

# tuple(map1(tuple,
#            [map1(next,
#                  [iter([])])]))
# ---> <module>.StopIteration1: 1

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM