list.__contains__ compares objects in wrong order

Issue #2352 resolved
Vasiliy Faronov
created an issue

Consider the following program:

class Foo(object):

    def __init__(self, bar):
        self.bar = bar

    def __repr__(self):
        return 'Foo(%r)' % (self.bar,)

    def __eq__(self, other):
        print('Foo.__eq__(%r, %r)' % (self, other))
        return self.bar == other

foo1 = Foo('A')
foo2 = Foo('B')
assert foo1 not in [foo2]

Under CPython 2.7.11 and 3.5.1, it prints:

Foo.__eq__(Foo('A'), Foo('B'))
Foo.__eq__(Foo('B'), 'A')

But under PyPy 5.3.1 (2.7), it prints:

Foo.__eq__(Foo('B'), Foo('A'))
Foo.__eq__(Foo('A'), 'B')

PyPy’s behavior contradicts the Python 2 language reference:

For the list and tuple types, x in y is true if and only if there exists an index i such that either x is y[i] or x == y[i] is true.

because according to the data model:

x==y calls x.__eq__(y)

According to the Python 3 language reference (can’t find anything about this in Python 2), equality does not have to be symmetric (emphasis mine):

User-defined classes that customize their comparison behavior should follow some consistency rules, if possible:

. . .

  • Comparison should be symmetric

. . .

Python does not enforce these consistency rules. In fact, the not-a-number values are an example for not following these rules.

In my actual case, x and y are instances of different classes with completely different, magical __eq__, so this becomes important.

Comments (6)

  1. Armin Rigo

    Also, I should add that this is borderline implementation detail: on CPython, list.__contains__ calls __eq__ in one order (which we just fixed in PyPy), but dict.__contains__ calls it in the other order anyway...

  2. Log in to comment