关于python:为什么** kwargs映射与不同排序的OrderedDict相等?

Why does the **kwargs mapping compare equal with a differently ordered OrderedDict?

根据PEP 468:

Starting in version 3.6 Python will preserve the order of keyword arguments as passed to a function. To accomplish this the collected kwargs will now be an ordered mapping. Note that this does not necessarily mean OrderedDict.

在这种情况下,为什么这个有序映射无法与Python的规范有序映射类型collections.OrderedDict进行相等的比较:

1
2
3
4
5
6
7
8
9
>>> from collections import OrderedDict
>>> data = OrderedDict(zip('xy', 'xy'))
>>> def foo(**kwargs):
...     return kwargs == data
...
>>> foo(x='x', y='y')  # expected result: True
True
>>> foo(y='y', x='x')  # expected result: False
True

虽然现在保留了迭代顺序,但kwargs似乎表现得像比较的普通字典。 从3.5开始,Python有一个C实现的有序字典,因此它可以直接使用(或者,如果性能仍然是一个问题,使用3.6紧凑字典的瘦子类更快的实现)。

为什么函数接收的有序映射不会在相等比较中遵循排序?


无论"有序映射"的含义如何,只要它不一定是OrderedDictOrderedDict==就不会考虑它的顺序。文档:

Equality tests between OrderedDict objects are order-sensitive and are implemented as list(od1.items())==list(od2.items()). Equality tests between OrderedDict objects and other Mapping objects are order-insensitive like regular dictionaries. This allows OrderedDict objects to be substituted anywhere a regular dictionary is used.


"有序映射"仅表示映射必须保留顺序。这并不意味着顺序必须是映射的==关系的一部分。

PEP 468的目的只是为了保存订购信息。将命令作为==的一部分会产生向后不兼容性,而不会对激发PEP 468的任何用例产生任何实际好处。使用OrderedDict也会更昂贵(因为OrderedDict仍然保持其自己的单独链接列表以跟踪在popitemmove_to_end中不牺牲大O效率的情况下,它不能放弃该链表。


第一个'为什么'的答案是因为这个功能是通过在CPython中使用普通的dict来实现的。正如@ Ryan的答案指出的那样,这意味着比较不会对订单敏感。

第二个"为什么"这就是为什么这不使用OrderedDict

使用OrderedDict是PEP 486初稿中所述的初始计划。如本回复中所述,该想法是收集一些性能数据以显示插入OrderedDict的效果,因为这是一个点当这个想法浮出水面之前的争论。 PEP的作者甚至暗示保留dict的顺序是该主题的最终答复中的另一种选择。

在那之后,关于该主题的对话似乎已经消失,直到Python 3.6出现。当新的dict出现时,它具有开箱即用的PEP 486的良好副作用(正如这个Python-dev线程所述)。该线程中的特定消息还说明了作者如何将术语OrderedDict更改为有序映射。 (这也是在最初的PEP 468之后的PEP 468的新提交时)

据我所知,这个重写是为了允许其他实现提供他们认为合适的功能。 CPython和PyPy已经有了一个很容易实现PEP 468的字典,其他实现可能会选择OrderedDict,其他实现可能会采用另一种形式的有序映射。

但这确实为问题打开了大门。从理论上讲,它确实意味着在Python 3.6的实现中使用OrderedDict作为实现此功能的结构,比较将是对顺序敏感的,而在其他(CPython)它不会。 (在Python 3.7中,所有dict都需要按插入顺序排列,因此这一点可能没什么意义,因为所有实现都会将它用于**kwargs)

虽然看起来确实是一个问题,但事实并非如此。正如@ user2357112指出的那样,==无法保证。 PEP 468仅保证订单。据我所知,==基本上是实现定义的。

简而言之,它在CPython中比较相等,因为CPython中的kwargsdict并且它是dict因为在3.6之后整个事情才起作用。


只是要补充一下,如果你确实想要进行这项检查(不依赖于实现细节(即便如此,也不会在python 3.7中)),只需要做

1
2
3
4
from collections import OrderedDict
>>> data = OrderedDict(zip('xy', 'xy'))
>>> def foo(**kwargs):
...     return OrderedDict(kwargs) == data

因为这保证是真的。