You have a Python class which needs an equals test. Python should use duck-typing but is it (better/more accurate) to include or exclude an isinstance test in the eq function? For example:
class Trout(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
return isinstance(other, Trout) and self.value == other.value
Using isinstance in __eq__
methods is pretty common. The reason for this is that if the __eq__
method fails, it can fallback on an __eq__
method from another object. Most normal methods are called explicitly, but __eq__
is called implicitly, so it requires look-before-you-leap more frequently.
EDIT (thanks for the reminder, Sven Marnach):
To make it fallback, you can return the NotImplemented singleton, as in this example:
class Trout(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
if isinstance(other, Trout):
return self.value == other.value
else:
return NotImplemented
Suppose a RainbowTrout
knows how to compare itself to a Trout
or to another RainbowTrout
, but a Trout
only knows how to compare itself to a Trout
. In this example, if you test mytrout == myrainbowtrout
, Python will first call mytrout.__eq__(myrainbowtrout)
, notice that it fails, and then call myrainbowtrout.__eq__(mytrout)
, which succeeds.
Using isintsance()
is usually fine in __eq__()
methods. You shouldn't return False
immediately if the isinstance()
check fails, though -- it is better to return NotImplemented
to give other.__eq__()
a chance of being executed:
def __eq__(self, other):
if isinstance(other, Trout):
return self.x == other.x
return NotImplemented
This will become particularly important in class hierarchies where more than one class defines __eq__()
:
class A(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
if isinstance(other, A):
return self.x == other.x
return NotImplemented
class B(A):
def __init__(self, x, y):
A.__init__(self, x)
self.y = y
def __eq__(self, other):
if isinstance(other, B):
return self.x, self.y == other.x, other.y
return NotImplemented
If you would return False
immediately, as you did in your original code, you would lose symmetry between A(3) == B(3, 4)
and B(3, 4) == A(3)
.
The "duck-typing" principle is that you don't care what other
is, as long as it has a value
attribute. So unless your attributes share names with conflicting semantics, I'd suggest doing it like this:
def __eq__(self, other):
try:
return self.value == other.value
except AttributeError:
return False # or whatever
(Alternately you could test whether other
has a value
attribute, but "it's easier to ask forgiveness than to get permission")