根据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紧凑字典的瘦子类更快的实现)。
为什么函数接收的有序映射不会在相等比较中遵循排序?
-
那个部分说"请注意,这并不一定意味着OrderedDict"?
-
是的,那怎么样?
-
所以你把它与OrderedDict比较,也许,这不是"有序映射"的含义
无论"有序映射"的含义如何,只要它不一定是OrderedDict,OrderedDict的==就不会考虑它的顺序。文档:
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.
-
3.6 dict顺序是实现细节,但函数接收的kwargs对象是有序映射不是实现细节。所以它应该与其他有序映射进行正确的相等比较,不应该吗?
-
@wim:添加了一些括号。
-
@wim关于你的相等运算符,你不能在不违反equals运算符的"传递"要求的情况下做你想做的事。也就是说,如果dict0 == ordered_dict0和dict0 == ordered_dict1,则必须是ordered_dict0 == ordered_dict1。根据你的建议,情况可能并非总是如此。
-
我想你错过了问题的重点。 PEP中的主张是"kwargs是有序映射",但实现似乎更弱,更像是"kwargs将保留迭代顺序"。
-
@Dune Transitivity已经被打破(OrderedDict([(1,2), (3,4)]) == {1:2, 3:4} == OrderedDict([(3,4), (1,2)])),所以谁在乎呢?
-
@wim:更新了特定的文档
-
@Ryan我仍然不认为它真的回答了这个问题,这只是重述行为是什么。 CPython开发人员肯定知道在这里有一个设计选择,并故意做出这个决定。我在寻找原因。
-
@wim:它表明行为已记录在案。我不认为你有充分的理由期望比较考虑到顺序。
-
@Ryan严格来说,它甚至没有表明行为是有记录的。我把return kwargs == data,而不是return data == kwargs,这意味着"有序映射"首先负责处理比较,所以OrderedDict.__eq__的实现方式在这里是无关紧要的。碰巧OrderedDict是dict的子类,而kwargs是dict,因此考虑了OrderedDict.__eq__的实现,但没有理由必须以这种方式完成。
"有序映射"仅表示映射必须保留顺序。这并不意味着顺序必须是映射的==关系的一部分。
PEP 468的目的只是为了保存订购信息。将命令作为==的一部分会产生向后不兼容性,而不会对激发PEP 468的任何用例产生任何实际好处。使用OrderedDict也会更昂贵(因为OrderedDict仍然保持其自己的单独链接列表以跟踪在popitem和move_to_end中不牺牲大O效率的情况下,它不能放弃该链表。
-
1.我不相信这里改变==的想法必须更加昂贵(尽管使用OrderedDict本身会更贵,是的)。例如,紧凑型词典提高了性能以及保留顺序。 2.会产生什么向后的不兼容性,这首先不是一个错误?更改此值不会影响与其他dicts的比较,只会对其他有序映射进行比较。
-
@wim:1)改变所有dicts的==将破坏一个荒谬的代码量,没有任何实际原因,并且创建一个新的dict子类只是为了使命令成为==的一部分会增加复杂性而没有真正的好处,以及与OrderedDict一起引入互操作性问题。 2)依赖于旧的==定义的任何代码都会破坏。依赖==的定义不是错误。此外,如果关键字参数包含在dict子类或其他映射类型中,则需要重新检查使用PyDict_Whatever访问关键字参数dicts的C代码。
第一个'为什么'的答案是因为这个功能是通过在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中的kwargs是dict并且它是dict因为在3.6之后整个事情才起作用。
-
伟大的发现吉姆,这似乎是正确的答案(特别是这一部分)。 ** kwargs迭代顺序是dict类型中其他开发的"快乐事故"。因此,他们摒弃了PEP 486并接受了它,但这或多或少是紧凑型dict impl的副作用。我确实认为他们可以更仔细地重新考虑PEP,因为任何有价值的"有序映射"应该具有eq比较所考虑的顺序。
-
也就是说,3.6 dict不是有序映射,它只是一个映射,恰好保留了插入顺序。
-
@wim PEP可能没有像你期望的那样充实,因为它没有通过其他PEP经历的通常的Python-dev迭代。此外,是的,在OrderedDict被定义为的意义上,它没有被排序。使用"有序"是误导性的,但我猜是"营销"。我猜我需要在几个答案中澄清一下。
只是要补充一下,如果你确实想要进行这项检查(不依赖于实现细节(即便如此,也不会在python 3.7中)),只需要做
1 2 3 4
| from collections import OrderedDict
>>> data = OrderedDict(zip('xy', 'xy'))
>>> def foo(**kwargs):
... return OrderedDict(kwargs) == data |
因为这保证是真的。
-
我知道这个解决方法,但为什么首先需要这个,因为kwargs现在将是一个有序的映射?
-
这将为foo(y='y', x='x')和foo(x='x', y='y')返回False
-
@ smac89呢? foo(x='x', y='y')给了我True。你没有运行python 3.6,或者这是OrderedDict不按顺序添加dict元素的东西吗?
-
@FHTMitchell,我认为我在测试时运行的是3.5。我将安装3.6并检查
-
@wim因为dict等于运算符不关心顺序。仅仅因为保留了订单,就无法保证平等运营商会考虑到这一点。
-
@FHTMitchell但是不能保证kwargs必须是dict,所以也要使用dict相等方法。它很容易成为dict的一个瘦子类,它会覆盖__eq__。
-
我不确定这个答案在"为什么"的问题中是否有用。真的,似乎更适合作为评论。
-
这将是一个破坏向后兼容性的可怕想法。再次"保证订单保存"不会评论是否将等同性测试考虑在内。
-
@JimFasarakisHilliard因为它表明它可以快速而简单地进行,因此部分答案"为什么"是因为它不被认为是必要的,因为解决这个特定问题很容易。