On 7/23/2019 8:27 PM, Steven D'Aprano wrote:
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.

If one has not learned the default meaning of '==' in Python. Perhaps this should be given more emphasis in beginner courses. "What does it mean for two object to be 'equal'?" It is not a trivial question.

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

Makes sense, up to here.

          # Fall back on value by value comparison?
          return list(self) == list(other)

This seems wrong. Creating two lists raises the cost and the comparison will depend on insertion order.

     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.

I strongly agree, with almost? likely omitted. Testing before acting and recovering from failure after acting should be alternatives.

--
Terry Jan Reedy
_______________________________________________
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/PJVCRTTZI26VVXZLZ4NAMY35HMRJAI64/

Reply via email to