Search code examples
pythoncomparison-operators

Inconsistent object comparison behaviour when inheriting from dict


This problem arose from a failing test that refused to fail locally, and would only fail on our CI server.

It turned out some rather dodgy object comparison was being unintentionally done.

I'm now rather curious as to why the behavior is so different between two installations of the same Python version (2.7.9).

This test case could probably be simplified further, but this is what I've got:

import operator


class Thing(dict):
    def __int__(self, number):
        return self['number']

    def __gt__(self, other):
        return self['number'] > other

thing = Thing({'number': 2})

for o in [
        operator.lt,
        operator.le,
        operator.eq,
        operator.ne,
        operator.ge,
        operator.gt]:
    print o
    print o(0.01, thing)
    print o(thing, 0.01)

And the result of running it locally is:

<built-in function lt>
True
False
<built-in function le>
True
False
<built-in function eq>
False
False
<built-in function ne>
True
True
<built-in function ge>
False
True
<built-in function gt>
False
True

But on the Travis CI server it is:

<built-in function lt>
True
True
<built-in function le>
False
True
<built-in function eq>
False
False
<built-in function ne>
True
True
<built-in function ge>
True
False
<built-in function gt>
True
True

What kind of comparison behavior is Python falling back to, and why would it exhibit such different behavior on two installations of the same version?

My initial thought was some kind of id based comparison, but from looking at the value of the id, they don't correlate at all with the results of the comparisons.

Update:

This differing behavior only happens when the class inherits from dict. When it inherits from object, the comparisons behave the same on both installations, and give the same results as the local result above.

Update 2:

I've just found that I can simplify the test case even further with just the __int__ and the __gt__ methods, but if I remove either of those methods then the strange behavior disappears.


Solution

  • After further investigation, and based on @BrenBarn's fantastic answer I've found the root of the strange behaviour.

    The last resort step of the "undefined" comparison is to compare the memory location of the object types. After comparing id(type(thing)) and id(type(0.02)) locally and on the CI server, I see that Thing's id is always higher locally, and always lower on the CI server!