Why can't I use the method __cmp__ in Python 3 as for Python 2?
Question:
The following piece of code
class point:
def __init__(self, x, y):
self.x = x
self.y = y
def dispc(self):
return ('(' + str(self.x) + ',' + str(self.y) + ')')
def __cmp__(self, other):
return ((self.x > other.x) and (self.y > other.y))
works fine in Python 2, but in Python 3 I get an error:
>>> p=point(2,3)
>>> q=point(3,4)
>>> p>q
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: point() > point()
It only works for ==
and !=
.
Answers:
This was a major and deliberate change in Python 3. See here for more details.
- The ordering comparison operators (
<
, <=
, >=
, >
) raise a TypeError
exception when the operands don’t have a meaningful natural ordering. Thus, expressions like 1 < ''
, 0 > None
or len <= len
are no longer valid, and e.g. None < None
raises TypeError
instead of returning False
. A corollary is that sorting a heterogeneous list no longer makes sense – all the elements must be comparable to each other. Note that this does not apply to the ==
and !=
operators: objects of different incomparable types always compare unequal to each other.
builtin.sorted()
and list.sort()
no longer accept the cmp
argument providing a comparison function. Use the key
argument instead. N.B. the key
and reverse
arguments are now “keyword-only”.
- The
cmp()
function should be treated as gone, and the __cmp__()
special method is no longer supported. Use __lt__()
for sorting, __eq__()
with __hash__()
, and other rich comparisons as needed. (If you really need the cmp()
functionality, you could use the expression (a > b) - (a < b)
as the equivalent for cmp(a, b)
.)
You need to provide the rich comparison methods for ordering in Python 3, which are __lt__
, __gt__
, __le__
, __ge__
, __eq__
, and __ne__
. See also: PEP 207 — Rich Comparisons.
__cmp__
is no longer used.
More specifically, __lt__
takes self
and other
as arguments, and needs to return whether self
is less than other
. For example:
class Point(object):
...
def __lt__(self, other):
return ((self.x < other.x) and (self.y < other.y))
(This isn’t a sensible comparison implementation, but it’s hard to tell what you were going for.)
So if you have the following situation:
p1 = Point(1, 2)
p2 = Point(3, 4)
p1 < p2
This will be equivalent to:
p1.__lt__(p2)
which would return True
.
__eq__
would return True
if the points are equal and False
otherwise. The other methods work analogously.
If you use the functools.total_ordering
decorator, you only need to implement e.g. the __lt__
and __eq__
methods:
from functools import total_ordering
@total_ordering
class Point(object):
def __lt__(self, other):
...
def __eq__(self, other):
...
In Python3 the six rich comparison operators
__lt__(self, other)
__le__(self, other)
__eq__(self, other)
__ne__(self, other)
__gt__(self, other)
__ge__(self, other)
must be provided individually. This can be abbreviated by using functools.total_ordering
.
This however turns out rather unreadable and unpractical most of the time. Still you have to put similar code pieces in 2 funcs – or use a further helper func.
So mostly I prefer to use the mixin class PY3__cmp__
shown below. This reestablishes the single __cmp__
method framework, which was and is quite clear and practical in most cases. One can still override selected rich comparisons.
Your example would just become:
class point(PY3__cmp__):
...
# unchanged code
The PY3__cmp__ mixin class:
PY3 = sys.version_info[0] >= 3
if PY3:
def cmp(a, b):
return (a > b) - (a < b)
# mixin class for Python3 supporting __cmp__
class PY3__cmp__:
def __eq__(self, other):
return self.__cmp__(other) == 0
def __ne__(self, other):
return self.__cmp__(other) != 0
def __gt__(self, other):
return self.__cmp__(other) > 0
def __lt__(self, other):
return self.__cmp__(other) < 0
def __ge__(self, other):
return self.__cmp__(other) >= 0
def __le__(self, other):
return self.__cmp__(other) <= 0
else:
class PY3__cmp__:
pass
Python 2 was ahead of time, since the __cmp__
operator to implement three-way comparison is currently gaining popularity, in C++ (spaceship operator) and elsewhere. See Universal Comparison Operator In Python?.
I like the mixin class from kxr in principle, but for my taste a decorator would be more fitting
The PY3__cmp__
decorator is modeled after @krx’s answer and the functools.total_ordering
decorator.
def cmp(a, b):
return (a > b) - (a < b)
_convert = {
'__eq__': lambda self, other: self.__cmp__(other) == 0,
'__ne__': lambda self, other: self.__cmp__(other) != 0,
'__lt__': lambda self, other: self.__cmp__(other) < 0,
'__le__': lambda self, other: self.__cmp__(other) <= 0,
'__gt__': lambda self, other: self.__cmp__(other) > 0,
'__ge__': lambda self, other: self.__cmp__(other) >= 0,
}
def PY3__cmp__(cls):
"""Class decorator that fills in missing ordering methods when
Python2-style `__cmp__(self, other)` method is provided."""
if not hasattr(cls, '__cmp__'):
raise ValueError('must define the __cmp__ Python2-style method')
if sys.version_info < (3, 0, 0):
return cls
for op, opfunc in _convert.items():
# Overwrite the `raise NotImplemented` comparisons inherited from object
if getattr(cls, op, None) is getattr(object, op, None):
setattr(cls, op, opfunc)
return cls
(And yes, my codebase still holds onto some pre PEP-8 ideas about indentation.)
The following piece of code
class point:
def __init__(self, x, y):
self.x = x
self.y = y
def dispc(self):
return ('(' + str(self.x) + ',' + str(self.y) + ')')
def __cmp__(self, other):
return ((self.x > other.x) and (self.y > other.y))
works fine in Python 2, but in Python 3 I get an error:
>>> p=point(2,3)
>>> q=point(3,4)
>>> p>q
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: point() > point()
It only works for ==
and !=
.
This was a major and deliberate change in Python 3. See here for more details.
- The ordering comparison operators (
<
,<=
,>=
,>
) raise aTypeError
exception when the operands don’t have a meaningful natural ordering. Thus, expressions like1 < ''
,0 > None
orlen <= len
are no longer valid, and e.g.None < None
raisesTypeError
instead of returningFalse
. A corollary is that sorting a heterogeneous list no longer makes sense – all the elements must be comparable to each other. Note that this does not apply to the==
and!=
operators: objects of different incomparable types always compare unequal to each other.builtin.sorted()
andlist.sort()
no longer accept thecmp
argument providing a comparison function. Use thekey
argument instead. N.B. thekey
andreverse
arguments are now “keyword-only”.- The
cmp()
function should be treated as gone, and the__cmp__()
special method is no longer supported. Use__lt__()
for sorting,__eq__()
with__hash__()
, and other rich comparisons as needed. (If you really need thecmp()
functionality, you could use the expression(a > b) - (a < b)
as the equivalent forcmp(a, b)
.)
You need to provide the rich comparison methods for ordering in Python 3, which are __lt__
, __gt__
, __le__
, __ge__
, __eq__
, and __ne__
. See also: PEP 207 — Rich Comparisons.
__cmp__
is no longer used.
More specifically, __lt__
takes self
and other
as arguments, and needs to return whether self
is less than other
. For example:
class Point(object):
...
def __lt__(self, other):
return ((self.x < other.x) and (self.y < other.y))
(This isn’t a sensible comparison implementation, but it’s hard to tell what you were going for.)
So if you have the following situation:
p1 = Point(1, 2)
p2 = Point(3, 4)
p1 < p2
This will be equivalent to:
p1.__lt__(p2)
which would return True
.
__eq__
would return True
if the points are equal and False
otherwise. The other methods work analogously.
If you use the functools.total_ordering
decorator, you only need to implement e.g. the __lt__
and __eq__
methods:
from functools import total_ordering
@total_ordering
class Point(object):
def __lt__(self, other):
...
def __eq__(self, other):
...
In Python3 the six rich comparison operators
__lt__(self, other)
__le__(self, other)
__eq__(self, other)
__ne__(self, other)
__gt__(self, other)
__ge__(self, other)
must be provided individually. This can be abbreviated by using functools.total_ordering
.
This however turns out rather unreadable and unpractical most of the time. Still you have to put similar code pieces in 2 funcs – or use a further helper func.
So mostly I prefer to use the mixin class PY3__cmp__
shown below. This reestablishes the single __cmp__
method framework, which was and is quite clear and practical in most cases. One can still override selected rich comparisons.
Your example would just become:
class point(PY3__cmp__):
...
# unchanged code
The PY3__cmp__ mixin class:
PY3 = sys.version_info[0] >= 3
if PY3:
def cmp(a, b):
return (a > b) - (a < b)
# mixin class for Python3 supporting __cmp__
class PY3__cmp__:
def __eq__(self, other):
return self.__cmp__(other) == 0
def __ne__(self, other):
return self.__cmp__(other) != 0
def __gt__(self, other):
return self.__cmp__(other) > 0
def __lt__(self, other):
return self.__cmp__(other) < 0
def __ge__(self, other):
return self.__cmp__(other) >= 0
def __le__(self, other):
return self.__cmp__(other) <= 0
else:
class PY3__cmp__:
pass
Python 2 was ahead of time, since the __cmp__
operator to implement three-way comparison is currently gaining popularity, in C++ (spaceship operator) and elsewhere. See Universal Comparison Operator In Python?.
I like the mixin class from kxr in principle, but for my taste a decorator would be more fitting
The PY3__cmp__
decorator is modeled after @krx’s answer and the functools.total_ordering
decorator.
def cmp(a, b):
return (a > b) - (a < b)
_convert = {
'__eq__': lambda self, other: self.__cmp__(other) == 0,
'__ne__': lambda self, other: self.__cmp__(other) != 0,
'__lt__': lambda self, other: self.__cmp__(other) < 0,
'__le__': lambda self, other: self.__cmp__(other) <= 0,
'__gt__': lambda self, other: self.__cmp__(other) > 0,
'__ge__': lambda self, other: self.__cmp__(other) >= 0,
}
def PY3__cmp__(cls):
"""Class decorator that fills in missing ordering methods when
Python2-style `__cmp__(self, other)` method is provided."""
if not hasattr(cls, '__cmp__'):
raise ValueError('must define the __cmp__ Python2-style method')
if sys.version_info < (3, 0, 0):
return cls
for op, opfunc in _convert.items():
# Overwrite the `raise NotImplemented` comparisons inherited from object
if getattr(cls, op, None) is getattr(object, op, None):
setattr(cls, op, opfunc)
return cls
(And yes, my codebase still holds onto some pre PEP-8 ideas about indentation.)