Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 468 – 在函数中保留 **kwargs 的顺序。

作者:
Eric Snow <ericsnowcurrently at gmail.com>
讨论至:
Python-Ideas 邮件列表
状态:
最终版
类型:
标准跟踪
创建日期:
2014年4月5日
Python 版本:
3.6
发布历史:
2014年4月5日,2016年9月8日
决议:
Python-Dev 消息

目录

摘要

函数定义中的 **kwargs 语法表明解释器应收集所有不对应于其他命名参数的关键字参数。然而,Python 不保留这些收集到的关键字参数传递给函数的顺序。在某些情况下,顺序很重要。此 PEP 规定,收集到的关键字参数应以有序映射的形式在函数体中公开。

动机

Python 函数定义中的 **kwargs 语法提供了一种动态处理关键字参数的强大方法。在某些语法应用中(参见用例),应用于收集到的关键字参数的语义要求保留顺序。不出所料,这类似于 OrderedDict 与 dict 的关系。

目前,要保留顺序,您必须手动进行,并且要与实际的函数调用分开。这涉及构建一个有序映射,无论是 OrderedDict 还是 2 元组的可迭代对象,然后将其作为单个参数传递给函数。[1]

有了本 PEP 中描述的功能,就不再需要这些样板代码了。

相比之下,目前

kwargs = OrderedDict()
kwargs['eggs'] = ...
...
def spam(a, kwargs):
    ...

以及本提案中的方案

def spam(a, **kwargs):
    ...

Alyssa (Nick) Coghlan 在谈到一些用例时,很好地总结道:[2]

These *can* all be done today, but *not* by using keyword arguments.
In my view, the problem to be addressed is that keyword arguments
*look* like they should work for these cases, because they have a
definite order in the source code. The only reason they don't work
is because the interpreter throws that ordering information away.

It's a textbook case of a language feature becoming an attractive
nuisance in some circumstances: the simple and obvious solution for
the above use cases *doesn't actually work* for reasons that aren't
obviously clear if you don't have a firm grasp of Python's admittedly
complicated argument handling.

多年来,这个提案的出现以及人们多次对 OrderedDict 构造函数感到困惑都支持了这一观察。[3] [4] [5]

用例

正如 Alyssa 指出的那样,在期望顺序重要的某些情况下,**kwargs 的当前行为是不直观的。除了下面概述的更具体的情况之外,通常“任何其他你希望控制迭代顺序*并*在一次调用中设置字段名称和值的地方都可能受益。”[6] 这在有序类型的工厂(例如 __init__())中很重要。

序列化

显然,OrderedDict 将从有序 kwargs 中受益(包括 __init__() 和 update())。然而,这种益处也延伸到序列化 API。[2]

In the context of serialisation, one key lesson we have learned is
that arbitrary ordering is a problem when you want to minimise
spurious diffs, and sorting isn't a simple solution.

Tools like doctest don't tolerate spurious diffs at all, but are
often amenable to a sorting based answer.

The cases where it would be highly desirable to be able use keyword
arguments to control the order of display of a collection of key
value pairs are ones like:

* printing out key:value pairs in CLI output
* mapping semantic names to column order in a CSV
* serialising attributes and elements in particular orders in XML
* serialising map keys in particular orders in human readable formats
  like JSON and YAML (particularly when they're going to be placed
  under source control)

调试

用 Raymond Hettinger 的话说 [7]

It makes it easier to debug if the arguments show-up in the order
they were created.  AFAICT, no purpose is served by scrambling them.

其他用例

  • 模拟对象。[8]
  • 控制对象呈现。
  • 可以指定默认值的替代 namedtuple()。
  • 按顺序指定参数优先级。

关注点

性能

正如前面已经指出的,有序关键字参数的想法已经提出了很多次。每次都得到了相同的回应,即保留关键字参数的顺序会对函数调用性能产生足够不利的影响,不值得这样做。然而,Guido 指出了以下几点 [9]

Making **kwds ordered is still open, but requires careful design and
implementation to avoid slowing down function calls that don't benefit.

如下所述,有一些方法可以解决这个问题,但代价是增加了复杂性。最终,最简单的方法是最有意义的方法:将收集到的关键字参数打包到 OrderedDict 中。然而,如果没有 OrderedDict 的 C 实现,就没有太多可讨论的了。这在 Python 3.5 中发生了变化。[10]

注意:在 Python 3.6 中,字典是保留顺序的。这几乎消除了性能问题。

其他 Python 实现

另一个需要考虑的重要问题是,新功能必须考虑到多个 Python 实现。在某种程度上,预计每个实现都将实现有序的 kwargs。在这方面,这个想法似乎没有什么问题。[11] 对主要的 Python 实现进行的非正式调查表明,此功能不会造成重大负担。

规范

从版本 3.6 开始,Python 将保留传递给函数的关键字参数的顺序。为了实现这一点,收集到的 kwargs 现在将是一个有序映射。请注意,这不一定意味着 OrderedDict。CPython 3.6 中的 dict 现在是有序的,类似于 PyPy。

这仅适用于其定义使用 **kwargs 语法收集未指定关键字参数的函数。只有这些关键字参数的顺序会被保留。

与 ** 解包语法的关系

函数调用中的 ** 解包语法与本提案没有特殊关联。通过解包提供的关键字参数将以与现在完全相同的方式处理:匹配已定义参数的关键字参数将被收集到那里,其余的将收集到有序 kwargs 中(就像任何其他不匹配的关键字参数一样)。

请注意,解包一个未定义顺序的映射(例如 dict)将像往常一样保留其迭代顺序。只是顺序将保持未定义。解包后的键值对将被打包到其中的有序映射将无法提供任何替代排序。这不应该令人惊讶。

曾有简短的讨论,建议简单地将这些映射直接传递给函数的 kwargs,而不进行解包和重新打包,但这超出了本提案的范围,而且无论如何都可能是一个糟糕的主意。(那些讨论之所以简短是有原因的。)

与 inspect.Signature 的关系

Signature 对象应该不需要任何更改。inspect.BoundArguments(由 Signature.bind() 和 Signature.bind_partial() 返回)的 kwargs 参数将从 dict 更改为 OrderedDict。

C-API

无更改。

语法

本提案未添加或更改任何语法。

向后兼容性

以下内容将发生变化

  • kwargs 的迭代顺序现在将保持一致(当然,除了上述情况外)

参考实现

对于 CPython 来说,没有什么可做的。

替代方法

选择退出装饰器

这与当前提案相同,不同之处在于 Python 还将在 functools 中提供一个装饰器,该装饰器将导致收集到的关键字参数被打包到一个普通的 dict 中,而不是 OrderedDict。

预后

只有在某些不常见的情况下,性能被确定为显著不同,或者存在其他无法通过其他方式解决的向后兼容性问题时,这才是必要的。

选择加入装饰器

现状将保持不变。相反,Python 将在 functools 中提供一个装饰器,该装饰器将注册或标记被装饰的函数,使其获得有序的关键字参数。在调用时检查函数的性能开销将是微不足道的。

预后

唯一的真正缺点是对于函数包装器工厂(例如 functools.partial 和许多装饰器),它们旨在通过在包装器定义中使用 kwargs 并在调用被包装函数时使用 kwargs 解包来完美地保留关键字参数。每个包装器都必须单独更新,尽管让 functools.wraps() 自动完成会有所帮助。

__kworder__

关键字参数的顺序将在调用时单独存储在一个列表中。该列表将绑定到函数局部变量中的 __kworder__。

预后

这同样使包装器的情况变得复杂。

具有更快迭代速度的紧凑型字典

Raymond Hettinger 提出了一个 dict 实现的想法,该实现将保留 dict 的插入顺序(直到第一次删除)。这将完美适用于 kwargs。[5]

预后

这个想法在可行性和时间框架上仍不确定。

请注意,Python 3.6 现在有了这个 dict 实现。

***kwargs

这将为函数签名添加一种新形式,作为 **kwargs 的互斥并行形式。新语法 ***kwargs(注意有三个星号)将指示 kwargs 应保留关键字参数的顺序。

预后

新语法只会在最**严峻**的情况下添加到 Python 中。在有其他可用解决方案的情况下,新语法是不合理的。此外,像所有选择加入的解决方案一样,新语法会使传递情况变得复杂。

注解

这是装饰器方法的一种变体。您将不再使用装饰器标记函数,而是使用 **kwargs 上的函数注解。

预后

除了传递复杂性之外,Python 核心开发还积极劝阻使用注解。使用注解选择加入顺序保留可能会干扰注解的其他应用程序级别使用。

dict.__order__

dict 对象将有一个新的属性 __order__,它将默认为 None,并且在 kwargs 的情况下,解释器将以与上述 __kworder__ 相同的方式使用它。

预后

这意味着对 kwargs 性能没有影响,但这种改变将是相当侵入性的(Python 大量使用 dict)。此外,对于包装器情况,解释器必须小心保留 __order__

KWArgsDict.__order__

这与 dict.__order__ 想法相同,但是 kwargs 将是一个新的最小 dict 子类的实例,该子类提供 __order__ 属性。dict 将保持不变。

预后

简单地切换到 OrderedDict 是一个更简单、更直观的改变。

致谢

感谢 Andrew Barnert 的有益反馈,以及所有过去邮件讨论的参与者。

脚注

参考资料


来源:https://github.com/python/peps/blob/main/peps/pep-0468.rst

最后修改:2025-02-01 08:59:27 GMT