可以产生意想不到的结果。PEP-618建议给它加一个参数,可以有效解决大家的痛点。这是Python3.10版本正式采用的第一个PEP。《蟒猫》一直都有跟进社区最新动态的习惯,所以特地翻译出来供大家试用。强烈推荐阅读。(PS:严格来说zip()是内置类(built-intype),不是内置函数(built-infunction),但我们一般称它为内置函数。)PEP原文:https://www.python.org/dev/peps/pep-0618/PEPTitle:AddOptionalLength-CheckingtozipPEP作者:BrandtBucher创建日期:2020-05-01合并版本:3.10译者:PeaFlowerUnderCat@Python猫公众号PEP翻译计划:https://github.com/chinesehua...总结本PEP建议为内置zip添加一个可选的strictBoolean关键字参数。启用后,如果其中一个参数首先用完,将引发ValueError。动机根据作者的个人经验和对标准库的调查,很明显许多(如果不是大多数的话)zip用例要求iterables的长度相等。有时周围代码的上下文保证了这一点,但通常要压缩的数据是传入的、单独提供的或由调用者以某种方式生成的。在这些情况下,zip的默认行为意味着糟糕的重构或逻辑错误很容易导致数据悄无声息地丢失。这些错误不仅难以定位,甚至难以检测。很容易想到导致此类问题的简单情况。例如,以下代码在items是一个序列时工作正常,但如果调用者将items重构为可消耗的迭代器,代码将悄悄地产生缩短的、不匹配的结果:defapply_calculations(items):transformed=transform(items)fori,tinzip(items,transformed):yieldcalculate(i,t)zip还有其他几种常见用法。惯用技巧尤其成问题,因为它们经常被不完全理解代码工作原理的用户使用。这是一个例子,解压成一个zip文件变成一个嵌套的迭代器:>>>x=[[1,2,3],["one""two""three"]]>>>xt=list(zip(*x))另一个例子是将数据“分块”成大小相等的组:>>>n=3>>>x=range(n**2),>>>xn=list(zip(*[iter(x)]*n))第一个例子中,非矩形数据往往会导致逻辑错误。在第二个例子中,长度不是n的倍数的数据通常也是一个错误。因为这两个习语都会默默地忽略不匹配的尾随元素。最有说服力的例子来自使用zip的标准库ast。它在literal_eval中有一个错误,会直接丢弃不匹配的节点:>>>fromastimportConstant,Dict,literal_eval>>>nasty_dict=Dict(keys=[Constant(None)],values=[])>>>literal_eval(nasty_dict)#Likeeval("{None:}"){}其实笔者已经在Python的标准库和工具中发现了很多适合立即启用这个新特性的调用点。基本原理一些评论者声称布尔开关常量是一种“代码味道”,或者它们违背了Python的设计理念。然而,Python目前在内置函数上有几种boolean关键字参数的用法,通常使用编译时常量调用它们:compile(...,dont_inherit=True)open(...,closefd=False)print(...,flush=True)sorted(...,reverse=True)标准库中有很多类似的用法。这个新参数的想法和名称最初是由RamRachum提出的。该主题收到了100多个回复,候选人“equal”获得了相似数量的赞成票。我对他们没有强烈的偏好,虽然“equalequals”读起来有点别扭。它也可能(错误地)暗示zip对象是相等的:>>>z=zip([2.0,4.0,6.0],[2,4,8],equal=True)specificationwhenusedwithkeywordargumentstrict=TrueWhen调用内置类zip,如果参数长度不同,生成的迭代器将引发ValueError。此异常发生在迭代器通常停止迭代的位置。向上兼容性此更改是完全向上兼容的。目前zip不接受关键字参数,省略strict的默认“非严格”用法保持不变。ReferenceImplementation作者设计了一个C实现。在Python中大致翻译如下:defzip(*iterables,strict=False):ifnotiterables:returniterators=tuple(iter(iterable)foriterableiniterables)try:whileTrue:items=[]foriteratoriniterators:items.append(next(iterator))yieldtuple(items)exceptStopIteration:如果不严格:returnifitems:i=len(items)plural=""ifi==1else"s1-"msg=f"zip()argument{i+1}isshorterthanargument{plural}{i}"raiseValueError(msg)sentinel=object()fori,iteratorinenumerate(iterators[1:],1):ifnext(iterator,sentinel)不是sentinel:plural=""ifi==1else"s1-"msg=f"zip()argument{i+1}islongerthanargument{plural}{i}"raiseValueError(msg)被拒绝的想法(1)添加itertools.zip_strict这是Python-Ideas邮件列表上支持最多的替代方案,因此值得在这里讨论。它没有任何严重的缺陷,如果这个PEP被拒绝,它是一个很好的替代品。考虑到这一点,向zip添加一个可选参数可以更好地解决导致此PEP发生微小变化的问题。(2)按照先例,itertools里面有个zip_longest,貌似很有动力加个zip_strict。然而,zip_longest在许多方面是一个更复杂和更具体的程序:它负责填充缺失值,但其他函数都不需要为此担心。如果zip和zip_longest都在itertools中,或者都是内置函数,那么在同一个地方添加zip_strict确实是一个更有效的论点。然而,新的“strict”用法在接口和行为方面比zip_longest更接近zip的概念,但不足以成为内置对象。出于这个原因,使用新选项就地扩展zip似乎是最自然的选择。(3)易用性如果zip能够杜绝此类bug,用户在调用处开始检查就会变得非常简单。无需编写大量逻辑来处理它,而是使用此新功能直接进行检查。一些人还争辩说,将新函数放入标准库比向内置函数添加关键字参数更“容易发现”。作者不同意这种说法。(4)维护成本虽然在提高易用性时具体实现是次要问题,但重要的是要认识到添加新程序比修改现有程序要复杂得多。此PEP提供的CPython实现非常简单,对zip的默认行为没有明显的性能影响,而向itertools添加一个全新的程序将需要复制zip的大部分现有逻辑,而zip_longest就是这样做的。显着重构zip或zip_longest或两者以共享公共或继承的实现(这可能会影响性能)。(5)增加多种“模式”切换。如果预期有三种或更多模式,则此建议仅比二进制标志更有意义。三种最明显的模式是:“最短”(zip的当前行为)、“严格”(此PEP提出的行为)和“最长”(itertools.zip_longest的行为)。但是,似乎没有必要在当前默认值和该提案的“严格”模式之外添加其他模式。最有可能是添加一个“最长”模式,但这需要一个新的fillvalue参数(这对前两种模式没有意义),而且,itertools.zip_longest已经完美地处理了这个模式,如果在zip添加这个中的模式会导致重复。目前尚不清楚哪个是“明显”的选择:内置zip上的模式参数,或itertools中长期存在的zip_longest。(6)给zip添加方法或构造函数考虑下面两种建议的做法:>>>zm=zip(*iters).strict()>>>zd=zip.strict(*iters)不清楚哪个更好,或者哪个更糟。如果zip.strict作为方法实现,zm没问题,但zd有几个令人困惑的情况:返回未包装在元组中的结果(如果iters只包含一个元素,一个zip迭代器)。如果参数类型错误(如果iters只包含一个元素,而不是zip迭代器),则引发TypeError。否则,如果参数数量不正确,则会抛出TypeError。如果zip.strict被实现为类方法或静态方法,那么zd将成功执行,而zm将不会产生任何结果(这正是我们首先试图避免的问题)。该提案还面临进一步的复杂性,因为CPython中zip内置类的实现细节没有记录。这意味着如果选择了上述行为之一,则当前实现被“锁定”(或至少需要被模拟)。(7)改变zip的默认行为zip的默认行为没有什么“错误”,因为在许多情况下它确实是处理不等大小输入的正确方法。它很有用,例如,在处理无限迭代器时。itertools.zip_longest已用于仍然需要“额外”尾部数据的情况。(8)使用回调来处理剩余对象虽然基本上可以做用户想做的任何事情,但在处理诸如丢弃不匹配长度等常见问题时,此解决方案变得不必要地复杂且不直观。(9)引发AssertionError没有内置函数或内置类的API将引发AssertionError。此外,官方文档是这样说的(全部):断言语句失败时引发。由于此功能与Python的assert语句无关,因此不应引发AssertionError。希望在优化模式下禁用检查(如断言语句)的用户可以改用strict=__debug__。(10)向地图添加类似的功能本PEP不建议对地图进行任何更改,因为很少使用具有多个可迭代参数的地图。但是,此PEP的裁决可作为未来讨论类似功能的先例(如果出现)。如果这个PEP被拒绝,地图的那个特性也不值得追求。如果通过,对地图的更改不需要新的PEP(尽管像所有提案一样,应仔细考虑它们的用处)。为了保持一致性,它应该遵循与此处讨论的zip相同的API和语义。(11)什么都不做这个建议可能是最没有吸引力的。悄无声息地截断数据是一个特别讨厌的错误,手动编写一个健壮的解决方案是非常重要的。Python自己的标准库(前面提到的ast)是一个实际的反例,很容易落入本PEP试图避免的那种陷阱。推荐阅读:1.PEP中文翻译项目(https://github.com/chinesehua...2.学Python,怎么能看不懂PEP?(https://mp.weixin.qq.com/s/oR...
