Métodos Especiales en Python¶
En Python, los métodos especiales son una serie de funciones predefinidas que permiten a los desarrolladores definir comportamientos personalizados para sus clases. Estos métodos son también conocidos como «métodos mágicos» debido a la sintaxis única que emplean, ya que sus nombres comienzan y terminan con un doble guion bajo (__). Estos métodos se activan automáticamente en respuesta a ciertas operaciones predefinidas en Python, lo que permite a las clases integrarse de manera más natural con el lenguaje.
Por ejemplo, cuando se utiliza el operador de suma (+) con objetos de una clase, Python llama al método especial __add__ si está definido en la clase para realizar la operación. Esto permite que los objetos de la clase tengan comportamientos personalizados al interactuar con operadores y funciones nativas del lenguaje.
class Punto:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, otro):
return Punto(self.x + otro.x, self.y + otro.y)
p1 = Punto(1, 2)
p2 = Punto(3, 4)
p3 = p1 + p2 # Llama a __add__: crea un nuevo Punto(4, 6)
Algunos Métodos Especiales en Python¶
A continuación se presenta una lista con algunos de los métodos especiales en Python, acompañados de una breve descripción de su uso:
Métodos de Inicialización y Destrucción¶
`__init__(self, …)`: Inicializa una nueva instancia de la clase. Es el constructor de la clase.
`__del__(self)`: Se llama cuando un objeto está a punto de ser destruido; es el destructor de la clase.
Métodos de Representación¶
`__repr__(self)`: Retorna una representación «oficial» del objeto, útil para depuración. Debe ser una cadena válida de Python.
`__str__(self)`: Retorna una representación «informal» o legible del objeto, utilizada por print() y str().
Métodos de Comparación¶
`__eq__(self, other)`: Define el comportamiento para la igualdad (==).
`__ne__(self, other)`: Define el comportamiento para la desigualdad (!=).
`__lt__(self, other)`: Define el comportamiento para menor que (<).
`__le__(self, other)`: Define el comportamiento para menor o igual que (<=).
`__gt__(self, other)`: Define el comportamiento para mayor que (>).
`__ge__(self, other)`: Define el comportamiento para mayor o igual que (>=).
Métodos de Operadores Aritméticos¶
`__add__(self, other)`: Define el comportamiento para la suma (+).
`__sub__(self, other)`: Define el comportamiento para la resta (-).
`__mul__(self, other)`: Define el comportamiento para la multiplicación (*).
`__truediv__(self, other)`: Define el comportamiento para la división (/).
`__floordiv__(self, other)`: Define el comportamiento para la división entera (//).
`__mod__(self, other)`: Define el comportamiento para el módulo (%).
`__pow__(self, other[, modulo])`: Define el comportamiento para la exponenciación (**).
`__and__(self, other)`: Define el comportamiento para la operación bit a bit AND (&).
`__or__(self, other)`: Define el comportamiento para la operación bit a bit OR (|).
`__xor__(self, other)`: Define el comportamiento para la operación bit a bit XOR (^).
`__lshift__(self, other)`: Define el comportamiento para el desplazamiento a la izquierda (<<).
`__rshift__(self, other)`: Define el comportamiento para el desplazamiento a la derecha (>>).
`__neg__(self)`: Define el comportamiento para la negación (-self).
`__pos__(self)`: Define el comportamiento para el signo positivo (+self).
`__abs__(self)`: Define el comportamiento para la función abs().
Métodos de Asignación de Operadores Aritméticos¶
`__iadd__(self, other)`: Define el comportamiento para la suma en el lugar (+=).
`__isub__(self, other)`: Define el comportamiento para la resta en el lugar (-=).
`__imul__(self, other)`: Define el comportamiento para la multiplicación en el lugar (*=).
`__itruediv__(self, other)`: Define el comportamiento para la división en el lugar (/=).
`__ifloordiv__(self, other)`: Define el comportamiento para la división entera en el lugar (//=).
`__imod__(self, other)`: Define el comportamiento para el módulo en el lugar (%=).
`__ipow__(self, other[, modulo])`: Define el comportamiento para la exponenciación en el lugar (**=).
`__iand__(self, other)`: Define el comportamiento para la operación bit a bit AND en el lugar (&=).
`__ior__(self, other)`: Define el comportamiento para la operación bit a bit OR en el lugar (|=).
`__ixor__(self, other)`: Define el comportamiento para la operación bit a bit XOR en el lugar (^=).
`__ilshift__(self, other)`: Define el comportamiento para el desplazamiento a la izquierda en el lugar (<<=).
`__irshift__(self, other)`: Define el comportamiento para el desplazamiento a la derecha en el lugar (>>=).
Métodos de Conversión¶
`__bool__(self)`: Define el comportamiento para la conversión a booleano, utilizado por bool().
`__int__(self)`: Define el comportamiento para la conversión a entero, utilizado por int().
`__float__(self)`: Define el comportamiento para la conversión a flotante, utilizado por float().
`__complex__(self)`: Define el comportamiento para la conversión a número complejo, utilizado por complex().
`__index__(self)`: Define el comportamiento para la conversión a un índice entero, utilizado por hex(), oct(), bin().
`__round__(self, n)`: Define el comportamiento para la función round().
`__trunc__(self)`: Define el comportamiento para la función math.trunc().
`__floor__(self)`: Define el comportamiento para la función math.floor().
`__ceil__(self)`: Define el comportamiento para la función math.ceil().
Ejemplo de Métodos Especiales en Python: Librería para Números Complejos¶
Supongamos que, en lugar de utilizar la librería estándar de Python para operar con números complejos, queremos desarrollar nuestros propios algoritmos. Una forma inicial de abordar este problema es representar un número complejo como una tupla que contiene su parte real y su parte imaginaria, y luego definir funciones para realizar operaciones con estos números, como en el siguiente ejemplo:
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Programa para demostrar la creación de una librería para operar números
complejos.
Cada número complejo se representa como una tupla con la parte real y
la parte imaginaria del número, es decir:
z = (re, im)
"""
def suma(a, b):
"""
Función que calcula la suma de 2 números complejos.
"""
are = a[0]
aim = a[1]
bre = b[0]
bim = b[1]
return (are + bre, aim + bim)
def multip(a, b):
"""
Función que calcula el producto de 2 números complejos.
"""
are = a[0]
aim = a[1]
bre = b[0]
bim = b[1]
return (are * bre - aim * bim, aim * bre + bim * are)
def divi(a, b):
"""
Función que calcula la división entre 2 números complejos.
a / b
"""
are = a[0]
aim = a[1]
bre = b[0]
bim = b[1]
return (are * bre + aim * bim) / (bre ** 2 + bim ** 2), (aim * bre - are * bim) / (bre ** 2 + bim ** 2)
def imp(a):
"""
Función que genera una cadena de caracteres con la representación
de un número complejo.
"""
return "{:f}{:+f}i".format(a[0], a[1])
if __name__ == "__main__":
q = (1., 5.)
w = (3., 4.)
h = (4., 0)
a = suma(q, w)
b = multip(q, h)
c = divi(w, h)
print(imp(a))
print(imp(b))
print(imp(c))
Aunque esta aproximación es válida, presenta limitaciones. Por ejemplo, ¿cómo escribiríamos la siguiente operación?
Para poder escribir expresiones como esta de manera más natural, podemos utilizar las clases de Python y sus métodos especiales, como se muestra a continuación.
Primera aproximación a un programa con objetos¶
Antes de entrar en detalles sobre el uso de los métodos especiales, reescribamos el programa utilizando clases:
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Programa para demostrar la creación de una librería con una clase para
operar números complejos.
"""
class Complex:
def __init__(self, re, im=0):
"""Define un número complejo, donde `re` contiene la parte real e
`im` contiene la parte imaginaria del número.
"""
self.re = re
self.im = im
def suma(self, other):
"""
Función que calcula la suma de 2 números complejos.
"""
return Complex(self.re + other.re, self.im + other.im)
def multip(self, other):
"""
Función que calcula el producto de 2 números complejos.
"""
return Complex(self.re * other.re - self.im * other.im, self.im * other.re + other.im * self.re)
def divi(self, other):
"""Función que calcula la división entre 2 números complejos.
self / other"""
return Complex((self.re * other.re + self.im * other.im) / (other.re**2 + other.im**2),
(self.im * other.re - self.re * other.im) / (other.re**2 + other.im**2))
def imp(self):
"""Función que genera una cadena de caracteres con la representación
de un número complejo.
"""
return "{:f}{:+f}i".format(self.re, self.im)
if __name__ == "__main__":
q = Complex(1., 5.)
w = Complex(3., 4.)
h = Complex(4.)
a = q.suma(w)
b = q.multip(h)
c = w.divi(h)
print(a.imp())
print(b.imp())
print(c.imp())
En este programa, estamos encapsulando el comportamiento de los números complejos en la clase Complex, agrupando datos y métodos que operan sobre esos datos dentro de una única entidad. Esto facilita la gestión y el mantenimiento del código, ya que las operaciones sobre los números complejos están directamente asociadas con los datos que representan. Este encapsulamiento representa una ventaja clara al comparar esta implementación con la aproximación basada en funciones del código anterior. La implementación orientada a objetos organiza el código de manera más coherente, asociando los datos (la parte real e imaginaria) con las operaciones que pueden realizarse sobre ellos (suma, multiplicación, división). Sin embargo, sigue existiendo una limitación importante: escribir operaciones aritméticas complejas entre objetos Complex no es tan intuitivo como podría ser.
Primera solución con métodos especiales¶
Para resolver la dificultad que puede surgir al escribir operaciones complejas entre números Complex, Python ofrece una poderosa herramienta: los métodos especiales. Estos métodos nos permiten definir cómo deben comportarse los objetos de nuestra clase Complex al interactuar con operadores como +, *, /, entre otros. En el siguiente ejemplo, se muestra cómo utilizar estos métodos especiales para mejorar la legibilidad y usabilidad de nuestra clase Complex.
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Programa para demostrar la creación de una librería con una clase para
operar números complejos.
Ver:
http://docs.python.org/reference/datamodel.html#emulating-numeric-types
"""
class Complex:
def __init__(self, re, im=0):
"""Define un número complejo, donde `re` contiene la parte real e
`im` contiene la parte imaginaria del número.
"""
self.re = re
self.im = im
def __add__(self, other):
"""
Calcula la suma de dos números complejos.
"""
return Complex(self.re + other.re, self.im + other.im)
def __sub__(self, other):
"""
Calcula la resta de dos números complejos.
"""
return Complex(self.re - other.re, self.im - other.im)
def __neg__(self):
"""
Calcula el negativo de un número complejo.
"""
return Complex(-self.re, -self.im)
def __mul__(self, other):
"""
Calcula el producto de dos números complejos.
"""
return Complex(self.re * other.re - self.im * other.im, self.im * other.re + other.im * self.re)
def __truediv__(self, other):
"""
Calcula la división entre dos números complejos.
"""
return Complex((self.re * other.re + self.im * other.im) / (other.re**2 + other.im**2),
(self.im * other.re - self.re * other.im) / (other.re**2 + other.im**2))
def __str__(self):
"""
Genera una cadena de caracteres con la representación
de un número complejo.
"""
return "{:f}{:+f}i".format(self.re, self.im)
if __name__ == "__main__":
q = Complex(1., 5.)
w = Complex(3., 4.)
h = Complex(4.)
a = q + w
a1 = q - w
a3 = -w
b = q * h
c = w / h
print(a)
print(a1)
print(a3)
print(b)
print(c)
Con esta nueva definición de la clase Complex, podemos realizar cualquier operación aritmética entre números complejos de forma natural. Sin embargo, si intentamos operar un número complejo con un entero o un número flotante, obtendremos un error, ya que la clase Complex no está diseñada para manejar operaciones con diferentes tipos numéricos. Para solucionar este problema, es necesario implementar métodos especiales adicionales que permitan la interoperabilidad entre Complex y otros tipos numéricos, como enteros y flotantes.
Una mejor implementación de la librería¶
En esta nueva implementación de la clase Complex, se han realizado varias mejoras significativas para hacerla más versátil y capaz de manejar operaciones con otros tipos numéricos, como enteros y flotantes. Estas mejoras permiten que la clase Complex sea más flexible y fácil de usar en diferentes contextos matemáticos. Además, se han implementado métodos especiales con el prefijo __r (como __radd__, __rsub__, etc.), que se activan cuando un objeto Complex aparece en el lado derecho de un operador en una expresión. Estos métodos son llamados automáticamente por Python si el operador no sabe cómo manejar el objeto en el lado izquierdo de la operación. Por ejemplo, si escribimos 3 + q, donde q es un objeto de la clase Complex, Python intenta primero realizar la operación usando el método __add__ del entero 3. Sin embargo, como el método __add__ de int no sabe cómo manejar un objeto Complex, devuelve NotImplemented. En respuesta, Python recurre al método __radd__ de q, lo que permite que la operación se complete correctamente.
Al igual que en el int de Python, en la clase Complex también se ha implementado la devolución de NotImplemented en los métodos __add__, __sub__, __mul__, __truediv__ y sus correspondientes métodos __r. Esto se hace para manejar situaciones en las que el tipo de dato pasado como argumento no es compatible con la operación. Al devolver NotImplemented, se le indica a Python que intente realizar la operación utilizando otros métodos disponibles o, si no es posible, que lance un error adecuado. Este enfoque asegura que la clase Complex maneje de manera segura y eficiente las operaciones con diferentes tipos de datos, permitiendo que las operaciones aritméticas se manejen de forma correcta y predecible.
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Programa para demostrar la creación de una librería con una clase para
operar números complejos.
Ver:
http://docs.python.org/reference/datamodel.html#emulating-numeric-types
"""
class Complex:
def __init__(self, re, im=0):
"""Define un número complejo, donde `re` contiene la parte real e
`im` contiene la parte imaginaria del número.
"""
self.re = re
self.im = im
def __add__(self, other):
"""
Calcula la suma de dos números, que pueden ser complejos o reales.
- Soporta la suma de un `Complex` con otro `Complex`.
- También permite sumar un `Complex` con un entero o flotante.
- Retorna `NotImplemented` si `other` no es un tipo soportado.
"""
if isinstance(other, Complex):
return Complex(self.re + other.re, self.im + other.im)
elif isinstance(other, (int, float)):
return Complex(self.re + other, self.im)
else:
return NotImplemented
def __radd__(self, other):
"""
Permite la suma cuando el número complejo está a la derecha del operador `+`.
- Esto maneja casos como `3 + q` donde `q` es un `Complex`.
"""
return self + other
def __sub__(self, other):
"""
Calcula la resta de dos números, que pueden ser complejos o reales.
- Soporta la resta de un `Complex` con otro `Complex`.
- También permite restar un entero o flotante de un `Complex`.
- Retorna `NotImplemented` si `other` no es un tipo soportado.
"""
if isinstance(other, Complex):
return Complex(self.re - other.re, self.im - other.im)
elif isinstance(other, (int, float)):
return Complex(self.re - other, self.im)
else:
return NotImplemented
def __rsub__(self, other):
"""
Permite la resta cuando el número complejo está a la derecha del operador `-`.
- Esto maneja casos como `5 - q` donde `q` es un `Complex`.
"""
return -self + other
def __neg__(self):
"""
Calcula el opuesto de un número complejo.
- Devuelve un nuevo `Complex` con las partes real e imaginaria negadas.
"""
return Complex(-self.re, -self.im)
def __mul__(self, other):
"""
Calcula el producto de dos números, que pueden ser complejos o reales.
- Soporta el producto de un `Complex` con otro `Complex`.
- También permite multiplicar un `Complex` por un entero o flotante.
- Retorna `NotImplemented` si `other` no es un tipo soportado.
"""
if isinstance(other, Complex):
return Complex(self.re * other.re - self.im * other.im, self.im * other.re + other.im * self.re)
elif isinstance(other, (int, float)):
return Complex(other * self.re, other * self.im)
else:
return NotImplemented
def __rmul__(self, other):
"""
Permite el producto cuando el número complejo está a la derecha del operador `*`.
- Esto maneja casos como `2 * q` donde `q` es un `Complex`.
"""
return self * other
def __truediv__(self, other):
"""
Calcula la división entre dos números, que pueden ser complejos o reales.
- Soporta la división de un `Complex` por otro `Complex`.
- También permite dividir un `Complex` por un entero o flotante.
- Retorna `NotImplemented` si `other` no es un tipo soportado.
"""
if isinstance(other, Complex):
denom = other.re**2 + other.im**2
return Complex((self.re * other.re + self.im * other.im) / denom,
(self.im * other.re - self.re * other.im) / denom)
elif isinstance(other, (int, float)):
return Complex(self.re / other, self.im / other)
else:
return NotImplemented
def __rdiv__(self, other):
"""
Permite la división cuando el número complejo está a la derecha del operador `/`.
- Esto maneja casos como `10 / q` donde `q` es un `Complex`.
"""
temp = Complex(other, 0)
return temp / self
def __str__(self):
"""
Genera una cadena de caracteres con la representación
de un número complejo.
- Devuelve la representación en forma de cadena como "a+bi".
"""
return "{:f}{:+f}i".format(self.re, self.im)
if __name__ == "__main__":
q = Complex(1., 5.)
w = Complex(3., 4.)
h = Complex(4.)
a = q + w # Suma de dos números complejos
a1 = a * 3 # Multiplicación de un complejo por un entero
a3 = -w - 5 * q # Operaciones mixtas con complejos y enteros
b = q * h # Producto de dos números complejos
c = w / h # División de dos números complejos
print(a)
print(a1)
print(b)
print(c)
Mejoras implementadas¶
Soporte para diferentes tipos numéricos: Ahora, la clase Complex puede manejar operaciones no solo entre números complejos, sino también con enteros y flotantes. Esto se logra mediante la implementación de los métodos especiales __add__, __sub__, __mul__, y __truediv__, así como sus contrapartes para operaciones donde el número complejo aparece a la derecha del operador (__radd__, __rsub__, __rmul__, y __rdiv__).
Operaciones aritméticas naturales: Gracias a estos métodos especiales, es posible realizar operaciones aritméticas de manera natural y directa entre números complejos y otros tipos numéricos, mejorando la flexibilidad y usabilidad de la clase Complex.
Manejo de casos no implementados: Si se intenta realizar una operación con un tipo de dato no soportado, se retorna NotImplemented, lo que permite a Python intentar otras operaciones definidas o lanzar un error apropiado.
Mayor legibilidad y claridad: Los métodos de la clase Complex ahora están mejor organizados y son más fáciles de entender, lo que facilita su uso en aplicaciones matemáticas y científicas.
Estas mejoras hacen que la clase Complex sea más robusta y flexible, permitiendo operaciones más intuitivas y eficaces con números complejos en Python.