DEV Community

Aaron Maxwell
Aaron Maxwell

Posted on

Overloaded overloading

"If Python doesn't support overloading, how does '+' work which can be either addition or concatenation, correct?"

My collegue Peter asked me this once. Let me explain this question, then answer it. And it starts with the word "overloading", which has two different meanings in Python.

When you write a class in Python, you can have only one version of each method. For example, this doesn't work:

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 coords
        return math.sqrt( (self.x-x)**2 + (self.y-y)**2 )
Enter fullscreen mode Exit fullscreen mode

See the two distance() methods? See how it's defined twice?

In Python, only the second version is kept. The first one is silently overwritten and erased. If you call Point.distance() with one argument - matching the first distance(), but not the second - you will get an error.

But if you translate this class to Java, the situation is different. Java lets you define both versions of distance(), and Java will use one or the other, depending on how many arguments you pass in. This is called "method overloading".

But Python doesn't let you do that. Peter knew this, and that's why he wondered about the "+" operator - and why Python lets you use it for different operations. Doesn't that conflict with Python's "no overloading" rule?

The answer is that while Python does not support method overloading...

It DOES support operator overloading.

And that's why you can use "+" for both strings and numbers, and in fact you can make it work with your own classes as well - using "magic method" hooks. For the "+" operator, we do that by defining a method called __add__. For example, in Point:

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

If you check, you'll find both float() and str() have their own __add__ methods. And they implement addition and string concatenation, respectively.

See how this works? When we're talking about programming languages, "Overloading" refers to two different things. And Python does one of them, but not the other.

(You might say the term "overloaded" is... overloaded.)

If you liked this, you will enjoy the Powerful Python Newsletter.

P.S. Going back to the Point class - what would you do if you really need overloading for the distance() method?

Answer: There's two ways to do it. The more general method is to implement the method overloading yourself, by making distance() a dispatching method. That means its job is not to make any calculations directly; but rather determine which method should handle it, and then call it.

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)
Enter fullscreen mode Exit fullscreen mode

The second and generally better way is to use the @singledispatch decorator, in Python's functools module. That lets you cleanly register different "dispatch" methods based on the type of the first argument.

That doesn't work with the Point class here, though, because its dispatching logic is too complex. But if you ever need to do something like this, try to use @singledispatch first.

Top comments (0)