6

I have the following code:

a = str('5')
b = int(5)
a == b
# False

But if I make a subclass of int, and reimplement __cmp__:

class A(int):
    def __cmp__(self, other):
        return super(A, self).__cmp__(other)
a = str('5')
b = A(5)
a == b
# TypeError: A.__cmp__(x,y) requires y to be a 'A', not a 'str'

Why are these two different? Is the python runtime catching the TypeError thrown by int.__cmp__(), and interpreting that as a False value? Can someone point me to the bit in the 2.x cpython source that shows how this is working?

Chris
  • 1,657
  • 1
  • 13
  • 20
  • On a side note: you know that `__cmp__` was deprecated ages ago? You should implement rich-comparison functions. – Bakuriu Sep 17 '12 at 19:00
  • Yes, this came up when I was trying to figure out if I should raise an exception or return NotImplemented in an implementation of __eq__. I wanted to see what the builtin Python classes did, and found this example that seemed inconsistent. – Chris Sep 17 '12 at 19:08

3 Answers3

5

The documentation isn't completely explicit on this point, but see here:

If both are numbers, they are converted to a common type. Otherwise, objects of different types always compare unequal, and are ordered consistently but arbitrarily. You can control comparison behavior of objects of non-built-in types by defining a __cmp__ method or rich comparison methods like __gt__, described in section Special method names.

This (particularly the implicit contrast between "objects of different types" and "objects of non-built-in types") suggests that the normal process of actually calling comparison methods is skipped for built-in types: if you try to compare objects of two dfferent (and non-numeric) built-in types, it just short-circuits to an automatic False.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
3

A comparison decision tree for a == b looks something like:

  • python calls a.__cmp__(b)
    • a checks that b is an appropriate type
    • if b is an appropriate type, return -1, 0, or +1
    • if b is not, return NotImplented
  • if -1, 0, or +1 returned, python is done; otherwise
  • if NotImplented returned, try
  • b.__cmp__(a)
    • b checks that a is an appropriate type
    • if a is an appropriate type, return -1, 0, or +1
    • if a is not, return NotImplemented
  • if -1, 0, or +1 returned, python is done; otherwise
  • if NotImplented returned again, the answer is False

Not an exact answer, but hopefully it helps.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
-2

If I understood your problem right, you need something like:

>>> class A(int):
...     def __cmp__(self, other):
...         return super(A, self).__cmp__(A(other)) # <--- A(other) instead of other
... 
>>> a = str('5')
>>> b = A(5)
>>> a == b
True

Updated

Regarding to 2.x cpython source, you can find reason for this result in typeobject.c in function wrap_cmpfunc which actually checks two things: given compare function is a func and other is subtype for self.

if (Py_TYPE(other)->tp_compare != func &&
    !PyType_IsSubtype(Py_TYPE(other), Py_TYPE(self))) {
// ....
}
Alexey Kachayev
  • 6,106
  • 27
  • 24
  • 3
    I don't think this addresses the question at all. I think OP recognizes that he can coerce the type of other to make the comparison work. The question is *why does he have to?* Why doesn't `__cmp__` see that the types aren't the same and return `False` right away? – mgilson Sep 17 '12 at 18:25
  • 4
    The question is why are there two different results, not how to make `str` and `int` compare. – Ethan Furman Sep 17 '12 at 18:25
  • The subtype check is done to allow subclasses to completely reimplement operators, otherwise doing `A == B` and `B == A` could yield to different results if `B` is a subclass of `A`.(see [this](http://stackoverflow.com/questions/2281222/why-when-in-python-does-x-y-call-y-eq-x) question and answer. – Bakuriu Sep 17 '12 at 18:58
  • I think it's a combination of that `wrap_cmpfunc` in `typeobjec.c`, and `do_cmp` and the `*_3way_compare` functions in object.c. Based on what I see when actually running the code, `do_cmp` runs first, and can short-circuit before calling `int.__cmp__()`, aka `wrap_cmpfunc`. Which is why calling `int.__cmp__()` directly raises an exception, but the `==` operator does not. – Chris Sep 17 '12 at 19:12