Why does the OrderedDict keys view compare order-i

2019-02-06 11:59发布

问题:

Why does the OrderedDict keys view compare order-insensitive?

>>> from collections import OrderedDict
>>> xy = OrderedDict([('x', None), ('y', None)])
>>> yx = OrderedDict([('y', None), ('x', None)])
>>> xy == yx
False
>>> xy.keys() == yx.keys()
True

The OrderedDict keys view should arguably behave like an OrderedSet, but instead it behaves the same as dict.keys (i.e. like a usual set).

Same "issue" in python2:

>>> xy.viewkeys() == yx.viewkeys()
True

They are different types, (odict_keys is a subclass of dict_keys)

>>> type(xy.keys())
odict_keys
>>> type({}.keys())
dict_keys

And there is already an order-sensitive keys comparison available that they could have trivially used, but it's apparently only used as a post-check for the odict rich comparison.

Is this a design decision, or a bug? If it's a design decision, where could I find a discussion of the justification?

回答1:

Looks like OrderedDict delegates the implementation of the various view objects to the common dict implementation; this remains the case even in Python 3.5 where OrderedDict gained a C accelerated implementation (it delegates object construction to _PyDictView_New and provides no override for the generic view's rich comparison function.

Basically, OrderedDict views iterate with the same order their backing OrderedDict would (because there is no cost to do so), but for set-like operations, they act like set, using content equality, subset/superset checks, etc.

This makes the choice to ignore ordering make sense to some extent; for some set operations (e.g. &, |, ^), the return value is a set without order (because there is no OrderedSet, and even if there were, which ordering do you use for something like & where the ordering may be different in each view?), you'd get inconsistent behaviors if some of the set-like operations were order sensitive and some weren't. And it would be even weirder when two OrderedDict keys views were order sensitive, but comparing OrderedDict views to dict views wasn't.

As I noted in the comments, you can get order sensitive keys comparison pretty easily with:

from operator import eq

# Verify that keys are the same length and same set of values first for speed
# The `all` check then verifies that the known identical keys appear in the
# same order.
xy.keys() == yx.keys() and all(map(eq, xy, yx))

# If you expect equality to occur more often than not, you can save a little
# work in the "are equal" case in exchange for costing a little time in the
# "not even equal ignoring order case" by only checking length, not keys equality:
len(xy) == len(yz) and all(map(eq, xy, yx))


回答2:

I can't find anything published, but I guess this logic could justify the behaviour:

If you have two dictionaries, d1 and d2, you'd expect that comparing the keys checks whether they have the same keys, right?

def compare_dict_keys(d1, d2):
    d1.keys() == d2.keys()

This function should behave the same way for any types of dictionary (and OrderedDict is a type of dict). It would seem wrong if such a function started returning False just because d1 and d2 are sorted.

In other words, these should all evaluate the same (and they do):

>>> {1:2, 3:4}.keys() == {3:4, 1:2}.keys()
True
>>> {1:2, 3:4}.keys() == OrderedDict([(3,4),(1,2)]).keys()
True
>>> OrderedDict([(1,2),(3,4)]).keys() == OrderedDict([(3,4),(1,2)]).keys()
True

But OrderedDict is specal, isn't it?

What OrderedDict does offer you is the guarantee of order when you iterate through it. The same guarantee exists for OrderedDict.keys(), but without breaking the compatibility with dict.