Function overloading is a very interesting tool that allows several different functions to share the same identifier, improving the readability and interpretability of our code in some scenarios.
This tool is available by default in many of today’s most widely used programming languages, such as Java or C++. In Python, however, function overloading is not a tool available by default, being necessary the use of libraries that make possible to make use of this utility. In this article, we will see in detail what overloading is, showing practical examples of how this concept is implemented in other programming languages and clarifying those libraries that allow us to make use of it in Python.
What is overloading?
We can define overloading in a programming language as the possibility of defining multiple functions that share the same name but differ in the number of arguments they receive, in the type of those arguments, or in both factors at the same time. This latter nuance is important, since there must always be a difference in the arguments of an overloaded function, and this difference cannot reside in the return of the function, which is indifferent for these purposes.
This tool is very useful in scenarios where we need a method to have several different implementations depending on the type of arguments received, thus accepting arguments of different types.
Another common scenario is when a method has arguments that are not essential to its operation, but are rather complementary or supportive. This second scenario is very common in class constructors, where different implementations of the constructor can be provided according to the arguments given to instantiate the class, where there can be complete implementations that require each of the attributes or parameters necessary to instantiate the class, or reduced versions with only the essential attributes.
It is quite common among less experienced programmers to confuse the concept of overriding with that of overloading. Overriding is the tool that allows us to create a new implementation of a method that has been defined in a parent class of the class in question, completely changing the implementation of the inherited method or adding new functionality to it. The two main differences between these concepts are the following:
- In order to make use of overriding it is necessary that two classes with an inheritance relationship exist, while in the case of overloading this is not a requirement.
- In the case of overriding, the function parameters must be exactly the same as those of the function whose behavior is overwritten, unlike what happens in the case of overloading.
Overloading in other programming languages
In order to reinforce what overloading is and how it is usually implemented in common programming languages, a simple example will be presented to show where the need to use this concept comes from, as well as to provide an implementation close to a real use case.
For this purpose, an Account class is proposed, which we can define as an entity to model a bank account in which only money can be added to the total balance. In this class, it is intended to be able to increase the total balance of the account in several scenarios that, despite being similar in essence, contemplate certain differences in terms of the input elements they take and the underlying logic to be implemented.
In the first scenario, you need to be able to increase the balance of the account by means of a transfer, for which you must first check that the identifier of the destination account of the transfer coincides with that of the bank account to which you intend to add the money.
The second case is very similar to the previous one, since in this one the balance will also be increased by means of a transfer, but deducting from the amount of the transfer a commission given for the operation.
Finally, in the third example we intend to add money to our account by means of a bank deposit, such as the typical one that can be made through an ATM at any bank branch. When taking into account this bank deposit, it will also be necessary to check that the identifiers of the accounts match in a similar way to how it has been done with the transfer, and in addition it will be necessary to make a matching of the amount to add, since our balance is of type float while the amount of a deposit is of type integer (normally only banknotes can be deposited, which makes sense that the amount is of type integer).
As we can see, these three scenarios have a very clear common denominator, they intend to add money to the total balance of the account, but to carry out this action they depend on different elements, as well as implementing different logic depending on the type of element or even the number of elements supplied as input. When implementing it, we could think of defining three different methods to our Account class that reflect in the arguments these differences of input elements, together with the associated logic depending on these. Thanks to the overloading, these three methods could have the same name, which makes sense since the objective they pursue is the same, besides improving the general readability of the program and the interpretability by third party users who do not know the implementation of this class.
Having stated the example, an implementation in Java programming language is presented, in which it can be observed that the entity has been represented by a class with three attributes: the identifier, the user to which it belongs and the total balance. After this, three class methods are defined that implement each of the scenarios previously mentioned, which share the name of the method in spite of differing in the type and number of arguments given as input.
Overloading in Python
As we have seen, implementing overloading in a programming language like Java is something as simple as defining the same method name as long as the methods differ in their input arguments. Unfortunately, in Python this is not so trivial and straightforward, since, by default, it does not natively support function overloading.
This is mainly because Python is a dynamically typed language and, when defining several functions with the same identifier, Python will understand that the last one defined is the valid definition for the function, discarding all previous ones. Despite this, there is still hope for making use of this type of tool in Python, thanks to the fact that we can define alternative functions at runtime depending on the types of the arguments, a technique known as multimethods or multiple dispatch.
Currently, Python 3 natively only supports single dispatch, that is, selecting the implementation of a function based on the type of a single argument. In order to do multiple dispatch it is necessary to make use of external libraries that provide tools to make this possible. In this post, we are going to consider two of these libraries, showing how the previously mentioned example would be implemented with each one, and emphasizing the advantages and disadvantages of one with respect to the other.
This library provides an efficient implementation of multipledispatch in Python by using a decorator in which the types of the arguments received by the function are specified. This library is defined in the Python Package Index (PyPI), so installing it is as simple as executing:
pip install multipledispatch. Using it, the previous example would look like this:
Among the advantages of this library we can see the following:
- It performs a static analysis to avoid conflicts between defined methods.
- It is optimized to reduce the execution time consumed by this type of techniques.
As main disadvantage of multipledispatch, we can mention that it does not support keyword arguments, something that may be relevant in your specific use case, and that may make you opt for the second option presented in this post.
Like multipledispatch, the multimethod library provides different decorators to add this functionality to our Python methods. Like the previous one, this library is available in the Python Package Index (PyPI), and can be installed with a simple
pip install multimethod. Using it, the example would look like this:
Some of the advantages of this library are the following:
- It pursues simplicity and speed, and by caching argument types it ensures to be the fastest pure Python implementation of this concept.
- The argument type information is added directly in the method signature, unlike multipledispatch, where this type information was added in the decorator. This makes the code much cleaner and more intuitive.
- It offers different decorator options according to your needs, with options that are not very flexible but very fast at runtime, or slow options but very flexible in terms of the arguments they allow.
- It supports the use of keyword arguments.
Unlike what happened with the previous library, multimethod does not present any relevant disadvantage, being from our point of view a more complete and simple option to implement this type of techniques in Python.
Function overloading is a useful tool to make your code more interpretable and readable. In this post, we have seen what this concept consists of, and a concrete use case in which it can provide value, seeing how it is implemented in an extended programming language such as Java. In addition, we have discussed how to implement this concept in Python using two different libraries, which have been contrasted to expose which option is best suited to a particular case.