

Python Programming
While it does support method overriding, unlike many object-oriented languages, Python does not directly support method overloading. To a C++ or Delphi programmer this realisation can come as a bit of a surprise. On the face of it, it may seem a strange omission, but when you think about it a bit more you can start to realise why. In this article we will explore this Python restriction and consider how you can achieve something similar, and why you don't need overloading in Python.
Function overloading is the facility whereby you can declare multiple methods of the same name in a class or at the global level as functions that are distinguished from one another by their parameter types, order and count. Function overriding, by contrast is where you declare a method (usually in a derived class) that replaces (or overrides) a method of the same name in an inherited class. Overriding is supported but means that the redeclared function or method completely replaces the original method, whereas overloaded methods exist side by side with the original method: either can be called depending on the parameter list used by the caller. In, python with its dynamic typing, variable parameter lists and optional parameters, the ability to reliably distinguish two functions of the same name so they can overload each other is contra-indicated, hence it is not directly supported.
While function overloading per-se is not supported there are a number of ways to achieve a similar capability. Key to these approaches is the fact that while Python lacks static typing (declaring a parameter of a particular type, and no - type hints do not count in this regard as they are not enforceable), it does support type testing and parameter count checking so dynamic typing does not mean there is no typing at all. Let us consider a few approaches.
This approach allows you to provide different initialization options by using default values for parameters. A default argument is one where the parameter is declared with a value that is used if the function or method is called without that positional or named parameter:
class MyClass:
def __init__(self, a=None, b=None):
if a is not None and b is not None:
print(f"Initialized with a={a} and b={b}")
elif a is not None:
print(f"Initialized with a={a}")
else:
print("Initialized with default values")
# Usage examples:
obj1 = MyClass(a=10, b=20) # Initialized with a=10 and b=20
obj2 = MyClass(a=5) # Initialized with a=5
obj3 = MyClass() # Initialized with default values
This approach is more flexible and allows you to process different sets of arguments dynamically:
class MyClass:
def __init__(self, *args, **kwargs):
if len(args) == 2: # Check if two positional arguments were provided
print(f"Initialized with args: {args[0]} and {args[1]}")
elif 'x' in kwargs and 'y' in kwargs:
print(f"Initialized with x={kwargs['x']} and y={kwargs['y']}")
else:
print("Initialized with default or unexpected arguments")
# Usage examples:
obj1 = MyClass(1, 2) # Initialized with args: 1 and 2
obj2 = MyClass(x=10, y=20) # Initialized with x=10 and y=20
obj3 = MyClass() # Initialized with default or unexpected arguments
Another way to simulate "overloading" is by using class methods to provide alternate constructors. This keeps the primary __init__ simple while still offering multiple ways to initialize the class.
class MyClass:
def __init__(self, value):
self.value = value
@classmethod
def from_two_values(cls, a, b):
return cls(a + b) # Example: combine two values during initialization
# Usage examples:
obj1 = MyClass(10) # Initialize directly
obj2 = MyClass.from_two_values(5, 15) # Initialize using alternate constructor
print(obj1.value) # Output: 10
print(obj2.value) # Output: 20
You can use *args to accept a variable number of positional arguments or **kwargs for keyword arguments. Example with *args:
#Example with *args
class MyClass:
def add(self, *args):
return sum(args)
# Usage:
obj = MyClass()
print(obj.add(5)) # Output: 5
print(obj.add(5, 10, 15)) # Output: 30
#Example with **kwargs:
class MyClass:
def add(self, **kwargs):
return sum(kwargs.values())
# Usage:
obj = MyClass()
print(obj.add(a=5, b=10)) # Output: 15
You can manually inspect the arguments passed to the method to implement behavior based on their number or type.
#Checking the number of args
class MyClass:
def add(self, *args):
if len(args) == 2: # Two arguments
return args[0] + args[1]
elif len(args) == 3: # Three arguments
return args[0] + args[1] + args[2]
else:
raise TypeError("add() requires 2 or 3 arguments")
# Usage:
obj = MyClass()
print(obj.add(5, 10)) # Output: 15
print(obj.add(5, 10, 15)) # Output: 30
Python provides several ways to check types, which allow us to test the type of a parameter provided to the function and perform appropriate actions based thereon. Of these type(), isinstance(), issubclass(), callable() and a range of "behaviours" checks available in collections.abc are perhaps the most useful for this purpose. (See our article on type and behaviours checking in python - here). For example:
We can check the type directly using type() and whether something is a member of a class (and inherited class) or has a behavior (like iterable) using isinstance():
from collections.abc import Iterable
if type(x) is int:
print("x is an integer")
if isinstance(x, (int, float)):
print("x is a number")
if isinstance(x, Iterable)
print("x is iterable")
Although Python doesn't natively support method overloading, we can use third-party libraries like singledispatch from the functools module to achieve this. Example with @singledispatch:
from functools import singledispatchmethod
class MyClass:
@singledispatchmethod
def add(self, a):
raise NotImplementedError("Unsupported type")
@add.register
def _(self, a: int, b: int):
return a + b
@add.register
def _(self, a: list):
return sum(a)
# Usage:
obj = MyClass()
print(obj.add(5, 10)) # Output: 15
print(obj.add([5, 10, 15])) # Output: 30
While python does not support method or function overloading directly, there are a number of approaches to making a single declaration of a function perform differing actions based on its parameter count and types.