PEP 468 – 保留函数中 **kwargs 的顺序。
- 作者:
- Eric Snow <ericsnowcurrently at gmail.com>
- 讨论地址:
- Python-Ideas 邮件列表
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建日期:
- 2014-04-05
- Python 版本:
- 3.6
- 历史记录:
- 2014-04-05, 2016-09-08
- 决议:
- 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 中,dict 是有序的。这实际上消除了性能方面的担忧。
其他 Python 实现
另一个需要考虑的重要问题是,新功能必须考虑到多种 Python 实现。在某种程度上,它们中的每一个都应该实现有序 kwargs。在这方面,似乎这个想法没有问题。 [11] 对主要 Python 实现的非正式调查表明,此功能不会成为一项重大负担。
规范
从 3.6 版本开始,Python 将保留传递给函数的关键字参数的顺序。为了实现这一点,收集到的 kwargs 现在将是一个有序映射。注意,这并不一定意味着 OrderedDict。CPython 3.6 中的 dict 现在是有序的,类似于 PyPy。
这将仅适用于定义使用 **kwargs 语法来收集未指定关键字参数的函数。只有这些关键字参数的顺序才会被保留。
与 ** 解包语法的关系
在函数调用中,** 解包语法与本提案没有特殊关系。通过解包提供的关键字参数将与现在完全相同的方式处理:与定义的参数匹配的参数将收集在那里,其余参数将被收集到有序 kwargs 中(就像任何其他不匹配的关键字参数一样)。
注意,解包没有定义顺序的映射(如 dict)将保留其迭代顺序,就像平常一样。只是顺序仍然是未定义的。打包解包的键值对的有序映射将无法提供任何替代排序。这应该不足为奇。
有人短暂地讨论过是否应该将这些映射直接传递给函数的 kwargs,而不需要解包和重新打包它们,但这既超出了本提案的范围,而且无论如何可能都是个坏主意。(这些讨论时间短是有原因的。)
与 inspect.Signature 的关系
Signature 对象应该不需要进行任何更改。inspect.BoundArguments 的 kwargs
参数(由 Signature.bind() 和 Signature.bind_partial() 返回)将从 dict 更改为 OrderedDict。
C-API
没有变化。
语法
本提案没有添加或更改任何语法。
向后兼容性
以下内容将发生变化
- kwargs 的迭代顺序现在将保持一致(当然,除了上面描述的案例以外)
参考实现
对于 CPython,不需要做任何事情。
替代方案
退出装饰器
这与目前的提案相同,只是 Python 还将在 functools 中提供一个装饰器,该装饰器将导致收集到的关键字参数被打包到普通 dict 中,而不是 OrderedDict 中。
预后
只有在某些不常见情况下确定性能存在显着差异,或者存在其他无法通过其他方式解决的向后兼容性问题时,这才是必要的。
加入装饰器
现状将保持不变。相反,Python 将在 functools 中提供一个装饰器,该装饰器将注册或标记装饰过的函数,使其接收有序关键字参数。在调用时检查函数的性能开销将很小。
预后
唯一真正的缺点是,对于旨在通过在包装器定义中使用 kwargs 并在调用被包装的函数时使用 kwargs 解包来完美保留关键字参数的函数包装器工厂(例如 functools.partial 和许多装饰器),每个包装器都必须单独更新,尽管让 functools.wraps() 自动执行此操作会有所帮助。
__kworder__
关键字参数的顺序将在调用时单独存储在一个列表中。该列表将绑定到函数本地范围内的 __kworder__。
预后
这同样会使包装器案例复杂化。
具有更快迭代的紧凑字典
Raymond Hettinger 提出了一种字典实现的想法,该实现将导致保留字典的插入顺序(直到第一次删除)。这将非常适合 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 将是提供 __order__
属性的新最小字典子类的实例。dict 将保持不变。
预后
简单地切换到 OrderedDict 是一种更简单、更直观的更改。
鸣谢
感谢 Andrew Barnert 提供了有益的反馈,也感谢所有过去邮件列表的参与者。
脚注
参考文献
https://mail.python.org/pipermail/python-ideas/2010-October/008445.html
https://mail.python.org/pipermail/python-ideas/2011-January/009037.html
https://mail.python.org/pipermail/python-ideas/2013-February/019690.html
https://mail.python.org/pipermail/python-ideas/2013-May/020727.html
https://mail.python.org/pipermail/python-ideas/2014-March/027225.html
http://bugs.python.org/issue16276
http://bugs.python.org/issue16553
https://mail.python.org/pipermail/python-dev/2013-May/126327.html
版权
本文件已进入公有领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0468.rst
上次修改时间: 2023-10-11 12:05:51 GMT