Funciones especiales de las clases
Tambien conocidos como métodos mágicos (podemos pensar a la palabra método como sinónimo de función) estas funciones se aplican a situaciones habituales de otros objetos de Python. Estas situación son la suma, resta, division, comparación, etc.
__add__
Que pasara si quisiéramos sumar dos Personas
tal como las vimos en la clase anterior:
juan = Persona('Juan', 'Perez')
victor = Persona('Victor', 'Gutierrez')
a = juan + victor
Este código daría un error porque no esta definida la función especial (o mágica) __add__
.
No esta definida porque esta suma no tendría sentido en estos objetos.
Veamos un ejemplo donde si pudiera ser útil.
class FacturaServicio:
""" Cada factura para el pago de servicios hogareños """
def __init__(self, monto, servicio):
self.monto = monto
self.servicio = servicio
def __add__(self, otro):
""" Sumar esta factura a otra factura
Notar que el resultado no es otro objeto de este tipo,
es solo un numero."""
if type(otro) != FacturaServicio:
raise Exception('La suma solo está permitida para objetos del mismo tipo')
return self.monto + otro.monto
La funcion especial __add__
debe incluir un parámetro despues de self
en el que
recibiremos cualquiera sea el objeto al que debemos sumarnos.
Veamos este código en acción:
f1 = FacturaServicio(3500.90, 'Internet')
f2 = FacturaServicio(1806.06, 'Telefono')
print(f1 + f2)
5306.96
La función __add__
devuelve un numero pero podría haber casos donde se devuelvan otros tipos de
datos. Muchas veces podemos esperar que dos objetos del mismo tipo al sumarse devuelvan un nuevo
objeto de ese tipo pero no es siempre el caso. Esto puede definirse a gusto.
De la misma forma, podríamos sumar nuestro objetos con lo de otra clase. Nosotros lo hemos bloqueado
(lanzando un error) pero podríamos hacerlo si fuera necesario. Incluso podríamos devolver resultados
distintos por cada tipo de objeto al que sumamos nuestro objeto.
__str__
Es probablemente la función especial más usada. Se usa para definir que texto se va a devolver cuando el usuario necesite una representación string de este objeto.
def __str__(self):
return f'$ {self.monto} a pagar por el servicio de {self.servicio}'
Ejemplo de uso:
f1 = FacturaServicio(3500.90, 'Internet')
f1_str = str(f1)
print(f1_str)
# o directamente cuando se quiere imprimir nuestro objetio
f1 = FacturaServicio(3500.90, 'Internet')
print(f1)
__eq__
Si queremos permitir la comparación de objetos de nuestra clase se puede definir esta función.
Esta función será llamada cuando nuestro objeto sea comparado con otro mediante el operador ==
.
Al igual que __add__
, podríamos comparar nuestro objetos con lo de otra clase si fuera necesario
(este no es el caso).
def __eq__(self, otro):
""" Revisar si son iguales a otra factura """
if type(otro) != FacturaServicio:
raise Exception('La comparacion solo está permitida para objetos del mismo tipo')
montos_iguales = self.monto == otro.monto
servicios_iguales = self.servicio == otro.servicio
return montos_iguales and servicios_iguales
Veamoslo en acción:
f1 = FacturaServicio(1500.90, 'Internet')
f2 = FacturaServicio(1500.90, 'Internet')
f3 = FacturaServicio(3500.90, 'Internet')
if f1 == f2:
print('f1 y f2 SI son iguales')
else:
print('f1 y f2 NO son iguales')
if f2 == f3:
print('f2 y f3 SI son iguales')
else:
print('f2 y f3 NO son iguales')
"""
f1 y f2 SI son iguales
f2 y f3 NO son iguales
"""
Código final de nuestra clase
Disponible aquí.
class FacturaServicio:
""" Cada factura para el pago de servicios hogareños """
def __init__(self, monto, servicio):
self.monto = monto
self.servicio = servicio
def __add__(self, otro):
""" Sumar esta factura a otra factura
Notar que el resultado no es otro objeto de este tipo,
es solo un numero."""
if type(otro) != FacturaServicio:
raise Exception('La suma solo está permitida para objetos del mismo tipo')
return self.monto + otro.monto
def __str__(self):
return f'$ {self.monto} a pagar por el servicio de {self.servicio}'
def __eq__(self, otro):
""" Revisar si son iguales a otra factura """
if type(otro) != FacturaServicio:
raise Exception('La comparacion solo está permitida para objetos del mismo tipo')
montos_iguales = self.monto == otro.monto
servicios_iguales = self.servicio == otro.servicio
return montos_iguales and servicios_iguales
Otras funciones especiales
Estas algunas otras de las funciones especiales.
__mul__
: Multiplipicación__sub__
: Resta (Substraction)Para que nuestros objetos se comporten como diccionarios
__getitem__
: Obtener un item con la clave que se pasa como parámetro
__setitem__
: Definir un item con la clave y el valor que se pasan como parámetro
__delitem__
: Eliminar el item que tiene la clave que se pasa como parámetro
__ne__
: No igual (Not equal)!=
__lt__
: Menor que (less than)<
__gt__
: Mayor que (grater than)>
__neg__
: Negativo (para cuando usan-MY-OBJETO
)
Y hay muchas más.
Tareas
Hacer un PR a la clase Carta para agregar la función
__add__
para que devuelva unint
calculando el envido solo de esas dos cartas. Incluir multiples asserts al final que pruebe al menos tres sumas (diferentes y variadas) y sus resultados (envidos) esperados.Hacer un PR a la clase Carta para agregar la función
__eq__
para que devuelvaTrue
solo cuando el numero y el palo sean iguales.
Algunos ejemplos de uso
"""
Ejemplo de una clase para manejar fracciones
(de numerador y denominador entero y positivo)
"""
class Fraccion:
""" Clase para manejar fracciones de numeros enteros positivos """
def __init__(self, numerador, denominador):
if type(numerador) != int or type(denominador) != int:
raise ValueError('Solo numeros enteros aceptados')
if numerador < 0 or denominador < 1:
raise ValueError('Solo numeros positivos aceptados')
self._numerador = numerador
self._denominador = denominador
self._simplificar()
@property
def numerador(self):
return self._numerador
@numerador.setter
def numerador(self, num):
if type(num) != int:
raise ValueError('Tipo de dato no admitido para numerador')
if num < 1:
raise ValueError('Valor de numerador no admitido')
self._numerador = num
self._simplificar()
@property
def denominador(self):
return self._denominador
@denominador.setter
def denominador(self, den):
if type(den) != int:
raise ValueError('Tipo de dato no admitido para denominador')
if den <= 0:
raise ValueError('Valor de denominador no admitido')
self._denominador = den
self._simplificar()
def _simplificar(self):
""" Simplificar la fraccion a los numeros mas bajos posibles """
men = min(self._numerador, self._denominador)
for n in range(men, 1, -1):
# Si este numero divide a los dos, entonces los divido
if self._numerador % n == 0 and self._denominador % n == 0:
self._numerador = int(self._numerador / n)
self._denominador = int(self._denominador / n)
break
def __add__(self, otro):
""" Sumar fracciones"""
if type(otro) != Fraccion:
raise ValueError('Solo suma aceptada entre fracciones')
nuevo_denominador = self._denominador * otro.denominador
nuevo_numerador = self._numerador * otro.denominador + otro.numerador * self._denominador
return Fraccion(nuevo_numerador, nuevo_denominador)
def __eq__(self, otro):
if type(otro) != Fraccion:
raise ValueError('No son objetos iguales')
return self._numerador == otro.numerador and self._denominador == otro.denominador
def __str__(self):
return f'({self._numerador} / {self._denominador})'
def __repr__(self):
return f'<Fraccion {self._numerador} / {self._denominador}>'
# Pruebas de funcionamiento
assert Fraccion(2, 3) + Fraccion(1, 3) == Fraccion(1, 1)
assert Fraccion(4, 5) + Fraccion(3, 5) == Fraccion(7, 5)
assert Fraccion(4, 5) + Fraccion(6, 5) == Fraccion(2, 1)
assert Fraccion(5, 12) + Fraccion(4, 19) == Fraccion(143, 228)
assert Fraccion(3, 2) + Fraccion(8, 11) == Fraccion(49, 22)
# Probar la simplificacion al inicio
assert Fraccion(8, 4) == Fraccion(2, 1)
f1 = Fraccion(2, 4)
assert f1 == Fraccion(1, 2)
f1.numerador = 2
assert f1 == Fraccion(1, 1), f'{f1} no es igual a {Fraccion(1, 1)}'
f1.denominador = 8
assert f1 == Fraccion(1, 8)
print('TODO OK')