Method overloading in Python
Python does not support method/functions overloading as other languages do. For example in C++ we can define three different methods that will support different types of arguments
1 | class printData { |
But in Python only the last declared method will be used.
1 | class PrintData: |
As usually in Python world there is a workaround or some way to implement any concept or idea. First approach is a straightforward simple implementation of writing logical conditions within method/function itself. Thanks to keywords-only arguments we can force to explicitly provide parameters names for our arguments therefore emulating calls to functions with different signatures. You can also do the same manually checking **kwargs
for the presence of desired arguments (in case you use Python 2).
1 | class ResolverManual(object): |
Now we just need to invoke multiply
method with desired set of arguments
1 | r1 = ResolverManual() |
In each branch of our logical if conditions we can provide a call to any internal method we want but essentially we will get something like this at the end
1 | Method called without arguments |
With a help of functools
built-in module we can achieve almost the same behaviour. It contains singledispatch decorator allowing to call appropriate function based on the type of the first argument (therefore single) . You can define one generic function and register as many other function as you want with different types of the first argument and it will automatically dispatch a call to matched one.
1 | from functools import singledispatch |
Optionally you can decorate each of your functions with function.register(type) decorator (@multiply2.register(complex)
) inplace instead of registering them later. To fully emulate overloading with dispatching based on types we can use multipledispatch library which does the same but generalizes for any number of parameters (note, it’s not the part of standard library).
1 | print(multiply2(3, 4)) |
Invocation above will give us following output
1 | Generic function for 3, 4 |
In case you want to use single dispatch behaviour on the methods of your class you need to slightly modify the decorator. The problem is that it decides which registered function to call based on the type of a first argument and in case of a method our first argument will be always self
instance.
1 | from functools import singledispatch, update_wrapper |
So our new methdispatch
will substitute a call with actual first argument instead of a self and then just elevate the rest of the function. We can use this decorator as showed below
1 | class ResolverMethDispatch(object): |
The next step is to instantiate our class and use the method as usually
1 | r3 = ResolverMethDispatch() |
You will not be surprised at this point because an output satisfies our expectations
1 | Generic method for [3], [4] |
There are two other very similar options based on classes that I like the most when trying to implement function overloading. The idea is to pass all the logic to either __call__
or __new__
magic method and implement all the logic within them. And the best thing about it is using as a regular function call. Lets see an example
1 | class _multiply4(object): |
We define 4 different functions (1 default generic and 3 type specific) and make our decision in a __call___
method. Then we instantiate our class to get a target callable that we can use in our code
1 | print(multiply4()) |
As expected different functions will be called for different arguments based on our conditions
1 | Generic method for arguments: () |
The same concept but with a __new__
will look like this
1 | class multiply5(object): |
This way we don’t need to instantiate a class and the usage remain the same
1 | print(multiply5(7, 10)) |
will give us predictable result
1 | Multiplying two ints: 7x10 |
Final thoughts
I do not encourage to use this examples in a real code (besides singledispatch one). This demonstrates great flexibility of a language and can be a guide to some interesting concepts within it but as Zen of Python states
There should be one - and preferably only one - obvious way to do it.
Basically it means that in case you need to implement anything “hacky” there is something missing in the design of your program/algorithm and it should be revised. Tools (as well as algorithms/data structures) matters so choose them wisely but do not stop to obtain new knowledge implementing such a tricky things.