[Python-Dev] Exceptions in comparison operators

Guido van Rossum guido at python.org
Tue Mar 13 17:27:51 CET 2012


Mark, did you do anything with my reply?

On Mon, Mar 5, 2012 at 10:41 AM, Guido van Rossum <guido at python.org> wrote:
> On Mon, Mar 5, 2012 at 4:41 AM, Mark Shannon <mark at hotpy.org> wrote:
>> Comparing two objects (of the same type for simplicity)
>> involves a three stage lookup:
>> The class has the operator C.__eq__
>> It can be applied to operator (descriptor protocol): C().__eq__
>> and it produces a result: C().__eq__(C())
>>
>> Exceptions can be raised in all 3 phases,
>> but an exception in the first phase is not really an error,
>> its just says the operation is not supported.
>> E.g.
>>
>> class C: pass
>>
>> C() == C() is False, rather than raising an Exception.
>>
>> If an exception is raised in the 3rd stage, then it is propogated,
>> as follows:
>>
>> class C:
>>   def __eq__(self, other):
>>       raise Exception("I'm incomparable")
>>
>> C() == C()  raises an exception
>>
>> However, if an exception is raised in the second phase (descriptor)
>> then it is silenced:
>>
>> def no_eq(self):
>>    raise Exception("I'm incomparable")
>>
>> class C:
>>   __eq__ = property(no_eq)
>>
>> C() == C() is False.
>>
>> But should it raise an exception?
>>
>> The behaviour for arithmetic is different.
>>
>> def no_add(self):
>>    raise Exception("I don't add up")
>>
>> class C:
>>   __add__ = property(no_add)
>>
>> C() + C() raises an exception.
>>
>> So what is the "correct" behaviour?
>> It is my opinion that comparisons should behave like arithmetic
>> and raise an exception.
>
> I think you're probably right. This is one of those edge cases that
> are so rare (and always considered a bug in the user code) that we
> didn't define carefully what should happen. There are probably some
> implementation-specific reasons why it was done this way (comparisons
> use a very different code path from regular binary operators) but that
> doesn't sound like a very good reason.
>
> OTOH there *is* a difference: as you say, C() == C() is False when the
> class doesn't define __eq__, whereas C() + C() raises an exception if
> it doesn't define __add__. Still, this is more likely to have favored
> the wrong outcome for (2) by accident than by design.
>
> You'll have to dig through the CPython implementation and find out
> exactly what code needs to be changed before I could be sure though --
> sometimes seeing the code jogs my memory.
>
> But I think of x==y as roughly equivalent to
>
> r = NotImplemented
> if hasattr(x, '__eq__'):
>  r = x.__eq__(y)
> if r is NotImplemented and hasattr(y, '__eq__'):
>  r = y.__eq__(x)
> if r is NotImplemented:
>  r = False
>
> which would certainly suggest that (2) should raise an exception. A
> possibility is that the code looking for the __eq__ attribute
> suppresses *all* exceptions instead of just AttributeError. If you
> change no_eq() to return 42, for example, the comparison raises the
> much more reasonable TypeError: 'int' object is not callable.
>
> --
> --Guido van Rossum (python.org/~guido)



-- 
--Guido van Rossum (python.org/~guido)


More information about the Python-Dev mailing list