平心在线官网:Python 3.10 的首个 PEP 降生,内置类型 zip() 迎来新特征

admin 4个月前 (07-03) 科技 59 0

译者前言:信赖通常用过 zip() 内置函数的人,都市赞许它很有用,然则,它的最大问题是可能会发生出非预期的效果。PEP-618 提出给它增添一个参数,可以有用地解决人人的痛点。

这是 Python 3.10 版本正式采取的第一个 PEP,「Python猫」一直有跟进社区最新动态的习惯,以是翻译了出来给人人尝鲜,强烈推荐一读。(PS:严酷来说,zip() 是一个内置类(built-in type),而不是一个内置函数(built-in function),但我们一样平常都称它为一个内置函数。)

PEP原文 : https://www.python.org/dev/peps/pep-0618/

PEP题目: Add Optional Length-Checking To zip

PEP作者: Brandt Bucher

建立日期: 2020-05-01

合入版本: 3.10

译者 :豌豆花下猫 @Python猫民众号

PEP翻译设计 :https://github.com/chinesehuazhou/peps-cn

摘要

本 PEP 建议给内置的 zip 添加一个可选的 strict 布尔关键字参数。当启用时,若是其中一个参数先被用尽了,则会引发 ValueError 。

念头

从作者的小我私家履历和一份对尺度库的观察 来看,显著有许多(若是不是绝大多数)zip 用例要求可迭代工具必须是等长的。有时候,周围代码的上下文可以保证这点,然则要 zip 处置的数据通常是由挪用者传入的、单独提供的或者以某种方式天生的。在这些情形下,zip 的默认行为意味着错误的重构或逻辑错误,很容易悄悄地导致数据丢失。这些 bug 不仅难以定位,甚至难以被觉察到。

很容易想到造成这种问题的简朴案例。例如,以下代码在 items 为一个序列(sequence)时可以优越地运行,然则若是挪用者将 item 重构为一个可消耗的迭代器,则代码会悄悄地发生缩短的、不匹配的效果:

def apply_calculations(items):
    transfoRMed = transform(items)
    for i, t in zip(items, transformed):
        yield calculate(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 里发生过一个 bug,会直接抛弃不匹配的节点:

>>> from ast import Constant, Dict, literal_eval
>>> nasty_dict = Dict(keys=[Constant(None)], values=[])
>>> literal_eval(nasty_dict)  # Like eval("{None: }")
{}

实际上,笔者已经在 Python 的尺度库和工具中找出了许多挪用点, 立刻在这些位置启用此新特征是适当的。

基本原理

一些评论者声称:布尔开关常量是一种“代码坏气息(code-smell)”,或者与 Python 的设计哲学南辕北辙。

然则,Python 当前在内置函数上有几个布尔关键字参数的用法,它们通常使用编译期常量来挪用:

  • compile(..., dont_inherit=True)
  • open(..., closefd=False)
  • print(..., flush=True)
  • sorted(..., reverse=True)

尺度库中另有许多类似用法。

这个新参数的想法和名称最初是由 Ram Rachum 提出的。该议题收到了 100 多个回复,而候选的“equal”也获得了相近的支持数。

笔者对它们没有很强烈的偏好,只管“equal equals” 读起来有点尴尬。它还可能(错误地)表示了 zip 的工具是相等的:

>>> z = zip([2.0, 4.0, 6.0], [2, 4, 8], equal=True)

规范

当用关键字参数 strict=True 挪用内置类 zip 时,若是参数的长度差别,则天生的迭代器会引发 ValueError。这个异常就发生在迭代器正常住手迭代的地方。

向上兼容

此项更改是完全向上兼容的。当前的 zip 不接受关键字参数,默认省略 strict 的“非严酷”用法会保持稳定。

参考实现

笔者设计了一个 C 实现。

用 Python 大致翻译如下:

def zip(*iterABLes, strict=False):
    if not iterables:
        return
    iterators = tuple(iter(iterable) for iterable in iterables)
    try:
        while True:
            items = []
            for iterator in iterators:
                items.append(next(iterator))
            yield tuple(items)
    except StopIteration:
        if not strict:
            return
    if items:
        i = len(items)
        plural = " " if i == 1 else "s 1-"
        MSG = f"zip() argument {i+1} is shorter than argument{plural}{i}"
        raise ValueError(msg)
    sentinel = object()
    for i, iterator in enumerate(iterators[1:], 1):
        if next(iterator, sentinel) is not sentinel:
            plural = " " if i == 1 else "s 1-"
            msg = f"zip() argument {i+1} is longer than argument{plural}{i}"
            raise ValueError(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,那么用户在挪用的地方启动检查,就会变得异常简朴。与其编写一套繁重的逻辑来处置,不如用这个新特征来直接检查。

有人还以为,在尺度库中放一个新的函数,相比在一个内置函数上加关键字参数,更“容易发现(discoverable)”。笔者差别意这一论断。

(4)维护成本

只管在提升易用性时,详细的实现是个次要问题,但主要的是要认识到,添加新的程序比修改原有程序庞大得多。与此 PEP 一起提供的 CPython 实现异常简朴,而且对 zip 的默认行为没有显著的性能影响,而在 itertools 中添加一个全新的程序将需要:

  • 复制 zip 的许多现有逻辑,zip_longest 就是这么干的。
  • 大刀阔斧地重构 zip 或 zip_longest 或这两者,以便共享一个公共的或者继承性的实现(这可能会影响性能)。

(5)添加多个“模式”以供切换

若是预期有三个或更多模式(mode),这个建议才会比二元标志更有意义。最显而易见的三种模式是:“最短的”(当前 zip 的行为),“严酷的”(本 PEP 提议的行为)和“最长的”(itertools.zip_longest 的行为)。

然则,除了当前的默认值以及本提案的“strict”模式,似乎不需要再添加其它模式。最可能的是添加一个“最长的”模式,但这需要一个新的 fillvalue 参数(它对于前两种模式都没有意义),另外,itertools.zip_longest 已经完善地处置了这种模式,若在 zip 中添加该模式,将会造成重复。现在尚不清晰哪一个是“显而易见的”选择:内置 zip 上的 mode 参数,照样已经历久存在于 itertools 中的 zip_longest。

(6)给 zip 添加方式或者组织函数

思量以下两个被提出来的做法

>>> zm = zip(*iters).strict()
>>> zd = zip.strict(*iters)

尚不清晰哪个更好,或者哪个更差。若是 zip.strict 作为一个方式来实现,则 zm 没问题,然则 zd 会泛起几种令人困惑的情形:

  • 返回不包装在元组中的效果(若是 iters 仅包罗一个元素,一个 zip 迭代器)。
  • 参数类型错误时抛出 TypeError(若是 iters 只包罗一个元素,不是一个 zip 迭代器)。
  • 否则,参数数目不对时抛出 TypeError。

若是 zip.strict 是作为 classmethod 或 staticmethod 实现,则 zd 将乐成执行,而 zm 将不发生任何效果(这正是我们最初要制止的问题)。

本提案还面临着更为庞大的问题,由于 CPython 中 zip 内置类的实现细节是未文档化的。这意味着若选择以上的某种行为,当前的实现就会被“锁定”(或至少要求对其举行仿真)。

(7)调换 zip 的默认行为

zip 的默认行为没有什么“错” ,由于在许多情形下,这确实是正确处置巨细不等的输入的方式。例如,在处置无限迭代器时,它异常有用。

itertools.zip_longest 已经用在仍然需要“分外”尾端数据的情形。

(8)使用回调来处置剩余工具

只管基本上可以执行用户需要的任何操作,但此解决方案在处置常见问题时(例如舍弃不匹配的长度),变得不必要的庞大且不直观。

(9)引发一个 AssertionError

没有内置函数或内置类的 API 会引发 AssertionError。此外,方文档 这么写的(它的所有):

Raised when an assert statement fails.

由于此功能与 Python 的 assert 语句无关,因此不应该引发 AssertionError。用户若希望在优化模式下禁用检查(像一个 assert 语句),可以改用 strict = __debug__。

(10)在 map 上添加类似的特征

本 PEP 不建议对 map 作任何更改,由于很少使用带有多个可迭代参数的map。然则,本 PEP 的裁定可作为未来讨论类似特征的先例(应该泛起)。

若是本 PEP 被拒绝,则 map 的那种特征实际上也不值得追求。若是通过了,则对 map 的更改不需要新的 PEP(只管像所有提案一样,都应仔细思量其有用性)。为了保持一致性,它应遵照此处讨论的跟 zip 相同的 API 和语义。

(11)什么也不做

此建议可能最没有吸引力。

悄悄地将数据截断是一种稀奇令人讨厌的 bug,而手写一个结实的解决方案却并非易事。Python 自己的尺度库(前文提到的 ast)是有现实意义的反例,很容易就陷入本 PEP 试图制止的那种陷阱。

推荐阅读:

1、PEP中文翻译设计 (https://github.com/chinesehuazhou/peps-cn)

2、学习 Python,怎能不懂点PEP呢? (https://mp.weixin.qq.com/s/oRoBxZ2-IyuPOf_MWyKZyw)

,

欧博官网

欢迎进入欧博官网(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

Sunbet声明:该文看法仅代表作者自己,与本平台无关。转载请注明:平心在线官网:Python 3.10 的首个 PEP 降生,内置类型 zip() 迎来新特征

网友评论

  • (*)

最新评论

标签列表

    文章归档

      站点信息

      • 文章总数:653
      • 页面总数:0
      • 分类总数:8
      • 标签总数:1146
      • 评论总数:283
      • 浏览总数:17261