Python

Python

Tutorial Python: Polimorfismo

Aprende polimorfismo en Python con ejemplos de duck typing y métodos polimórficos para escribir código flexible y reutilizable.

Aprende Python y certifícate

Concepto básico

El polimorfismo es uno de los pilares fundamentales de la programación orientada a objetos. La palabra proviene del griego "poly" (muchos) y "morphos" (formas), lo que refleja perfectamente su esencia: la capacidad de un objeto para tomar múltiples formas durante la ejecución de un programa.

En términos prácticos, el polimorfismo permite que objetos de diferentes clases respondan al mismo mensaje o método de manera distinta, cada uno según su propia implementación. Esta característica nos permite escribir código más flexible y reutilizable, ya que podemos tratar objetos de diferentes tipos de manera uniforme.

En Python, el polimorfismo se manifiesta de forma natural debido a su tipado dinámico. A diferencia de lenguajes más estrictos, Python no requiere que los objetos pertenezcan a una clase específica para poder invocar un método en ellos. Lo único que importa es que el objeto tenga implementado el método que estamos llamando.

Veamos un ejemplo sencillo para ilustrar este concepto:

def hacer_sonido(animal):
    animal.hablar()

class Perro:
    def hablar(self):
        print("¡Guau!")

class Gato:
    def hablar(self):
        print("¡Miau!")

class Pato:
    def hablar(self):
        print("¡Cuac!")

# Creamos instancias de diferentes animales
fido = Perro()
felix = Gato()
donald = Pato()

# La misma función funciona con diferentes tipos de objetos
hacer_sonido(fido)  # Imprime: ¡Guau!
hacer_sonido(felix)  # Imprime: ¡Miau!
hacer_sonido(donald)  # Imprime: ¡Cuac!

En este ejemplo, la función hacer_sonido() trabaja con cualquier objeto que tenga un método hablar(), sin importar a qué clase pertenezca. Cada animal "habla" a su manera, pero todos responden al mismo mensaje. Esto es la esencia del polimorfismo.

Tipos de polimorfismo en Python

En Python podemos identificar principalmente dos tipos de polimorfismo:

  • Polimorfismo de sobrecarga de métodos: Ocurre cuando una clase hija redefine un método de su clase padre. En Python, esto se conoce como sobreescritura de métodos.

  • Polimorfismo de interfaz: Cuando diferentes clases implementan los mismos métodos, permitiendo que sean tratadas de manera uniforme. En Python, esto se implementa a través del concepto de "duck typing" (que veremos en la siguiente sección).

Veamos un ejemplo del primer tipo, la sobreescritura de métodos:

class Animal:
    def desplazarse(self):
        print("El animal se desplaza")

class Pez(Animal):
    def desplazarse(self):
        print("El pez nada")

class Ave(Animal):
    def desplazarse(self):
        print("El ave vuela")

class Serpiente(Animal):
    def desplazarse(self):
        print("La serpiente repta")

# Creamos instancias de diferentes animales
animal_generico = Animal()
nemo = Pez()
piolin = Ave()
kaa = Serpiente()

# Cada animal se desplaza a su manera
animal_generico.desplazarse()  # Imprime: El animal se desplaza
nemo.desplazarse()             # Imprime: El pez nada
piolin.desplazarse()           # Imprime: El ave vuela
kaa.desplazarse()              # Imprime: La serpiente repta

En este ejemplo, cada clase hereda de Animal y sobreescribe el método desplazarse() para implementar su propio comportamiento. Cuando llamamos al método, Python ejecuta la versión correspondiente a la clase del objeto.

Ventajas del polimorfismo

El polimorfismo ofrece varias ventajas importantes:

  1. Flexibilidad: Permite tratar objetos de diferentes clases de manera uniforme.
  2. Extensibilidad: Facilita añadir nuevas clases sin modificar el código existente.
  3. Reutilización de código: Permite escribir funciones que trabajen con múltiples tipos de objetos.
  4. Abstracción: Nos permite centrarnos en lo que hacen los objetos, no en cómo lo hacen.

Polimorfismo con funciones y métodos integrados

Python también aplica el polimorfismo en sus funciones y métodos integrados. Por ejemplo, el operador + funciona de manera diferente según el tipo de datos:

# Con números realiza suma aritmética
print(5 + 3)        # Imprime: 8

# Con cadenas realiza concatenación
print("Hola " + "mundo")  # Imprime: Hola mundo

# Con listas realiza unión
print([1, 2] + [3, 4])    # Imprime: [1, 2, 3, 4]

De manera similar, la función len() funciona con diferentes tipos de colecciones:

# Longitud de una cadena
print(len("Python"))      # Imprime: 6

# Número de elementos en una lista
print(len([1, 2, 3, 4]))  # Imprime: 4

# Número de pares clave-valor en un diccionario
print(len({"a": 1, "b": 2}))  # Imprime: 2

Estos son ejemplos de polimorfismo paramétrico, donde una misma operación se comporta de manera diferente según el tipo de los argumentos.

Implementación del polimorfismo en clases propias

Podemos implementar comportamiento polimórfico en nuestras propias clases definiendo métodos con el mismo nombre. Por ejemplo, podemos crear diferentes formas geométricas que calculen su área de manera distinta:

class Forma:
    def area(self):
        pass  # Método base que será sobreescrito

class Rectangulo(Forma):
    def __init__(self, ancho, alto):
        self.ancho = ancho
        self.alto = alto
        
    def area(self):
        return self.ancho * self.alto

class Circulo(Forma):
    def __init__(self, radio):
        self.radio = radio
        
    def area(self):
        return 3.14159 * self.radio ** 2

# Función que trabaja con cualquier forma
def imprimir_area(forma):
    print(f"El área es: {forma.area()}")

# Creamos diferentes formas
rectangulo = Rectangulo(5, 4)
circulo = Circulo(3)

# La misma función funciona con diferentes formas
imprimir_area(rectangulo)  # Imprime: El área es: 20
imprimir_area(circulo)     # Imprime: El área es: 28.27431

En este ejemplo, la función imprimir_area() trabaja con cualquier objeto que tenga un método area(), sin importar cómo esté implementado internamente. Cada forma calcula su área según sus propias características, pero todas responden al mismo mensaje.

El polimorfismo es una herramienta poderosa que nos permite escribir código más modular, flexible y mantenible. En las siguientes secciones, exploraremos conceptos relacionados como el duck typing y los métodos polimórficos en mayor profundidad.

Duck typing

El duck typing es un concepto fundamental en Python que está estrechamente relacionado con el polimorfismo. Su nombre proviene de la expresión en inglés "If it walks like a duck and quacks like a duck, then it probably is a duck" (Si camina como un pato y grazna como un pato, probablemente sea un pato).

En Python, el duck typing se refiere a la forma en que el lenguaje determina si un objeto puede ser utilizado para un propósito particular. A diferencia de los lenguajes con tipado estático, Python no se preocupa por el tipo o la clase de un objeto, sino por su comportamiento - específicamente, qué métodos y atributos tiene disponibles.

Cómo funciona el duck typing

En lugar de verificar el tipo de un objeto, Python intenta usar el objeto como si tuviera los métodos y atributos necesarios. Si los tiene, el código funciona; si no, se produce un error en tiempo de ejecución.

Veamos un ejemplo sencillo:

def calcular_precio_total(productos):
    total = 0
    for producto in productos:
        total += producto.precio
    return total

Esta función no especifica qué tipo de objetos debe contener la lista productos. Lo único que importa es que cada objeto tenga un atributo precio. Podrían ser instancias de diferentes clases:

class Libro:
    def __init__(self, titulo, precio):
        self.titulo = titulo
        self.precio = precio

class Electronico:
    def __init__(self, nombre, precio, garantia):
        self.nombre = nombre
        self.precio = precio
        self.garantia = garantia

# Creamos una lista mixta de productos
carrito = [
    Libro("Python Cookbook", 39.99),
    Electronico("Teclado", 59.99, "2 años"),
    Libro("Fluent Python", 49.99)
]

# La función funciona con cualquier objeto que tenga un atributo 'precio'
print(f"Total: ${calcular_precio_total(carrito):.2f}")  # Imprime: Total: $149.97

La función calcular_precio_total() trabaja con cualquier colección de objetos que tengan un atributo precio, sin importar a qué clase pertenezcan o qué otros atributos tengan.

Duck typing vs. herencia

Una ventaja importante del duck typing es que no requiere relaciones de herencia entre clases. Dos clases pueden ser completamente independientes, pero si implementan los mismos métodos, pueden ser tratadas de manera uniforme.

Comparemos el enfoque de duck typing con el enfoque tradicional basado en herencia:

# Enfoque con herencia
class Reproductor:
    def reproducir(self):
        raise NotImplementedError("Las subclases deben implementar este método")

class ReproductorMP3(Reproductor):
    def reproducir(self):
        print("Reproduciendo archivo MP3...")

class ReproductorWAV(Reproductor):
    def reproducir(self):
        print("Reproduciendo archivo WAV...")

# Enfoque con duck typing
class ArchivoMP3:
    def reproducir(self):
        print("Reproduciendo archivo MP3...")

class ArchivoWAV:
    def reproducir(self):
        print("Reproduciendo archivo WAV...")

class ArchivoOGG:
    def reproducir(self):
        print("Reproduciendo archivo OGG...")

Con el enfoque de herencia, todas las clases deben heredar de Reproductor. Con duck typing, las clases pueden ser completamente independientes, siempre que implementen el método reproducir().

La función que utiliza estos objetos sería la misma en ambos casos:

def reproducir_audio(archivo):
    archivo.reproducir()

# Funciona con cualquier objeto que tenga un método 'reproducir'
mp3 = ArchivoMP3()
wav = ArchivoWAV()
ogg = ArchivoOGG()

reproducir_audio(mp3)  # Imprime: Reproduciendo archivo MP3...
reproducir_audio(wav)  # Imprime: Reproduciendo archivo WAV...
reproducir_audio(ogg)  # Imprime: Reproduciendo archivo OGG...

Ventajas del duck typing

El duck typing ofrece varias ventajas significativas:

  • Flexibilidad: Permite crear código que trabaja con tipos de objetos que ni siquiera existían cuando se escribió el código.
  • Desacoplamiento: Reduce la dependencia entre diferentes partes del código.
  • Simplicidad: Elimina la necesidad de jerarquías de clases complejas.
  • Extensibilidad: Facilita añadir nuevos tipos sin modificar el código existente.

Duck typing en la biblioteca estándar

La biblioteca estándar de Python hace un uso extensivo del duck typing. Por ejemplo, muchas funciones que trabajan con secuencias no requieren que el objeto sea específicamente una lista o una tupla, solo que se comporte como una secuencia:

# La función sum() funciona con cualquier iterable que contenga números
print(sum([1, 2, 3]))          # Lista: 6
print(sum((1, 2, 3)))          # Tupla: 6
print(sum({1, 2, 3}))          # Conjunto: 6
print(sum(range(1, 4)))        # Objeto range: 6

# La función sorted() funciona con cualquier iterable
print(sorted("python"))        # Cadena: ['h', 'n', 'o', 'p', 't', 'y']
print(sorted({'a': 1, 'c': 3, 'b': 2}))  # Diccionario (ordena las claves): ['a', 'b', 'c']

Protocolos en Python

En Python, los protocolos son conjuntos de métodos que un objeto debe implementar para comportarse de cierta manera. Son como interfaces informales. Algunos ejemplos comunes:

  • Protocolo de iteración: Objetos que implementan __iter__() y __next__() pueden usarse en bucles for.
  • Protocolo de contexto: Objetos que implementan __enter__() y __exit__() pueden usarse con la declaración with.
  • Protocolo de secuencia: Objetos que implementan __len__() y __getitem__() pueden usarse como secuencias.

Veamos un ejemplo de una clase que implementa el protocolo de iteración:

class Contador:
    def __init__(self, inicio, fin):
        self.inicio = inicio
        self.fin = fin
        self.valor = inicio
    
    def __iter__(self):
        self.valor = self.inicio
        return self
    
    def __next__(self):
        if self.valor > self.fin:
            raise StopIteration
        valor_actual = self.valor
        self.valor += 1
        return valor_actual

# Podemos usar nuestra clase en un bucle for
for num in Contador(1, 5):
    print(num)  # Imprime: 1, 2, 3, 4, 5

Aunque Contador no hereda de ninguna clase específica, puede usarse en un bucle for porque implementa el protocolo de iteración.

Verificación de tipos en duck typing

Si bien el duck typing se basa en la idea de "intentar y ver qué pasa", a veces es útil verificar si un objeto tiene ciertos métodos antes de usarlos. Python proporciona la función hasattr() para este propósito:

def procesar_archivo(archivo):
    if hasattr(archivo, 'leer') and callable(archivo.leer):
        contenido = archivo.leer()
        # Procesar el contenido
    else:
        raise TypeError("El objeto no tiene un método 'leer'")

Sin embargo, en la práctica, muchos programadores de Python prefieren el enfoque EAFP (Easier to Ask for Forgiveness than Permission - Es más fácil pedir perdón que permiso):

def procesar_archivo(archivo):
    try:
        contenido = archivo.leer()
        # Procesar el contenido
    except AttributeError:
        raise TypeError("El objeto no tiene un método 'leer'")

Este enfoque es más idiomático en Python y evita la verificación redundante de atributos.

Duck typing y anotaciones de tipo

Con la introducción de las anotaciones de tipo en Python 3.5+, surgió la pregunta de cómo conciliar el duck typing con la verificación estática de tipos. La respuesta vino en forma de protocolos estructurales en el módulo typing:

from typing import Protocol, List

class Producto(Protocol):
    precio: float

def calcular_precio_total(productos: List[Producto]) -> float:
    total = 0
    for producto in productos:
        total += producto.precio
    return total

Esto permite mantener la flexibilidad del duck typing mientras se proporciona información para herramientas de verificación estática de tipos como mypy.

El duck typing es una característica fundamental de Python que promueve un estilo de programación flexible y pragmático. En lugar de preocuparse por las jerarquías de clases, nos permite centrarnos en lo que los objetos pueden hacer, lo que resulta en código más simple y adaptable.

Métodos polimórficos

Los métodos polimórficos son el mecanismo práctico mediante el cual implementamos el polimorfismo en Python. Estos métodos comparten el mismo nombre en diferentes clases pero tienen implementaciones específicas adaptadas a cada clase. Cuando invocamos un método polimórfico, Python ejecuta la versión correspondiente al tipo de objeto con el que estamos trabajando.

En Python, los métodos polimórficos se pueden implementar de varias formas, cada una con sus propias características y casos de uso. Veamos las principales técnicas para crear y utilizar métodos polimórficos.

Sobreescritura de métodos

La forma más común de crear métodos polimórficos es mediante la sobreescritura (override) de métodos en clases derivadas. Cuando una clase hereda de otra, puede redefinir los métodos de la clase base para proporcionar una implementación específica:

class Instrumento:
    def tocar(self):
        print("Tocando un instrumento genérico")
    
    def afinar(self):
        print("Afinando instrumento")
    
class Guitarra(Instrumento):
    def tocar(self):
        print("Rasgueando las cuerdas de la guitarra")
        
class Piano(Instrumento):
    def tocar(self):
        print("Presionando teclas del piano")
        
class Bateria(Instrumento):
    def tocar(self):
        print("Golpeando los tambores y platillos")

Ahora podemos crear una función que trabaje con cualquier tipo de instrumento:

def concierto(instrumento):
    instrumento.afinar()
    instrumento.tocar()

# Creamos diferentes instrumentos
guitarra = Guitarra()
piano = Piano()
bateria = Bateria()

# La misma función funciona con diferentes instrumentos
concierto(guitarra)
# Salida:
# Afinando instrumento
# Rasgueando las cuerdas de la guitarra

concierto(piano)
# Salida:
# Afinando instrumento
# Presionando teclas del piano

Observa que el método afinar() no fue sobreescrito, por lo que se utiliza la implementación de la clase base. Sin embargo, cada instrumento implementa su propia versión del método tocar().

Métodos abstractos

Para garantizar que las clases derivadas implementen ciertos métodos, podemos utilizar métodos abstractos mediante el módulo abc (Abstract Base Classes):

from abc import ABC, abstractmethod

class FiguraGeometrica(ABC):
    @abstractmethod
    def calcular_area(self):
        pass
    
    @abstractmethod
    def calcular_perimetro(self):
        pass
    
    def describir(self):
        return f"Área: {self.calcular_area()}, Perímetro: {self.calcular_perimetro()}"

class Rectangulo(FiguraGeometrica):
    def __init__(self, base, altura):
        self.base = base
        self.altura = altura
    
    def calcular_area(self):
        return self.base * self.altura
    
    def calcular_perimetro(self):
        return 2 * (self.base + self.altura)

class Circulo(FiguraGeometrica):
    def __init__(self, radio):
        self.radio = radio
    
    def calcular_area(self):
        return 3.14159 * self.radio ** 2
    
    def calcular_perimetro(self):
        return 2 * 3.14159 * self.radio

Los métodos abstractos definen una interfaz que todas las clases derivadas deben implementar. Si intentamos crear una instancia de una clase que no implementa todos los métodos abstractos, Python lanzará un error:

# Esto funcionará correctamente
rectangulo = Rectangulo(5, 3)
print(rectangulo.describir())  # Área: 15, Perímetro: 16

circulo = Circulo(4)
print(circulo.describir())  # Área: 50.26544, Perímetro: 25.13272

# Esto causaría un error
class TrianguloIncompleto(FiguraGeometrica):
    def calcular_area(self):
        return 10
    # Falta implementar calcular_perimetro()

# TypeError: Can't instantiate abstract class TrianguloIncompleto with abstract method calcular_perimetro

Métodos mágicos (dunder methods)

Los métodos mágicos (también llamados métodos dunder por su doble guion bajo o "double underscore") son una forma poderosa de implementar polimorfismo en Python. Estos métodos permiten que nuestras clases respondan a operadores y funciones integradas:

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, otro):
        return Vector2D(self.x + otro.x, self.y + otro.y)
    
    def __mul__(self, escalar):
        return Vector2D(self.x * escalar, self.y * escalar)
    
    def __str__(self):
        return f"({self.x}, {self.y})"

# Creamos vectores
v1 = Vector2D(3, 4)
v2 = Vector2D(1, 2)

# Usamos operadores con nuestros objetos
v3 = v1 + v2
print(v3)  # (4, 6)

v4 = v1 * 2
print(v4)  # (6, 8)

Los métodos mágicos nos permiten definir cómo nuestros objetos deben comportarse con operadores y funciones integradas de Python. Algunos métodos mágicos comunes incluyen:

  • __str__ y __repr__: Para representación como cadena
  • __len__: Para obtener la longitud con len()
  • __getitem__ y __setitem__: Para acceso con notación de índice obj[key]
  • __eq__, __lt__, etc.: Para comparaciones
  • __call__: Para hacer que el objeto sea llamable como una función

Métodos de clase y métodos estáticos

Los métodos de clase y métodos estáticos también pueden ser polimórficos:

class Conversor:
    @staticmethod
    def convertir(valor):
        return valor  # Implementación base
    
class ConversorTemperatura(Conversor):
    @staticmethod
    def convertir(celsius):
        # Convierte de Celsius a Fahrenheit
        return (celsius * 9/5) + 32
    
class ConversorLongitud(Conversor):
    @staticmethod
    def convertir(metros):
        # Convierte de metros a pies
        return metros * 3.28084

# Uso polimórfico
print(ConversorTemperatura.convertir(25))  # 77.0
print(ConversorLongitud.convertir(10))     # 32.8084

Implementación de múltiples interfaces

A diferencia de otros lenguajes, Python no tiene un concepto formal de interfaces. Sin embargo, gracias al duck typing, podemos implementar múltiples "interfaces" simplemente proporcionando los métodos necesarios:

class ArchivoTexto:
    def __init__(self, contenido):
        self.contenido = contenido
    
    def leer(self):
        return self.contenido
    
    def escribir(self, texto):
        self.contenido = texto
    
    # Implementa protocolo de contexto
    def __enter__(self):
        print("Abriendo archivo")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Cerrando archivo")
    
    # Implementa protocolo de iteración
    def __iter__(self):
        self.indice = 0
        self.lineas = self.contenido.split('\n')
        return self
    
    def __next__(self):
        if self.indice >= len(self.lineas):
            raise StopIteration
        linea = self.lineas[self.indice]
        self.indice += 1
        return linea

Esta clase implementa múltiples protocolos:

# Como objeto de lectura/escritura
archivo = ArchivoTexto("Línea 1\nLínea 2\nLínea 3")
print(archivo.leer())
archivo.escribir("Nuevo contenido")

# Como administrador de contexto (with)
with ArchivoTexto("Hola mundo") as f:
    print(f.leer())
# Salida:
# Abriendo archivo
# Hola mundo
# Cerrando archivo

# Como iterable
archivo = ArchivoTexto("Primera línea\nSegunda línea\nTercera línea")
for linea in archivo:
    print(f"- {linea}")
# Salida:
# - Primera línea
# - Segunda línea
# - Tercera línea

Métodos polimórficos con parámetros variables

Los métodos polimórficos pueden tener diferentes parámetros en diferentes clases, lo que permite una mayor flexibilidad:

class Notificador:
    def enviar(self, mensaje):
        print(f"Enviando: {mensaje}")

class NotificadorEmail(Notificador):
    def enviar(self, mensaje, asunto="Sin asunto", destinatario="usuario@ejemplo.com"):
        print(f"Enviando email a {destinatario}")
        print(f"Asunto: {asunto}")
        print(f"Mensaje: {mensaje}")

class NotificadorSMS(Notificador):
    def enviar(self, mensaje, numero="123456789"):
        print(f"Enviando SMS al {numero}: {mensaje}")

Sin embargo, al usar estos objetos de manera polimórfica, debemos tener cuidado de proporcionar solo los parámetros que todas las implementaciones aceptan:

def notificar_usuario(notificador, mensaje):
    # Solo usamos el parámetro común a todas las implementaciones
    notificador.enviar(mensaje)

notificar_usuario(Notificador(), "Alerta general")
notificar_usuario(NotificadorEmail(), "Tu cuenta ha sido verificada")
notificar_usuario(NotificadorSMS(), "Código de verificación: 1234")

Patrones comunes con métodos polimórficos

Existen varios patrones de diseño que aprovechan los métodos polimórficos:

  • Patrón Estrategia: Define una familia de algoritmos intercambiables:
class EstrategiaOrdenamiento:
    def ordenar(self, datos):
        pass

class OrdenamientoBurbuja(EstrategiaOrdenamiento):
    def ordenar(self, datos):
        print("Ordenando con método burbuja")
        # Implementación del algoritmo burbuja
        return sorted(datos)  # Simplificado para el ejemplo

class OrdenamientoRapido(EstrategiaOrdenamiento):
    def ordenar(self, datos):
        print("Ordenando con método rápido")
        # Implementación del algoritmo quicksort
        return sorted(datos)  # Simplificado para el ejemplo

class Ordenador:
    def __init__(self, estrategia=None):
        self.estrategia = estrategia or OrdenamientoBurbuja()
    
    def ordenar(self, datos):
        return self.estrategia.ordenar(datos)
    
    def cambiar_estrategia(self, estrategia):
        self.estrategia = estrategia

# Uso
ordenador = Ordenador()
print(ordenador.ordenar([3, 1, 4, 1, 5, 9, 2]))

# Cambiamos la estrategia
ordenador.cambiar_estrategia(OrdenamientoRapido())
print(ordenador.ordenar([3, 1, 4, 1, 5, 9, 2]))
  • Patrón Comando: Encapsula una solicitud como un objeto:
class Comando:
    def ejecutar(self):
        pass
    
    def deshacer(self):
        pass

class ComandoInsertar(Comando):
    def __init__(self, documento, texto):
        self.documento = documento
        self.texto = texto
        self.posicion = len(documento.contenido)
    
    def ejecutar(self):
        self.documento.contenido += self.texto
    
    def deshacer(self):
        self.documento.contenido = self.documento.contenido[:-len(self.texto)]

class ComandoBorrar(Comando):
    def __init__(self, documento, cantidad):
        self.documento = documento
        self.cantidad = cantidad
        self.texto_borrado = ""
    
    def ejecutar(self):
        if len(self.documento.contenido) >= self.cantidad:
            self.texto_borrado = self.documento.contenido[-self.cantidad:]
            self.documento.contenido = self.documento.contenido[:-self.cantidad]
    
    def deshacer(self):
        self.documento.contenido += self.texto_borrado

class Documento:
    def __init__(self):
        self.contenido = ""
        self.historial = []
    
    def ejecutar_comando(self, comando):
        comando.ejecutar()
        self.historial.append(comando)
    
    def deshacer(self):
        if self.historial:
            comando = self.historial.pop()
            comando.deshacer()

Consideraciones de rendimiento

Los métodos polimórficos en Python son muy flexibles, pero pueden tener implicaciones de rendimiento:

  • La resolución dinámica de métodos (buscar el método correcto en tiempo de ejecución) puede ser más lenta que las llamadas directas.
  • El duck typing puede ocultar errores hasta el tiempo de ejecución.

Para casos donde el rendimiento es crítico, podemos considerar:

# Verificación explícita de tipo (cuando sea necesario)
def procesar_figura(figura):
    if not isinstance(figura, FiguraGeometrica):
        raise TypeError("Se esperaba una FiguraGeometrica")
    return figura.calcular_area()

# Caching de métodos para evitar búsquedas repetidas
def optimizar_llamadas(objeto, metodo_nombre):
    metodo = getattr(objeto, metodo_nombre)
    def wrapper(*args, **kwargs):
        return metodo(*args, **kwargs)
    return wrapper

Sin embargo, en la mayoría de los casos, la claridad y flexibilidad del código son más importantes que estas optimizaciones prematuras.

Los métodos polimórficos son una herramienta fundamental en Python que nos permite escribir código más flexible, extensible y mantenible. Al comprender cómo implementarlos y utilizarlos efectivamente, podemos aprovechar todo el poder del polimorfismo en nuestras aplicaciones.

Aprende Python online

Otros ejercicios de programación de Python

Evalúa tus conocimientos de esta lección Polimorfismo con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Módulo math

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

Todas las lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Python

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender el concepto de polimorfismo y su importancia en la programación orientada a objetos.
  • Identificar los tipos de polimorfismo en Python, incluyendo sobreescritura de métodos y polimorfismo de interfaz.
  • Entender el duck typing como mecanismo de polimorfismo basado en el comportamiento de los objetos.
  • Aprender a implementar métodos polimórficos mediante sobreescritura, métodos abstractos y métodos mágicos.
  • Reconocer las ventajas y aplicaciones prácticas del polimorfismo y duck typing en el desarrollo de código modular y extensible.