I've been fiddling with overloading operators in python and I've come across a question.
So I have a class that has values in which I want to use for a comparison.
class comparison:
def __init__(self, x):
self.x = x
...
def __lt__(self,other):
return self.x < other
which overloads the operator for less than. I made conditions on other such as what type it must be.
An example would be
x = comparison(2)
x < 1 #--> False
x < 3 #--> True
My question would be how could I check on the first part of the comparison?
I'm trying to limit the first part to something specific.
An example would be
7 < x # --> I don't want the first one to be an int
To do this, you can override the __gt__ method.
class comparison:
...
def __gt__(self, other):
...
7<comparison(2) will then get transformed in the call comparison(2).__gt__(7), which you can override.
Related
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 !=.
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):
...
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).)
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
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 !=.
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):
...
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).)
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
I know that comparison operators with complex numbers can't be defined in general. That is why python throws a TypeError exception when trying to use out-of-the-box complex comparison. I understand why this is the case (please don't go off topic trying to explain why two complex numbers can't be compared).
That said, in this particular case I would like to implement complex number comparison based on their magnitudes. In other words, for z1 and z2 complex values, then z1 > z2 if-and-only-if abs(z1) > abs(z2), where abs() implements complex number magnitude, as in numpy.abs().
I have come up with a solution (at least I think I have) as follows:
import numpy as np
class CustomComplex(complex):
def __lt__(self, other):
return np.abs(self) < np.abs(other)
def __le__(self, other):
return np.abs(self) <= np.abs(other)
def __eq__(self, other):
return np.abs(self) == np.abs(other)
def __ne__(self, other):
return np.abs(self) != np.abs(other)
def __gt__(self, other):
return np.abs(self) > np.abs(other)
def __ge__(self, other):
return np.abs(self) >= np.abs(other)
complex = CustomComplex
This seems to work, but I have a few questions:
Is this the way to go or is there a better alternative?
I would like my package to transparently work with the built-in complex data type as well as numpy.complex. How can this be done elegantly, without code duplication?
I'm afraid I'm going to be off topic (yes I fully read your post :-) ). Ok, Python do allow you to try to compare complex numbers that way because you can define separately all operators even if I strongly advice you not to redefine __eq__ like you did : you are saying 1 == -1 !
IMHO the problem lies there and will spring at your face at one moment (or at the face of anyone who would use your package) : when using equalities and inequalities, ordinary mortals (and most python code) do simple assumptions like -1 != 1, and (a <= b) && (b <= a) implies a == b. And you simply cannot have those 2 assumptions be true at the same time for pure mathematical reasons.
Another classic assumption is a <= b is equivalent to -b <= -a. But with you pre-order a <= b is equivalent to -a <= -b !
That being said, I'll try to answer to your 2 questions :
1: IMHO it is a harmfull way (as dicussed above), but I have no better alternative ...
2: I think a mixin could be an elegant way to limit code duplication
Code example (based on your own code, but not extensively tested):
import numpy as np
class ComplexOrder(Object):
def __lt__(self, other):
return np.absolute(self) < np.absolute(other)
# ... keep want you want (including or not eq and ne)
def __ge__(self, other):
return np.absolute(self) >= np.absolute(other)
class OrderedComplex(ComplexOrder, complex):
def __init__(self, real, imag = 0):
complex.__init__(self, real, imag)
class NPOrderedComplex64(ComplexOrder, np.complex64):
def __init__(self, real = 0):
np.complex64.__init__(self, real)
I'll forgo all the reasons why this may be a bad idea, as per your request.
Is this the way to go or is there a better alternative?
No need to go with numpy, when the normal abs accepts complex numbers and is much faster*. There's also a convenient total_ordering in functools that works well for such simple comparisons, if you want to reduce code (but this may be slower):
from functools import total_ordering
#total_ordering
class CustomComplex(complex):
def __eq__(self, other):
return abs(self) == abs(other)
def __lt__(self, other):
return abs(self) < abs(other)
(That's all the code you need.)
I would like my package to transparently work with the built-in complex data type as well as numpy.complex. How can this be done elegantly, without code duplication?
It automatically works when the right argument is a normal complex (or any) number:
>>> CustomComplex(1+7j) < 2+8j
True
But that's the best you can do, if you want to use the operators < etc. and not functions. The complex type doesn't allow you to set __lt__ and the TypeError is hardcoded.
If you want to do such comparisons on normal complex numbers, you must define and use your own comparison functions instead of the normal operators. Or just use abs(a) < abs(b) which is clear and not terribly verbose.
* Timing built-in abs vs. numpy.abs:
>>> timeit.timeit('abs(7+6j)')
0.10257387161254883
>>> timeit.timeit('np.abs(7+6j)', 'import numpy as np')
1.6638610363006592
I did not understand where is the logic in my bug, so I managed to find a minimal example. I defined one class t, and said that something happens when you use the <= operator and that a>=b must compute b<=a.
It works fine
Then I derived a subclass u from t.
When I compare two values, if they are both from t or both from u it works as expected, but if one is from class u and another from class t it fails. Why ??
class t :
def __le__(self,other) : return True
def __ge__(self,other) : return(other<=self)
class u(t) :
pass
a=t()
b=u()
#works
a<=a
a>=a
b<=b
b>=b
#works
a>=b
b<=a
#doesn't work RuntimeError: maximum recursion depth exceeded
a<=b
b>=a
EDIT : There is no problem in python 2.x (from tobias_k), but I want to use python 3.3 at least
When you do a <= b and b is an instance of a subclass of a's class, Python will first call b.__ge__('a') (and then try other methods if this call returns NotImplemented)
Here is how to implement it without infinite recursion:
>>> class t:
... def __le__(self, other):
... return True
... def __ge__(self, other):
... return NotImplemented
...
>>> class u(t):
... pass
...
In the manual is says:
in general, __lt__() and __eq__() are sufficient, if you want the
conventional meanings of the comparison operators
But I see the error:
> assert 2 < three
E TypeError: unorderable types: int() < IntVar()
when I run this test:
from unittest import TestCase
class IntVar(object):
def __init__(self, value=None):
if value is not None: value = int(value)
self.value = value
def __int__(self):
return self.value
def __lt__(self, other):
return self.value < other
def __eq__(self, other):
return self.value == other
def __hash__(self):
return hash(self.value)
class DynamicTest(TestCase):
def test_lt(self):
three = IntVar(3)
assert three < 4
assert 2 < three
assert 3 == three
I am surprised that when IntVar() is on the right, __int__() is not being called. What am I doing wrong?
Adding __gt__() fixes this, but means I don't understand what the minimal requirements are for ordering...
Thanks,
Andrew
Python 3.x will never do any type coercions for operators, so __int__() is not used in this context. The comparison
a < b
will first try to call type(a).__lt__(a, b), and if this returns NotImplemented, it will call type(b).__gt__(b, a).
The quote from the documentation is about making comparisons work for a single type, and the above explanation shows why this would be enough for a single type.
To make your type interact correctly with int, you should either implement all the comparison operator, or use the total_ordering decorator available in Python 2.7 or 3.2.