[go: up one dir, main page]

Function overloading PREMIUM

Series: Functions
Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
4 min. read 3 min. video Python 3.10—3.14
Python Morsels
Watch as video
02:54

Python doesn't have function overloading.

Instead of overloading we have overwriting

What if we have a function that we'd like to use with different arguments depending on the situation?

For example, we'd like to make a greet function that can be called with nothing or can be called with a single argument:

>>> greet()
Hello world
>>> greet("Trey")
Hello Trey

In some programming languages, we could define two functions with the same name to overload that function with two different behaviors, depending on the arguments that were passed to it.

def greet():
    print("Hello world")


def greet(name):
    print("Hello", name)

This doesn't work in Python:

>>> greet("Trey")
Hello Trey
>>> greet()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: greet() missing 1 required positional argument: 'name'

In Python, when the second function was defined, it completely overwrote the first function. This happens because defining a function in Python really defines a function object, and then points a variable name to that function object (remember everything is an object in Python).

So we're seeing the same behavior as if we had redefined a variable:

>>> x = 0
>>> x
0
>>> x
0
>>> x = 4
>>> x
4

How could we achieve something like function overloading? Well, it depends on what we're trying to accomplish.

Using default values instead

If we want a function that can accept different numbers of arguments, we could make some arguments optional by declaring default values when we define our function:

def greet(name="world"):
    print("Hello", name)

Default values are very widely used in the Python world:

>>> greet("Trey")
Hello Trey
>>> greet()
Hello world

Defining a factory function

Factory functions are another alternative to function overloading.

Class methods are a pretty common way to make a factory function in Python:

import json


class User:
    def __init__(self, id, name, email):
        self.id, self.name, self.email = id, name, email

    @classmethod
    def from_json(cls, json_string):
        data = json.loads(json_string)
        return cls(data["id"], data["name"], data["email"])

    def __repr__(self):
        return f"User({self.id!r}, {self.name!r}, {self.email!r})"

Class methods often act as a sort of alternative class initializer.

We could make a User object by calling this User class (as we normally would):

>>> user = User(4, "Trey", "[email protected]")
>>> user
User(4, 'Trey', '[email protected]')

But if we have some JSON data that represents a User object, we could use our from_json class method to turn that JSON data into a User object:

>>> user_json = '{"id": 123, "name": "Trey", "email": "[email protected]"}'
>>> user = User.from_json(user_json)
>>> user
User(123, 'Trey', '[email protected]')

Class methods are a common way to make factory functions, but they're not the only way.

Any function that simply wraps around another function or class can be thought of as a factory function.

For example, this user_from_json function could be thought of as a factory function:

import json


class User:
    def __init__(self, id, name, email):
        self.id, self.name, self.email = id, name, email

    def __repr__(self):
        return f"User({self.id!r}, {self.name!r}, {self.email!r})"


def user_from_json(json_string):
    data = json.loads(json_string)
    return User(data['id'], data['name'], data['email'])

In fact, I would actually prefer this factory function over a class method because decoding JSON data doesn't seem like the responsibility of a class that manages users.

Creating dispatch functions

If you really wanted to call different functions based on the arguments provided to your function, there are some options available in Python.

Python's functools module has a singledispatch decorator for making a function that accepts a single argument and which calls different helper functions based on the type of that argument.

Here's a stringify function that uses this singledispach decorator:

from functools import singledispatch

@singledispatch
def stringify(data):
    raise NotImplementedError("Data type not supported for stringification")

@stringify.register(list)
def _stringify_list(data):
    return "\n".join([
        str(row)
        for row in data
    ])

@stringify.register(dict)
def _stringify_dict(data):
    return "\n".join([f"{key}: {value}" for key, value in data.items()])

When we pass a list to this stringify function, it calls the _stringify_list function:

>>> print(stringify([2, 1, 3, 4]))
2
1
3
4

But if we pass in a dictionary, it calls the _stringify_dict function:

>>> print(stringify({"cats": 1, "dogs": 3, "rabbits": 9}))
cats: 1
dogs: 3
rabbits: 9

And if we passed in a different type, like a set, it calls the first function, which raises an exception:

>>> stringify({2, 1, 3})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.11/functools.py", line 909, in
 wrapper
    return dispatch(args[0].__class__)(*args, **kw)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/trey/stringify.py", line 5, in stringify
    raise NotImplementedError("Data type not supported for stringification")
NotImplementedError: Data type not supported for stringification

There are also third-party libraries that allow for multiple dispatch, which does something similar, but with multiple function arguments instead of just one. The multidispatch and multimethod libraries are two examples.

Keep your overloading alternative simple

Python doesn't have function overloading though you can achieve something similar with third-party libraries.

When you're seeking a function overloading alternative, it's usually best to keep it simple. Typically, it's best to rely on default argument values or factory functions instead.

Now it's your turn! 🚀

We don't learn by reading or watching. We learn by doing. That means writing Python code.

Practice this topic by working on these related Python exercises.

Series: Functions

Python, like many programming languages, has functions. A function is a block of code you can call to run that code.

Python's functions have a lot of "wait I didn't know that" features. Functions can define default argument values, functions can be called with keyword arguments, and functions can be written to accept any number of arguments.

To track your progress on this Python Morsels topic trail, sign in or sign up.

0%
Python Morsels
Watch as video
02:54
This is a free preview of a premium screencast. You have 2 previews remaining.