Métodos Especiales (dunder)
Los métodos especiales son métodos reservados del lenguaje Python para definir o modificar el comportamiento de las clases ante determinadas condiciones u operadores. Los métodos especiales van marcados con doble guion a ambos lados de su nombre, por eso se los llama dunder (double underscore).
En este ejemplo se muestran algunos de los métodos especiales más usados:
class Persona:
# Inicializador de instancias
def __init__(self, nombre,edad):
self.nombre = nombre
self.edad = edad
# Conversion a texto todo el contenido
def __str__(self):
return f'Persona(nombre={self.nombre}), edad={self.edad}'
# Crea un formato para representar la clase como texto
def __repr__(self):
return f"Persona('{self.nombre}','{self.edad}')"
# Metodos para la sobrecarga de operadores
# Ejemplo: suma de clases
def __add__(self, otro):
nuevo_valor= self.edad + otro.edad
return Persona(self.nombre+otro.nombre, nuevo_valor)
Ejemplo de uso:
# Creacion objeto (instancia)
yo = Persona('Sam', 138)
# lectura del objeto como texto
texto = yo
print(texto)
# representacion del objeto en formato texto
repre = repr(yo)
print(repre)
# Reconstruccion del objeto desde texto
resultado = eval(repre) # funcion eval()
print(resultado.nombre)
print(resultado.edad)
# "Suma" de clases
tu = Persona('Ana', 89)
tu_y_yo = tu + yo
print(tu_y_yo)
Sobrecarga de operadores
Introducción
La sobrecarga de operadores es la capacidad de los lenguajes para ejecutar diferentes operaciones al usar un mismo operador, dependiendo del tipo de operandos que sean afectados.
Por ejemplo, el operador +
("suma")
se comporta de distinta manera
ante variables de tipo int
, float
y str
:
# suma de enteros
entero = 3 + 1 # '4'
# suma de flotantes
flotante = 1.21 + 2.5 # '3.71'
# concatenación de strings
texto = "Hola " + "Mundo" # 'Hola Mundo'
Sin embargo, el intérprete no puede adivinar qué instrucciones ejecutar ante variables que no tengan un comportamiento predefinido. Por ejemplo, si se define una clase para manejar vectores de dos dimensiones:
class Vector2D:
def __init__(self, x=0, y=0):
"""Crea un vector de dos dimensiones."""
# componentes internos: x e y
self.x = x
self.y = y
el intérprete no sabe qué hacer en caso de intentarse la suma entre objetos de esta clase:
y entonces se dispara una excepción:
Implementación
En el ejemplo previo
se necesita implementar
la suma vectorial,
que deberá ejecutarse con el operador +
.
La operación de suma
se implementa con ayuda
del método especial __add__
:
class Vector2D:
def __init__(self, x=0, y=0):
"""Crea un vector de dos dimensiones."""
# componentes internos: x e y
self.x = x
self.y = y
def __add__(self, otro):
"""Habilita la suma vectorial con el operador '+'. """
# Implemento la suma componente a componente
x = self.x + otro.x
y = self.y + otro.y
# Creo un nuevo vector con el resultado
return Vector2D(x, y)
Como la operación de suma
se realiza entre dos objetos,
la método toma como referencia
al primer elemento (self
)
e incorpora como argumento al segundo
(otro
).
Una vez implementado el método, con el se calculan los valores del nuevo vector y se crea un vector nuevo como retorno.
v = Vector2D( 3, 1 )
w = Vector2D( 1, -3 )
t = v + w # 't.x=4', 't.y=-2'
A continuación se enumeran algunos de los métodos especiales implementados en Python para definir el comportamiento de los operadores.
Operadores unarios vs operadores binarios
- Los operadores unarios son aquellos
que afectan a un solo elemento.
Sus métodos reservados requieren solamente el argumento
self
. - Los operadores binarios son aquellos que afectan a dos elementos en simultáneo. Sus métodos reservados requieren un argumento adicional.
Argumentos inmutables
No se puede cambiar el número de argumentos de los métodos especiales. Esto significa que no se puede reescribir a un operador unario como si fuera binario y viceversa.
Operadores aritméticos
Operador | Método |
---|---|
+ |
__add__ |
- |
__sub__ |
* |
__mul__ |
** |
__pow__ |
/ |
__truediv__ |
// |
__floordiv__ |
% |
__mod__ |
Operadores lógicos
Operador | Método |
---|---|
and |
__and__ |
or |
__or__ |
not |
__not__ |
Nótese que el operador
not
es unario.
Operadores bit a bit
Operador | Método |
---|---|
and , & |
__and__ |
or , | |
__or__ |
^ |
__xor__ |
~ |
__invert__ |
<< |
__lshift__ |
>> |
__rshift__ |
Nótese que el operador
~
es unario.
Operadores relacionales
Operador | Método |
---|---|
== |
__eq__ |
!= |
__ne__ |
< |
__lt__ |
<= |
__le__ |
> |
__gt__ |
>= |
__ge__ |
Funciones especiales
__str__
Este método permite representar la data interna
de la instancia de clase
como texto (str
).
Esta representación es informal y está pensada para dar información práctica al desarrollador.
Esta información se consulta con la función str()
:
__repr__
Este método también permite representar la data interna
como texto.
A diferencia de __str__
,
esta información debe ser formal
y seguir un formato específico
para poder ser usada por la función eval()
:
__init__
__init__
es el método constructor o inicializador.
Es el encargado de darle estructura al nuevo objeto.
__init__
siempre es llamado despues que __new__
.
__new__
El método __new__
es el encargado de crear las nuevas instancias de la clase.
Este método rara vez es sobreescrito.
class Clase:
def __new__(cls):
# Crea la nueva instancia
instancia = super().__new__(cls)
# devuelve el objeto como retorno
return instancia
cls
representa a la nueva instancia,
en contraposición a self
que se apunta a sí mismo.
Para funcionar correctamente,
__new__
debe crear la nueva instancia
(super().__new__(cls)
)
y retornarla.
Argumentos
A __new__
debe asignársele
los mismos argumentos adicionales
que a __init__
para que la ejecución sea correcta:
__new__
siempre es llamado antes que __init__
.
__call__
Este método permite utilizar a los objetos de la clase como si fueran funciones.
Así es la definición:
class Llamable:
def __call__(self):
print( "¿Me llamabais?" )
objeto = Llamable() # creacion de instancia
objeto() # llamado como si fuera una función
Este método puede manejar atributos y argumentos como cualquier otro. Con ayuda de este método pueden crearse familias de objetos utilizables como funciones:
class Multiplicador:
def __init__(self, valor):
self.__valor = valor
def __call__(self, arg):
return self.__valor * arg
# instancias con distintos valores de inicialización
doble = Multiplicador(2)
triple = Multiplicador(3)
cuadruple = Multiplicador(4)
# uso
v1 = doble(9)
v2 = triple(9)
v3 = cuadruple(9)
dir()
Esta función permite consultar los métodos implementados de la clase pasada por argumento.
Referencias
Bigcode.es - Métodos Especiales y Sobrecarga de Operadores en Python