为什么**kwargs映射与顺序不同的OrderedDict相等?

12

根据PEP 468

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

既然如此,为什么这个有序映射无法与Python的规范有序映射类型collections.OrderedDict进行相等比较呢?

>>> 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 在比较方面表现得就像一个普通的字典一样。自 Python 3.5 起,Python 有一个由 C 实现的有序字典因此它本可以直接使用(或者,如果性能仍然是一个问题,可以使用一个更快的实现,使用一个 3.6 紧凑字典的薄子类)。

为什么函数接收到的有序映射在相等性比较中不考虑顺序?


3
这部分的意思是“请注意,这并不一定意味着OrderedDict”。 - smac89
是的,它有什么问题吗? - wim
所以你正在将其与OrderedDict进行比较,也许,这并不是“有序映射”所指的意思。 - smac89
4个回答

14

无论“有序映射”意味着什么,只要它不是必须使用OrderedDictOrderedDict==将不考虑其顺序。文档:

OrderedDict对象之间的相等性测试会保留顺序,并采用list(od1.items())==list(od2.items())实现。OrderedDict对象与其他Mapping对象之间的相等性测试不保留顺序,就像常规字典一样。 这使得可以在任何使用常规字典的地方替换为OrderedDict对象。


1
3.6字典的顺序是实现细节,但函数接收到的kwargs对象作为有序映射不是实现细节。因此,它应该与其他有序映射进行正确的相等比较,对吗? - wim
@wim 关于您的等式运算符,如果您不违反等式运算符的“传递性”要求,就不能按照您想要的方式进行操作。也就是说,如果 dict0 == ordered_dict0dict0 == ordered_dict1,那么必须有 ordered_dict0 == ordered_dict1。而按照您的建议,这种情况可能并不总是成立。 - Dunes
2
@Dunes 传递性已经被打破了(OrderedDict([(1,2), (3,4)]) == {1:2, 3:4} == OrderedDict([(3,4), (1,2)])), 那还有谁在乎呢? - wim
@Ryan,我仍然认为这并没有真正回答问题,这只是在重申行为什么。CPython开发人员肯定知道需要在这里做出设计选择,并有意做出了这个决定。我正在寻找原因。 - wim
6
@wim:这表明该行为已有文档记录。我认为您没有充分的理由期望比较会考虑顺序。 - Ry-
显示剩余3条评论

10
“有序映射”仅意味着映射必须保留顺序。这并不意味着顺序必须是映射的 == 关系的一部分。
PEP 468的目的只是为了保留排序信息。如果把顺序作为 == 的一部分,会产生向后不兼容性,而且对于激发PEP 468的任何用例都没有实际好处。使用OrderedDict也会更昂贵(因为OrderedDict仍然维护其自己的单独链接列表以跟踪顺序,并且它无法放弃该链接列表而不牺牲popitemmove_to_end 的big-O效率)。

  1. 我并不认同在这里更改==会更加昂贵的想法(尽管使用OrderedDict本身会更昂贵)。例如,紧凑的字典实现提高了性能,并保留了顺序。
  2. 如果没有第一个错误,那么将产生什么向后不兼容性?更改这个不会影响与其他字典的比较,只会影响与其他有序映射的比较。
- wim
4
@wim:1)将所有字典的==更改会无端地破坏大量代码,而为了让顺序成为==的一部分而创建一个新的字典子类会增加复杂性,却没有实际好处,并且引入有序字典(OrderedDict)的互操作性问题。2)任何依赖旧定义的==的代码都会出现问题。依赖==的定义不是错误。此外,如果关键字参数以字典子类或其他映射类型传递,则使用PyDict_Whatever访问关键字参数字典的C代码需要重新审查。 - user2357112

8
你的第一个问题的答案是因为CPython中使用了一个普通的dict来实现此功能。正如@Ryan的答案所指出的那样,这意味着比较不会受到顺序的影响。
第二个问题是为什么不使用OrderedDict
在PEP 486的第一版草案中,使用OrderedDict是最初的计划。正如此回复中所述,当时的想法是收集一些性能数据,以展示插入OrderedDict的效果,因为这是之前浮动的一个争议点。PEP的作者甚至在该线程的最终回复中暗示有序字典是另一个选择。
此后,关于这个话题的讨论似乎已经消失,直到Python 3.6出现。当新的字典出现时,它有一个好的副作用,就是直接实现了PEP 486(正如这个Python-dev线程所述)。该线程中的具体消息还说明了作者希望将术语OrderedDict更改为Ordered Mapping。(这也是在初始提交之后的PEP 468上做出的新提交
据我所知,这种措辞的改变是为了让其他实现以他们认为合适的方式提供此功能。CPython和PyPy已经有了一个轻松实现PEP 468的字典,其他实现可能会选择OrderedDict,其他人则可以选择另一种形式的有序映射。
虽然实现这个功能的Python 3.6中使用OrderedDict作为结构,这意味着比较是有序的,而在其他实现中(CPython),则没有这个要求。这可能会产生问题。但实际上并不是什么大问题,因为PEP468仅保证顺序,而无法保证“==”的结果。简而言之,在CPython中,由于kwargs是一个dict,而在3.6之后整个过程都能正常工作,所以它相等比较是成立的。

Jim,这真是个好发现,看起来这就是正确的答案(特别是this部分)。**kwargs的迭代顺序是字典类型中其他发展的“意外之喜”。所以,他们重新审视了PEP 486并接受了它,但这更多或多少是紧凑字典实现的副作用。我认为他们本可以更仔细地重新措辞PEP,因为任何值得一试的“有序映射”都应该考虑到顺序在等式比较中的影响。 - wim
3.6字典不是有序映射,它只是恰好保留插入顺序的映射。 - wim
1
@wim 可能不像你期望的那样详尽,因为它没有经过其他PEP通常经历的Python-dev迭代。此外,是的,它的顺序并不像OrderedDict定义的那样有序。使用“ordered”是具有误导性的,但我猜这是“营销”。我想我需要在我的一些答案中澄清这一点。 - Dimitris Fasarakis Hilliard

1

补充一点,如果您确实想进行此检查(而不依赖于实现细节(即使这样,也不会出现在Python 3.7中)),只需执行以下操作

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

由于这是保证为真的。

我知道这个解决方法,但是既然*kwargs现在是有序映射,为什么需要这样做呢? - wim
对于 foo(y='y', x='x')foo(x='x', y='y'),此代码将返回 False - smac89
@smac89是这样吗?foo(x='x', y='y')给我返回True。你没有运行Python 3.6吗,还是OrderedDict不按顺序添加dict元素的问题? - FHTMitchell
@FHTMitchell,我想我在测试时使用的是3.5版本。我会安装3.6并检查。 - smac89
3
在“为什么”问题中,我不确定这个答案的有用性。看起来更适合作为评论。 - Dimitris Fasarakis Hilliard
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接