On Tue, Jul 23, 2019 at 08:59:09PM -0000, Kristian Klette wrote:
> Hi!
> 
> During the sprints after EuroPython, I made an attempt at adding support for
> comparing the results from `.values()` of two dicts.
> 
> Currently the following works as expected:
> 
> ```
> d = {'a': 1234}
> 
> d.keys() == d.keys()
> d.items() == d.items()
> ```
> 
> but `d.values() == d.values()` does not return the expected
> results. It always returns `False`. The symmetry is a bit off.

It seems to be doing an identity test on the "dict_values" view object 
itself:

py> d = {'a': 1}
py> a = b = d.values()
py> a == b
True

which I expect is probably the default __eq__ inherited from object. 
Each time you call d.values() you get a distinct object, hence the 
False.

I agree that this is a little surprising. Given that they are *views* of 
an underlying dict, I would expect that two views of the same dict ought 
to compare equal:

assert d.values() == d.values()

So at the least, we ought to have dict.values() comparison return True 
if the underlying dicts are identical. In pseudocode:

def __eq__(self, other):
    if self is other:
        # Same object implies equality.
        return True
    if isinstance(other, Dict_Values_View_Type):
         if self.owner_dict is other.owner_dict:
             # Two views into the same dict are always equal.
             return True
    return NotImplemented


I think that's the minimal behaviour that makes sense for a view.

Beyond that, we start getting complicated, and potentially expensive. 
But I can suggest at least one useful invariant. If a, b are two dicts:

    a.items() == b.items()

ought to be equivalent to:

    (a.keys() == b.keys()) and (a.values() == b.values)


That implies something like this pseudo-code:


def __eq__(self, other):
    if self is other:
        # Same object implies equality.
        return True
    if isinstance(other, Dict_Values_View_Type):
         a = self.owner_dict  # dict we are taking a view of
         b = other.owner_dict
         if a is b:
             # Two views into the same dict are always equal.
             return True
         if len(a) != len(b):
             # Unequal lengths implies the values cannot be equal.
             return False
         if a.items() == b.items():
             # (key,value) pairs are equal implies values are equal.
             return True
         elif a.keys() == b.keys():
             # keys are equal but items are not equal implies 
             # that the values must be different.
             return False
         # Fall back on value by value comparison?
         return list(self) == list(other)
    return NotImplemented



> In the bug trackers[0] and the Github PR[1], I was asked
> to raise the issue on the python-dev mailing list to find
> a consensus on what comparing `.values()` should do.
> 
> I'd argue that Python should compare the values as expected here,
> or if we don't want to encourage that behaviour, maybe we should
> consider raising an exception. 

Equality tests should (almost?) never raise an exception. It should be 
safe to test "are these two objects equal?" without guarding it in a 
try...except block. The definition of "equal" may not always be obvious, 
but it shouldn't raise unless the __eq__ method is buggy.

In Python, its quite common for __eq__ to fall back on ``is``, e.g.:

py> a = lambda x: x+1
py> a == a
True
py> a == (lambda x: x+1)
False

but I think in the case of views, we should at least fall back on 
identity of the underlying dicts, even if we decide the more complex 
tests are not worth the trouble.





-- 
Steven
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/TT3NF3TFFRERELNJ6QCHRV2NBGW6EUN5/

Reply via email to