Sobrecarga de funciones y operadores en Python

La sobrecarga, en el contexto de la programación, se refiere a la capacidad de una función o un operador para comportarse de diferentes maneras dependiendo de los parámetros que se...

¿Qué es la sobrecarga?

La sobrecarga, en el contexto de la programación, se refiere a la capacidad de una función o un operador para comportarse de diferentes maneras según los parámetros que se pasan a la función o los operandos sobre los que actúa el operador. En este artículo, veremos cómo podemos realizar la sobrecarga de funciones y la sobrecarga de operadores en Python.

La sobrecarga de un método fomenta la reutilización. Por ejemplo, en lugar de escribir múltiples métodos que difieren solo un poco, podemos escribir un método y sobrecargarlo. La sobrecarga también mejora la claridad del código y elimina la complejidad.

La sobrecarga es un concepto muy útil. Sin embargo, tiene una serie de desventajas asociadas con él. La sobrecarga puede causar confusión cuando se usa a través de los límites de la herencia. Cuando se usa en exceso, se vuelve engorroso administrar funciones sobrecargadas.

En la sección restante de este artículo, discutiremos la sobrecarga de funciones y operadores en detalle.

Sobrecarga de funciones en Python

Dependiendo de cómo se haya definido la función, podemos llamarla con cero, uno, dos o incluso muchos parámetros. Esto se conoce como "sobrecarga de funciones".

La sobrecarga de funciones se divide además en dos tipos: sobrecarga de funciones integradas y sobrecarga de funciones personalizadas. Veremos ambos tipos en las próximas secciones.

Sobrecarga de funciones integradas

Es posible que cambiemos el comportamiento predeterminado de las funciones integradas de Python. Solo tenemos que definir el método especial correspondiente en nuestra clase.

Demostremos esto usando la función len() de Python en nuestra clase Compra:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Purchase:
    def __init__(self, basket, buyer):
        self.basket = list(basket)
        self.buyer = buyer

    def __len__(self):
        return len(self.basket)

purchase = Purchase(['pen', 'book', 'pencil'], 'Python')
print(len(purchase))

Producción:

1
3

Para cambiar el comportamiento de la función len(), definimos un método especial llamado _len_() en nuestra clase. Siempre que pasemos un objeto de nuestra clase a len(), el resultado se obtendrá llamando a nuestra función personalizada definida, es decir, _len_().

El resultado muestra que podemos usar len() para obtener la longitud de la canasta.

Si llamamos a len() en el objeto sin sobrecargar la función __len__(), obtendremos un TypeError como se muestra a continuación:

1
2
3
4
5
6
7
class Purchase:
    def __init__(self, basket, buyer):
        self.basket = list(basket)
        self.buyer = buyer

purchase = Purchase(['pen', 'book', 'pencil'], 'Python')
print(len(purchase))

Producción:

1
2
3
4
Traceback (most recent call last):
  File "C:/Users/admin/func.py", line 8, in <module>
    print(len(purchase))
TypeError: object of type 'Purchase' has no len()

Nota: Python espera que la función len() devuelva un número entero, por lo tanto, esto debe tenerse en cuenta al sobrecargar la función. Si se espera que su función sobrecargada devuelva algo más que un número entero, obtendrá un TypeError.

Podemos cambiar el comportamiento del método len() en el ejemplo anterior desde la definición de su implementación, es decir, __len__(). En lugar de devolver la longitud de la cesta, hagamos que devuelva otra cosa:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Purchase:
    def __init__(self, basket, buyer):
        self.basket = list(basket)
        self.buyer = buyer

    def __len__(self):
        return 10;

purchase = Purchase(['pen', 'book', 'pencil'], 'Python')
print(len(purchase))

Producción:

1
10

En lugar de devolver la longitud de la cesta, ahora devuelve el valor que hemos especificado.

Sobrecarga de funciones definidas por el usuario

Para sobrecargar una función definida por el usuario en Python, necesitamos escribir la lógica de la función de tal manera que, dependiendo de los parámetros pasados, se ejecute una pieza de código diferente dentro de la función. Echa un vistazo al siguiente ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Student:
    def hello(self, name=None):
        if name is not None:
            print('Hey ' + name)
        else:
            print('Hey ')

# Creating a class instance
std = Student()

# Call the method
std.hello()

# Call the method and pass a parameter
std.hello('Nicholas')

Producción:

1
2
Hey
Hey Nicholas

Hemos creado la clase Estudiante con una función llamada hola(). La clase toma el parámetro nombre que se ha establecido en Ninguno. Esto significa que el método se puede llamar con o sin un parámetro.

Hemos creado una instancia de la clase que se ha utilizado para llamar a la función dos veces, primero con cero parámetros y segundo con un parámetro. Hemos implementado la sobrecarga de funciones ya que hay dos formas de llamar a la función.

Ahora que sabemos cómo funciona la sobrecarga de funciones, la siguiente sección se centra en la sobrecarga de operadores.

Sobrecarga de operadores

Python nos permite cambiar el comportamiento predeterminado de un operador dependiendo de los operandos que usemos. Esta práctica se conoce como "sobrecarga de operadores".

La funcionalidad de los operadores de Python depende de las clases integradas. Sin embargo, el mismo operador se comportará de manera diferente cuando se aplique a diferentes tipos. Un buen ejemplo es el operador "+". Este operador realizará una operación aritmética cuando se aplique a dos números, concatenará dos cadenas y fusionará dos listas.

Ejemplos de sobrecarga de operadores

Para ver la sobrecarga del operador de Python en acción, inicie la terminal de Python y ejecute los siguientes comandos:

1
2
3
4
>>> 4 + 4
8
>>> "Py" + "thon"
'Python'

En el primer comando, hemos usado el operador "+" para sumar dos números. En el segundo comando, usamos el mismo operador para concatenar dos cadenas.

En este caso, el operador "+" tiene dos interpretaciones. Cuando se utiliza para sumar números, se denomina "operador de suma". Cuando se usa para agregar cadenas, se denomina "operador de concatenación". En resumen, podemos decir que el operador "+" se ha sobrecargado para las clases int y str.

Para lograr la sobrecarga de operadores, definimos un método especial en una definición de clase. El nombre del método debe comenzar y terminar con un doble guión bajo (__). El operador + se sobrecarga usando un método especial llamado __add__(). Este método es implementado por las clases int y str.

Considere la siguiente expresión:

1
x + y

Python interpretará la expresión como x.__add__(y). La versión de __add__() que se llame dependerá de los tipos de x e y. Por ejemplo:

1
2
3
4
5
6
7
8
>>> x, y = 5, 7

>>> x + y

12
>>> x.__add__(y)
12
>>>

El ejemplo anterior demuestra cómo usar el operador + así como también su método especial.

El siguiente ejemplo demuestra cómo sobrecargar varios operadores en Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import math

class Point:

    def __init__(self, xCoord=0, yCoord=0):
        self.__xCoord = xCoord
        self.__yCoord = yCoord

    # get x coordinate
    def get_xCoord(self):
        return self.__xCoord

    # set x coordinate
    def set_xCoord(self, xCoord):
        self.__xCoord = xCoord

    # get y coordinate
    def get_yCoord(self):
        return self.__yCoord

    # set y coordinate
    def set_yCoord(self, yCoord):
        self.__yCoord = yCoord

    # get current position
    def get_position(self):
        return self.__xCoord, self.__yCoord

    # change x & y coordinates by p & q
    def move(self, p, q):
        self.__xCoord += p
        self.__yCoord += q

    # overload + operator
    def __add__(self, point_ov):
        return Point(self.__xCoord + point_ov.__xCoord, self.__yCoord + point_ov.__yCoord)

    # overload - operator
    def __sub__(self, point_ov):
        return Point(self.__xCoord - point_ov.__xCoord, self.__yCoord - point_ov.__yCoord)

    # overload < (less than) operator
    def __lt__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) < math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)

    # overload > (greater than) operator
    def __gt__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) > math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)

    # overload <= (less than or equal to) operator
    def __le__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) <= math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)

    # overload >= (greater than or equal to) operator
    def __ge__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) >= math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)

    # overload == (equal to) operator
    def __eq__(self, point_ov):
        return math.sqrt(self.__xCoord ** 2 + self.__yCoord ** 2) == math.sqrt(point_ov.__xCoord ** 2 + point_ov.__yCoord ** 2)

point1 = Point(2, 4)
point2 = Point(12, 8)

print("point1 < point2:", point1 < point2)
print("point1 > point2:", point1 > point2)
print("point1 <= point2:", point1 <= point2)
print("point1 >= point2:", point1 >= point2)
print("point1 == point2:", point1 == point2)

Producción:

1
2
3
4
5
point1 < point2: True
point1 > point2: False
point1 <= point2: True
point1 >= point2: False
point1 == point2: False

Tenemos dos atributos privados en la clase Point, a saber, __xCoord y __yCoord que representan coordenadas planas cartesianas denominadas xCoord y yCoord. Hemos definido los métodos setter y getter para estos atributos. El método get_position() nos ayuda a obtener la posición actual, mientras que el método move() nos ayuda a cambiar las coordenadas.

Considere la siguiente línea extraída del código:

1
    def __add__(self, point_ov):

La línea nos ayuda a sobrecargar el operador + para nuestra clase. El método __add__() debería crear un nuevo objeto Point agregando las coordenadas individuales de un único objeto Point a otro objeto Point. Finalmente devuelve el objeto recién creado a la persona que llama. Esto nos ayuda a escribir expresiones como:

1
point3 = point1 + point2

Python interpretará lo anterior como point3 = point1.__add__(point2). Luego llamará al método __add__() para agregar dos objetos Point. El resultado se asignará a "point3".

Tenga en cuenta que una vez que se llama al método __add__(), el valor de point1 se asignará al parámetro self mientras que el valor de point2 se asignará al parámetro point_ov. Todos los otros métodos especiales funcionarán de manera similar.

Operadores para sobrecargar

La siguiente tabla muestra algunos de los operadores matemáticos sobrecargados más comúnmente y el método de clase para sobrecargar:

Método del operador


+ __add__(uno mismo, otro) - __sub__(uno mismo, otro) * __mul__(uno mismo, otro) / __truediv__(uno mismo, otro) % __mod__(uno mismo, otro) < __lt__(uno mismo, otro) <= __le__(uno mismo, otro) == __eq__(uno mismo, otro) != __ne__(uno mismo, otro) > __gt__(uno mismo, otro) >= __ge__(uno mismo, otro)

Conclusión

Python admite la sobrecarga de funciones y operadores. En la sobrecarga de funciones, podemos usar el mismo nombre para muchas funciones de Python pero con diferentes números o tipos de parámetros. Con la sobrecarga de operadores, podemos cambiar el significado de un operador de Python dentro del alcance de una clase.

Licensed under CC BY-NC-SA 4.0