Overloading: Sobrecarga de funciones en Python

Introducción

El overloading o sobrecarga de funciones es una herramienta muy interesante que nos permite que varias funciones diferentes compartan un mismo identificador, mejorando la legibilidad y la interpretabilidad de nuestro código en algunos escenarios.

Esta herramienta está disponible por defecto en muchos de los lenguajes de programación más usados en la actualidad, como por ejemplo Java o C++. En Python, sin embargo, la sobrecarga de funciones no es una herramienta disponible por defecto, siendo necesario el uso de librerías que posibiliten hacer uso de esta utilidad.

En este artículo, se verá en detalle qué es el overloading mostrando ejemplos prácticos de cómo se implementa dicho concepto en otros lenguajes de programación y aquellas librerías que nos permiten hacer uso de este en Python.

¿Qué es el overloading?

Podemos definir el overloading en un lenguaje de programación como la posibilidad de definir múltiples funciones que compartan el mismo nombre pero que difieren en el número de argumentos que reciben, en la tipología de dichos argumentos, o en ambos factores a la vez. Este último matiz es importante, ya que siempre debe existir una diferencia en los argumentos de una función sobrecargada y, dicha diferencia, no puede residir en el retorno de la función, el cual es indiferente a estos efectos.

Esta herramienta es muy útil en casos en los que necesitamos que un método tenga varias implementaciones diferentes según el tipo de los argumentos recibidos, aceptando de esta forma argumentos de diferentes tipos.

Otro escenario común es cuando un método tiene argumentos que no son esenciales para su funcionamiento, sino que son más bien complementarios o de apoyo. Este segundo ejemplo es muy común en los constructores de las clases, donde se pueden suministrar diferentes implementaciones del constructor según los argumentos dados para instanciar la clase, donde pueden existir implementaciones completas que requieran de cada uno de los atributos o parámetros necesarios para instanciar la clase o versiones reducidas con sólo los atributos esenciales.

Es bastante habitual entre los programadores menos experimentados confundir el concepto de overriding con el de overloading. El overriding es la herramienta que nos permite crear una nueva implementación de un método que ha sido definido en una clase padre de la clase en cuestión, cambiando por completo la implementación del método heredado o añadiendo nueva funcionalidad a este. Las dos principales diferencias entre estos conceptos son las siguientes:

  • Para poder hacer uso del overriding, es necesario que existan dos clases con una relación de herencia, mientras que en el caso del overloading esto no es un requisito.
  • En el caso del overriding, los parámetros de la función deben de ser exactamente iguales que los de la función cuyo comportamiento se sobreescribe, a diferencia de lo que ocurre en el caso del overloading.

Overloading en otros lenguajes de programación

Para afianzar qué es el overloading y cómo se suele implementar en lenguajes de programación comunes, se va a presentar un ejemplo sencillo en el que se pretende transmitir de dónde surge la necesidad de utilizar este concepto, además de aportar una implementación cercana a un caso de uso real.

Para esto, se plantea una clase Account, que podemos definir como una entidad para modelar una cuenta bancaria en la que sólo se puede añadir dinero al balance total. En esta clase, se pretende poder incrementar el balance total de la cuenta en varios escenarios que, pese a ser similares en esencia, contemplan ciertas diferencias en cuanto a los elementos de entrada que toman y la lógica subyacente a implementar. 

En un primer escenario, se requiere poder incrementar el balance de la cuenta mediante una transferencia, para la cual se deberá hacer previamente una comprobación de que el identificador de la cuenta de destino de dicha transferencia coincide con el de la cuenta bancaria a la que se pretende añadir el dinero.

El segundo caso es muy similar al anterior, ya que en este también se incrementará el balance mediante una transferencia, pero descontando de la cantidad de la transferencia una comisión dada para la operación.

Por último, en el tercer ejemplo se pretende añadir dinero a nuestra cuenta mediante un depósito bancario, como el típico que se puede hacer mediante un cajero en cualquier sucursal bancaria. A la hora de tener en cuenta este depósito bancario, también habrá que comprobar que los identificadores de las cuentas coinciden de forma similar a cómo se ha hecho con la transferencia. Además, habrá que hacer un casteo de la cantidad a añadir, ya que nuestro balance es de tipo float mientras que la cantidad de un depósito es de tipo integer (normalmente sólo se pueden depositar billetes, lo que hace que tenga sentido que dicha cantidad sea de tipo integer).

Como podemos observar, estos tres escenarios tienen un denominador común muy claro, pretenden añadir dinero al balance total de la cuenta. Para llevar a cabo esta acción, dependen de diferentes elementos, además de implementar diferente lógica dependiendo de qué tipo sea éste, o incluso del número de elementos suministrados como entrada. A la hora de implementarlo, podríamos pensar en definir tres métodos diferentes a nuestra clase Account que reflejen en los argumentos estas diferencias de elementos de entrada junto con la lógica asociada dependiendo de éstos.

Gracias al overloading, estos tres métodos podrían tener el mismo nombre, lo cual tiene sentido ya que el objetivo que persiguen es el mismo, además de mejorar la legibilidad general del programa y la interpretabilidad por terceros usuarios que desconocen la implementación de esta clase.

Planteado el ejemplo, se presenta una implementación en el lenguaje de programación Java, en el que efectivamente se puede observar que la entidad ha sido representada por una clase con tres atributos: el identificador, el usuario al que pertenece y el balance total. Tras esto, se definen tres métodos de clase que implementan cada uno de los escenarios planteados previamente, los cuales comparten el nombre del método pese a diferir en el tipo y en el número de los argumentos dados como entrada.

Overloading en Python

Como hemos visto, implementar el overloading en un lenguaje de programación como Java es algo tan sencillo como definir el mismo nombre de método siempre que los métodos difieran en sus argumentos de entrada. Desgraciadamente, en Python esto no es tan trivial y sencillo, ya que, por defecto, no da soporte nativo a la sobrecarga de funciones. 

Esto se debe principalmente a que Python es un lenguaje de tipado dinámico y, al definir varias funciones con el mismo identificador, Python entenderá que la última definida es la definición válida para la función, desechando todas las anteriores. Pese a esto, sigue habiendo esperanza para hacer uso de este tipo de herramienta en Python, gracias a que podemos definir funciones alternativas en tiempo de ejecución dependiendo de los tipos de los argumentos, técnica conocida como multimethods o multiple dispatch.

Actualmente, Python 3 de forma nativa sólo da soporte para hacer single dispatch, es decir, seleccionar la implementación de una función basándose en el tipo de un solo argumento. Para poder hacer multiple dispatch es necesario hacer uso de librerías externas que proveen herramientas para hacer esto posible. En este post, se van a considerar dos de estas librerías mostrando cómo se implementaría el ejemplo previamente planteado con cada una, y haciendo hincapié entre las ventajas y desventajas que plantea una con respecto a la otra.

Librería multipledispatch

Esta librería provee una implementación eficiente del multipledispatch en Python mediante el uso de un decorador en el que se especifican los tipos de los argumentos recibidos por la función. Esta librería está definida en el Python Package Index (PyPI), por lo que para instalarla es tan sencillo como ejecutar: pip install multipledispatch. Haciendo uso de ella, el ejemplo anterior quedaría de la siguiente manera:

Entre las ventajas de esta librería podemos ver las siguientes:

  • Realiza un análisis estático para evitar conflictos entre los métodos definidos.
  • Está optimizado para reducir el tiempo de ejecución consumido por este tipo de técnicas.

Como desventaja principal de multipledispatch, podemos mencionar que no da soporte a keyword arguments, algo que puede ser relevante en tu caso de uso concreto, y que puede que te haga decantarte por la segunda opción presentada en este post.

Librería multimethod

Al igual que multipledispatch, la librería multimethod provee diferentes decoradores para añadir esta funcionalidad a nuestros métodos en Python. Al igual que la anterior, esta librería está disponible en el Python Package Index (PyPI), y se puede instalar con un simple pip install multimethod. Al utilizarla, el ejemplo planteado quedaría de la siguiente forma:

Entre las ventajas de esta librería podemos observas las siguientes:

  • Persigue la simplicidad y la velocidad, y al cachear los tipos de los argumentos asegura ser la implementación pura en Python de este concepto más rápida.
  • La información del tipo de los argumentos es añadida directamente en la firma del método, a diferencia de multipledispatch, donde este tipo de información se añadía en el decorador. Esto hace que el código quede mucho más limpio e intuitivo.
  • Ofrece diferentes opciones de decoradores según tus necesidades, contando con opciones poco flexibles pero muy rápidas en tiempo de ejecución, u opciones lentas pero muy flexibles en cuanto a los argumentos que permiten.
  • Da soporte al uso de keyword arguments.

A diferencia de lo que ocurría con la librería anterior, multimethod no presenta ninguna desventaja relevante, siendo desde nuestro punto de vista una opción más completa y sencilla para implementar este tipo de técnicas en Python.

Conclusión

La sobrecarga de funciones es una herramienta útil para hacer que tu código sea más interpretable y legible. En este post, hemos visto en qué consiste este concepto y un caso de uso concreto en el que puede aportar valor, viendo cómo se implementa en un lenguaje de programación extendido como es Java. Además, se ha comentado cómo implementar este concepto en Python usando dos librerías diferentes, las cuales han sido contrastadas para exponer cual es la opción que mejor se adecúa a un caso concreto.

¡Esto es todo! Si este post te ha parecido interesante, te animamos a visitar la categoría Software para ver todos los posts relacionados y a compartirlo en redes. ¡Hasta pronto!
Agustín Mora
Agustín Mora
Artículos: 6