59

If I have two objects o1 and o2, and we know that

id(o1) == id(o2)

returns true.

Then, does it follow that

o1 == o2

Or is this not always the case? The paper I'm working on says this is not the case, but in my opinion it should be true!

Jonas Kaufmann
  • 1,797
  • 3
  • 22
  • 43
  • 14
    "this is not the case, but in my opinion it should be true!" -- note that "not the case" and "should be true" aren't mutually exclusive ;-) People *should* implement the `==` operator as a reflexive relation, but in some cases they choose not to (`WeirdEquals` below) and in some cases they even find a reason not to, perhaps due to external constraints (IEEE NaN). – Steve Jessop Jan 05 '16 at 00:48
  • 1
    In fact, to expand on the above, people should almost certainly implement `__eq__` as an equivalence relation (reflexive `assert a == a`, symmetric `if a == b: assert b == a`, transitive `if a == b and b == c: assert a == c`). – wchargin Jan 05 '16 at 05:38
  • Other than NaN and concurrency, what are some other common instances where `==` is not reflexive for a specific purpose? – MartyMacGyver Jan 05 '16 at 22:41
  • @MartyMacGyver but vanilla Python has neither of those – cat Jan 05 '16 at 22:51
  • @cat The nan instance works as below (after importing math) and I would expect the threading one does as well so I'm not sure what you mean by "vanilla" - I'm considering the default packages that install with Python as a baseline. I'm asking about those as well as any other common packages that are well-known for this idiosyncrasy. – MartyMacGyver Jan 06 '16 at 00:32

3 Answers3

129

Not always:

>>> nan = float('nan')
>>> nan is nan
True

or formulated the same way as in the question:

>>> id(nan) == id(nan)
True

but

>>> nan == nan
False

NaN is a strange thing. Per definition it is not equal nor less or greater than itself. But it is the same object. More details why all comparisons have to return False in this SO question.

Community
  • 1
  • 1
Mike Müller
  • 82,630
  • 20
  • 166
  • 161
  • @AdamSmith, why this is a more real case than the accepted answer? – Jorge Leitao Jan 04 '16 at 21:33
  • 29
    @J.C.Leitão -- this is because the accepted answer simply redefines `__eq__` to behave nonsensically for a certain trollish class of objects, but the behavior described here is present in a built-in type. (It's also the same for *all* systems/languages that claim IEEE754 conformance -- the equivalent C code does the *exact same thing* ;) – LThode Jan 04 '16 at 21:40
  • @J.C.Leitão Exactly as LThode replied. N.B. I'm not trying to denigrate recursive's accepted answer. If you were 10k you'd see a deleted answer from me that looks nearly identical! – Adam Smith Jan 04 '16 at 22:04
  • 6
    @AdamSmith: I think it's a better answer than mine, wish I'd thought of it! – recursive Jan 04 '16 at 22:06
  • @LThode Why is it trollish? There might be legitimate reasons to behave similar to `nan`. – gerrit Jan 05 '16 at 11:52
  • @gerrit -- the "trollish" was a reference to Timgeb's answer below :) (It's merely surprising behavior for someone who is told "here's a value-type, go use it", by the way) – LThode Jan 05 '16 at 13:44
  • @LThode, my question was more on the spirit of: is defining `__eq__` as being incompatible with `id() == id()` a bad thing? If yes, why Python allows it? If not, then I would argue that @recursive just didn't found a good example for a custom class where the behaviour has an advantage. – Jorge Leitao Jan 05 '16 at 14:28
  • 3
    @J.C.Leitão -- Python *must* allow it, or else you wouldn't be able to create a BigFloat UDT that implements the IEEE754 NaN semantics. (This would be a real drag for a Python set of bindings to the [MPFR library](http://www.mpfr.org/), for instance.) – LThode Jan 05 '16 at 17:22
  • 2
    @J.C.Leitão -- to the spirit of your question, though -- it's a matter of the contract your users expect the type to provide. A user-defined floating point type that didn't have fully unordered NaNs would be *extremely* surprising for scientific users who rely on the carefully crafted semantics of IEEE754, while irreflexive comparisons in a class that represents a parser token would be equally surprising to a compiler writer. – LThode Jan 05 '16 at 17:33
58

The paper is right. Consider the following.

class WeirdEquals:
    def __eq__(self, other):
        return False

w = WeirdEquals()
print("id(w) == id(w)", id(w) == id(w))
print("w == w", w == w)

Output is this:

id(w) == id(w) True
w == w False
recursive
  • 83,943
  • 34
  • 151
  • 241
25

id(o1) == id(o2) does not imply o1 == o2.

Let's have a look at this Troll which overrides __eq__ to always return False.

>>> class Troll(object):
...     def __eq__(self, other):
...         return False
... 
>>> a = Troll()
>>> b = a
>>> id(a) == id(b)
True
>>> a == b
False

That being said, there should be very few examples in the standard library where the object-ids match but __eq__ can return False anyway, kudos @MarkMüller for finding a good example.

So either the objects are insane, very special (like nan), or concurrency bites you. Consider this extreme example, where Foo has a more reasonable __eq__ method (which 'forgets' to check the ids) and f is f is always True.

import threading

class Foo(object):
    def __init__(self):
        self.x = 1

    def __eq__(self, other):
        return isinstance(other, Foo) and self.x == other.x

f = Foo()

class MutateThread(threading.Thread):
    def run(self):
        while True:
            f.x = 2
            f.x = 1

class CheckThread(threading.Thread):
    def run(self):
        i = 1
        while True:
            if not (f == f):
                print 'loop {0}: f != f'.format(i) 
            i += 1

MutateThread().start()
CheckThread().start()

Output:

$ python eqtest.py
loop 520617: f != f
loop 1556675: f != f
loop 1714709: f != f
loop 2436222: f != f
loop 3210760: f != f
loop 3772996: f != f
loop 5610559: f != f
loop 6065230: f != f
loop 6287500: f != f
...
timgeb
  • 76,762
  • 20
  • 123
  • 145