Function Overloading in Python

PAWAN PUNDIR
Practo Engineering
Published in
4 min readSep 26, 2018

Recently in one of the conversations at Practo, I found some guys complaining that its so bad that we do not have function overloading in Python. This is due to numerous articles and blogs around which claim that Python does not support function overloading. This will be evident the moment you search for function overloading with python.

This was somewhat similar to developers complaining about slower execution speed of Python (addressed in https://medium.com/practo-engineering/execute-python-code-at-the-speed-of-c-extending-python-93e081b53f04) and some others complaining that Python and other interpreted languages are not truly “concurrent” ( addressed in https://medium.com/practo-engineering/threading-vs-multiprocessing-in-python-7b57f224eadb).

Because of Python being open source and ultra flexible , there is almost nothing which you can achieve with other languages and not with Python.

Below snippet will highlight the issue of function overloading in Python.

So when you define two functions with same names, the last one only matters ( area(size) in above). Thus area(3) works but area(4,5) gives

TypeError: area() takes exactly 1 argument (2 given)

You can always print locals() symbol table which is a data structure maintained by a compiler containing all necessary information about the program. This stores like below:

{‘area’: <function area at 0x102231398>, ‘__builtins__’: <module ‘__builtin__’ (built-in)>,…}

In symbol table, key is function name here. Thus when another function with same name is encountered, in symbol table current key value pair is overridden. Hence, by default function overloading is not available.

But by using decorator design pattern in python, function overloading can be implemented. Below is code snippet to implement function overloading. Explanation will follow later.

In above, we have implemented decorator called overload. This is special case of decorators with arguments. These arguments to decorators are the key elements of function overloading since they specify the type and number of arguments.

Due to the decorator overload, call to area function resolves as below:

area (size ) resolves to overload(int)(area)(size)

and

area (length,breadth) resolves to overload(int, int)(area)(length,breadth)

Our main aim is to maintain a typemap dictionary. Here key is types which is a tuple of arguments to decorator and the value is function object.

self.typemap[types] = function

Let us see typemap in our cases.

overload(int)(area)(size)

results in :

{(<type 'int'>,): <function area at 0x102231938>} (actual){(int,): <function area at 0x102231938>}(for sake of explanation)

and

overload(int, int)(area)(length, breadth)

results in:

{(<type 'int'>, <type 'int'>): <function area at 0x1022318c0>} (actual){(int, int): <function area at 0x1022318c0>} (for sake of explanation)

Lets do deep dive to implementation of overload decorator function. From above, its straight to figure out that outer function accepts arguments and inner function accepts function object.

In above code, for area (length,breadth) which resolves to overload(int, int)(area)(length,breadth):

types = (int,int) and function refers to area function object.

From function object, we can get name by function.__name__

mm = MultiMethod(name)

MultiMethod(name) will now call class MultiMethod with function name (area in our case)

Thus self.name becomes “area” and self.typemap = {}

after this decorator calls mm.register(types, function)

types = (int, int) or (int,) for our cases

Note: I am using int for (<type ‘int’>) for the sake of easy explanation.

function refers to function object.

The register function will generate typemap now as below:

{(int,): <function area at 0x102231938> , (int, int): <function area at 0x1022318c0>}

The function decorator overload returns register (inner function object) which in turn returns mm object.

So what happens when actual function call happens:

Case1:

  • area (3 ) resolves to overload(int)(area)(3)
  • overload(int)(area) returns register which in turn returns mm.
  • overload(int)(area)(3) thus calls mm(3)

Interesting since mm was an object of MultiMethod class. So calling mm(3) is like calling object with some arguments. But yes, in Python you can have callable objects if class defines __call__ special function.

Lets see the __call__ function of MultiMethod class in detail.

when object is called, __call__ method is invoked.

Thus mm(3) means args = (3,)

From the passed arguments tuple of the type of variables passed is generated using:

types = tuple(arg.__class__ for arg in args)

To be noted that __class__ on any object gives its class.

Example:

>>> a  = 34
>>> a.__class__
<type 'int'>
>>> a = 45.6
>>> a.__class__
<type 'float'>
>>> a = "test"
>>> a.__class__
<type 'str'>

Now the invocation is straightforward.

function = self.typemap.get(types)

since our typemap looks like

{(int,): <function area at 0x102231938> , (int, int): <function area at 0x1022318c0>}
  • in case of mm(3) types = (int,)

thus function area at 0x102231938 will be invoked. (area(size))

Case2:

area(4,5)

  • area (4,5 ) resolves to overload(int,int)(area)(4,5)
  • overload(int,int)(area) returns register which in turn returns mm.
  • overload(int,int)(area)(4,5) thus calls mm(4,5)

when object mm is called, __call__ method is invoked.

Thus mm(4,5) means args = (4,5)

and type becomes (int,int)

Thus <function area at 0x1022318c0> gets called (area(length, breadth)).

Hence, by above strategy we can implement function overloading in Python.

Finally, actual call happens through

function(*args)

Between, you can code it once and use everywhere since this is implemented as decorator. So once you have written the code , you never need to write it again, rather you can just use decorator as and when needed.

You can clone this git repo https://github.com/ppundir/overload and use it to implement decorator in your code without knowing the inside details also.

Steps:

  1. git clone git@github.com:ppundir/overload.git
  2. python setup.py install

usage:

If you liked this article, please hit the 👏 button to support it. This will help other Medium users find it. Share this on twitter to help out reach as many readers as possible.

--

--