Overloaded overloading
A good question from one of y'all good readers:
"If Python doesn't support overloading, how does '+' work which can be either addition or concatenation, correct?"
This is a fun question, because the word "overloading" is used for two different things.
(You might say its definition is... overloaded.)
It's true that Python does not support method overloading. A class can have only one method of a given name. For example:
- import math
- class Point:
- def __init__(self, x, y):
- self.x, self.y = x, y
- def distance(self, other_point):
- 'Distance from this point to another point'
- return math.sqrt( (self.x-other_point.x)**2 + (self.y-other_point.y)**2 )
- def distance(self, x, y):
- 'Distance from this point to other x-y coordinates'
- return math.sqrt( (self.x-x)**2 + (self.y-y)**2 )
See the two distance()
methods? In Python, only the
second version is kept. If you call Point.distance()
with one argument, you'll get an
error.
We say Python does "single dispatch". Because it doesn't support method overloading - also called "multiple dispatch".
(What if you really need both distance()
methods? See the P.S. below.)
Now... how does "+" work for both strings and numbers? Because Python DOES support operator overloading. And that's different from method overloading.
Python lets you overload its built-in operators, using its system of "magic method" hooks. For example:
- class Point:
- def __init__(self, x, y):
- self.x, self.y = x, y
- # ...
- def __add__(self, other):
- return Point(self.x + other.x, self.y + other.y)
This "__add__" method can be defined on any of your classes. When you do, it lets you add instances of them together:
- >>> p = Point(1,1) + Point(2,2)
- >>> print(p.x, p.y)
- 3 3
If you check, you'll find both float() and str() have their own __add__ methods. And they implement addition and string concatenation, respectively.
So that's the answer. Python supports one kind of overloading, but not the other.
P.S. Going back to the Point class - what would you do if you
really need overloading for the distance()
method?
Answer: You implement the method overloading yourself, by making
distance()
a dispatch method. Typically you do
this with generic argument names, and checking types with
isinstance()
, or the presence/absence of certain arguments:
- import math
- class Point:
- def __init__(self, x, y):
- self.x, self.y = x, y
- def distance(self, first, second=None):
- if isinstance(first, Point):
- # distance to other Point
- assert second is None
- return self.distance_to_other(first)
- elif isinstance(first, tuple):
- # distance to (x, y) tuple
- assert second is None
- return self.distance_to_pair(first)
- else:
- # distance to coordinates
- assert second is not None
- return self.distance_to_coordinates(first, second)
- def distance_to_coordinates(self, x, y):
- 'Distance from this point to other x-y coordinates'
- return math.sqrt( (self.x-x)**2 + (self.y-y)**2 )
- def distance_to_other(self, other_point):
- 'Distance from this point to another point'
- return self.distance_to_coordinates(other_point.x, other_point.y)
- def distance_to_pair(self, pair):
- 'Distance from this point to (x, y) tuple'
- x, y = pair
- return self.distance_to_coordinates(x, y)