由于
zip
产生与给定的最短迭代一样多的值,我希望将零个参数传递给 zip
以返回一个可迭代,产生 infinitely 许多元组,而不是返回一个空的可迭代。毕竟,inf ∅ 是无穷大,而不是零。
这也与其他幺半群运算的行为方式一致:
>>> sum([]) # sum
0
>>> math.prod([]) # product
1
>>> all([]) # logical conjunction
True
>>> any([]) # logical disjunction
False
>>> list(itertools.product()) # Cartesian product
[()]
对于其中每一个操作,在不给定参数时返回的值是操作的标识值,也就是说,当包含在操作中时不会修改结果:
sum(xs) == sum([*xs, 0]) == sum([*xs, sum()])
math.prod(xs) == math.prod([*xs, 1]) == math.prod([*xs, math.prod()])
all(xs) == all([*xs, True]) == all([*xs, all()])
any(xs) == any([*xs, False]) == any([*xs, any()])
或者至少给出一个基本同构的结果:
itertools.product(*xs, itertools.product())
== itertools.product(*xs, [()])
== (*x, ()) for x in itertools.product(*xs)
在
zip
的情况下,这将是:
==zip(*xs, zip())
f(x) for x in zip(*xs)
对于某些功能
f
。因为当给定 n参数时
zip
返回 n 元组,因此具有 0 个参数的 zip()
必须产生 0 元组,即 ()
。这迫使 f
返回 (*x, ())
,因此 zip()
等于 itertools.repeat(())
。另一个更普遍的法则是:
==((*x, *y) for x, y in zip(zip(*xs), zip(*ys))
zip(*xs, *ys)
这对于所有
xs
和 ys
都成立,包括当 xs
或 ys
为空时(并且 对于 itertools.product
成立)。
无限期地产生空元组也是这种直接重新实现的行为:
def my_zip(*iterables):
iterables = tuple(map(iter, iterables))
while True:
item = []
for it in iterables:
try:
item.append(next(it))
except StopIteration:
return
yield tuple(item)
这意味着没有参数的
zip
的情况必须是特殊情况 not 才能做到这一点。
为什么
zip()
不等于 itertools.repeat(())
尽管有上述所有?
zip()
最初引发了异常。它被更改为返回一个空列表,因为这对于 zip(*s)
的某些情况更方便,其中 s
结果是一个空列表。没有考虑什么可能是“身份”,在任何情况下都很难定义 zip - 没有任何东西可以用任意 x
进行 zip 并返回 x
。
将某些交换和关联数学函数应用于空列表以默认返回恒等式的最初原因尚不清楚,但可能是出于方便、最小惊讶原则以及 Perl 或 ABC 等早期语言的历史。很少明确提及数学恒等式的概念(例如,参见空列表中“全部”和“任何”结果的原因)。因此,没有理由依赖一般函数来做到这一点。在许多情况下,他们提出例外也就不足为奇了。