I was refactoring some code today, and couldn’t figure out why my simple textbook change was making code break, until I relearned a subtlety of Python.

The code looked like this:

def __eq__ ( self , other ):

# Compare the parts, but be clever when comparing the specs.

if self . __class__ != other . __class__ :

return False

return self . spec . make_div_only () == other . spec . make_div_only () and self . chunks == other . chunks



I didn’t like that long last line, so I made the simple change to this:

def __eq__ ( self , other ):

# Compare the parts, but be clever when comparing the specs.

if self . __class__ != other . __class__ :

return False

if self . spec . make_div_only () != other . spec . make_div_only ():

return False

return self . chunks == other . chunks



and ran my unit tests, and they failed. What!? How could doing a simple boolean refactoring cause breakage? I scratched my head, re-read the lines, questioned my understanding of the short-circuit nature of the and operator, and so on. The usual “am I going crazy?” debugging techniques.

Undoing the refactor made the tests work again, I changed it again to the shorter lines, and the tests failed again. Adding some print statements to see the actual values being compared, I realized that the result of make_div_only is an object (of class Spec), and that object defines its own Spec.__eq__ method to define the meaning of the == operator for its instances.

Then it hit me: the class doesn’t define a __ne__ method. My refactoring changed the operator from == to !=, the first was overridden to provide custom semantics, but the second was not, so simple object inequality was being checked, so make_div_only inequality test was always true, and the method incorrectly always returned False.

The Python docs are clear on this point:

There are no implied relationships among the comparison operators. The truth of x==y does not imply that x!=y is false. Accordingly, when defining __eq__(), one should also define __ne__() so that the operators will behave as expected.

Adding a __ne__ method to my Spec class made everything work properly:

def __ne__ ( self , other ):

return not self . __eq__ ( other )

