Introduction
When it comes to function overloading, those who have learned Java should be familiar with it. One of the most common uses is logging, where different overloaded functions are called for different parameters. So, how can we implement function overloading in Python?
Concept of Overloading
Function overloading allows multiple functions with the same name to exist within the same scope, but with different parameter lists. Although many programming languages (like Java and C++) support function overloading, Python’s design philosophy does not support this feature directly.
Without Overloading
Let's look at an example of implementing a log
function without overloading:
from attr import dataclass
@dataclass
class MyException(Exception):
msg: str
def log(message):
if isinstance(message, MyException):
print(message.msg)
elif isinstance(message, str):
print(message)
else:
print(f'invalid message:{message}')
log(MyException('my exception'))
log('str exception')
log(1111)
Output:
my exception
str exception
invalid message:1111
Without overloading, we need to write a lot of conditional code to check the type of each argument.
Using Overloading
from functools import singledispatch
from attr import dataclass
@dataclass
class MyException(Exception):
msg: str
@singledispatch
def log(message):
print(f'invalid message:{message}')
@log.register
def _(message: MyException):
print(message.msg)
@log.register
def _(message: str):
print(message)
log(MyException('my exception'))
log('str exception')
log(1111)
Output:
As shown in the code above, using overloading makes the code much cleaner, removing the need for the previous `if-else` checks. Each overloaded function outputs a log based on its corresponding type, and when calling the function, the argument type is automatically detected, which then invokes the respective overloaded function.
my exception
str exception
invalid message:1111
If a new type needs to be added, we can simply add a new overloaded function, significantly simplifying the process. For example:
from functools import singledispatch
from attr import dataclass
@dataclass
class MyException(Exception):
msg: str
@singledispatch
def log(message):
print(f'invalid message:{message}')
@log.register
def _(message: MyException):
print(message.msg)
@log.register
def _(message: str):
print(message)
@log.register
def _(message: int):
print(f'int message:{message}')
log(MyException('my exception'))
log('str exception')
log(1111)
Output:
my exception
str exception
int message:1111
Overloaded Implementation
Summary of Using functools.singledispatch
for overloading in Python based on above code
- Define a Function with the
@singledispatch
Decorator: Begin by creating a base function and apply the@singledispatch
decorator. This function will handle cases where no specific overload is registered for the type. - Add Overloaded Functions: For each type you want to handle differently, define functions with an underscore (
_
) as the function name. Since each overloaded function will have the same name, using underscores keeps the names simple and consistent. - Specify the Parameter Type for Each Overload: In each overloaded function, set the function parameter with a specific type hint. This type hint determines which function will be called based on the argument’s type.
- Test the Function: Call the function with various argument types to ensure the correct overload executes.
About @dataclass
You might notice that custom exception classes in the examples are decorated with @dataclass
. Introduced in Python 3.6, @dataclass
automatically generates special methods such as __init__()
and __repr__()
for the class, similar to Java’s @Data
annotation from Lombok.
Here's an example:
from dataclasses import dataclass
@dataclass
class InventoryItem:
"""Class for keeping track of an item in inventory."""
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
This removes the need to manually write methods like __init__()
like below as they are auto-generated by @dataclass
, simplifying the code and boosting development efficiency.
def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
More details can be found on its official documentation.
Conclusion
While Python does not support traditional function overloading, functools.singledispatch
provides a way to implement similar functionality. Keep in mind that only the type of the first parameter is considered for overloading; type changes in subsequent parameters are ignored.
Reference: https://segmentfault.com/a/1190000045454704