Reputation: 9701
I'm currently looking into how Python does its operator overloading. So far I find it much more appealing than in C++ especially in term of operators such as *
(or similar arithmetic operators) that has to handle an operation that can be applied both right to left (2*x) and left to right (x*2) in some way.
I have the following class as a test:
from math import sqrt
class Vector3:
def __init__(self, x,y,z):
self.x = x
self.y = y
self.z = z
def __repr__(self):
return 'Vector3(x=%d, y=%d, z=%d)' % (self.x, self.y, self.z)
def __str__(self):
return '[x: %d, y: %d, z: %d]' % (self.x, self.y, self.z)
def length(self):
return sqrt(self.x**2 + self.y**2 + self.z**2)
def __add__(self, vector):
return Vector3(self.x + vector.x, self.y + vector.y, self.z + vector.z)
def __sub__(self, vector):
return Vector3(self.x - vector.x, self.y - vector.y, self.z - vector.z)
def __mul__(self, scalar):
return Vector3(self.x * scalar, self.y * scalar, self.z * scalar)
__rmul__ = __mul__ # Right multiplication equals left multiplication (if this defers, __rmul__ has to be overwritten and defined manually)
def __eq__(self, vector):
return (self.x == vector.x and self.y == vector.y and self.z == vector.z)
def __lt__(self, vector):
return self.length() < vector.length()
@staticmethod
def compareAndPrint(vector1, vector2):
if vector1 == vector2: return 'v1 == v2 since len(v1) = %f == %f = len(v2)' % (vector1.length(), vector2.length())
elif vector1 < vector2: return 'v1 < v2 since len(v1) = %f < %f = len(v2)' % (vector1.length(), vector2.length())
elif vector1 > vector2: return 'v1 > v2 since len(v1) = %f > %f = len(v2)' % (vector1.length(), vector2.length())
v1 = Vector3(1,2,3)
v2 = Vector3(0,-1,1)
v3 = v1 + v2
v4 = v3 - v1
v5 = v1 * 2
v6 = 2 * v1
print(v1)
print(v2)
print(v3)
print(v4)
print(v5)
print(v6)
print(Vector3.compareAndPrint(v1,v2))
print(Vector3.compareAndPrint(v2,v1))
print(Vector3.compareAndPrint(v1,v1))
I'm simply adding more and more operators to my custom class and observing how those behave. You might have noticed two things (based on my question in the title):
__gt__
is not overloaded andVector3.compareAndPrint(...)
function uses the >
(greater than) operatorFor some reason I get the output I would expect as if I have overloaded >
:
[x: 1, y: 2, z: 3]
[x: 0, y: -1, z: 1]
[x: 1, y: 1, z: 4]
[x: 0, y: -1, z: 1]
[x: 2, y: 4, z: 6]
[x: 2, y: 4, z: 6]
v1 > v2 since len(v1) = 3.741657 > 1.414214 = len(v2)
v1 < v2 since len(v1) = 1.414214 < 3.741657 = len(v2)
v1 == v2 since len(v1) = 3.741657 == 3.741657 = len(v2)
Does Python handle this automatically or have I done something that I haven't noticed to make this work? The only thing that comes into my mind is that Python takes the inverse of <
and at the same time adds exclusion for the ==
since the inverse of >
is <=
and not just <
.
The same thing applies to the !=
(unequal) operator. Here I'm 99% sure that Python inverts the overloaded ==
operator.
Upvotes: 1
Views: 1025
Reputation: 281604
Most binary operators in Python can be overloaded by either operand. There's one method for the left operand to define, like __add__
for addition, and one for the right operand, like __radd__
. The only one I recall that can only be overloaded by one operand is in
, which the right side must define.
For comparisons, instead of __gt__
and __rgt__
methods, __rgt__
is just __lt__
. That means that when you do left_thing > right_thing
and left_thing
doesn't know what to do, Python tries right_thing < left_thing
. Since you've implemented __lt__
, this works.
Note that Python will not try anything involving __le__
, __ge__
, or __eq__
if __gt__
and __lt__
fail.
Upvotes: 2
Reputation: 310089
This is a bit of the python data model that many seem to not understand. To trace this through the documentation we need to start with binary arithmetic operations (__mul__
, __add__
, etc.).
We notice that there is a __mul__
and a __rmul__
method. The difference is described in docs under the latter:
These methods are called to implement the binary arithmetic operations (+, -, *, /, %, divmod(), pow(), **, <<, >>, &, ^, |) with reflected (swapped) operands. These functions are only called if the left operand does not support the corresponding operation and the operands are of different types.
Now when we look at the documentation for rich comparison methods:
There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); rather,
__lt__()
and__gt__()
are each other’s reflection
So, what's happening in your case is that since __gt__
hasn't been overloaded, python actually swaps the order of the arguments and calls __lt__
. Pretty neat.
FWIW, if you want to construct a class that is orderable with other instances of the class, the functools.total_ordering
decorator can be super helpful. You just supply __lt__
and __eq__
and the decorator supplies the rest.
Upvotes: 2