Clases Abstractas en Python

📅 Actualizado en marzo 2026 📊 Nivel: Intermedio ⏱️ 13 min de lectura
📋 Contenido de esta lección

¿Qué es una clase abstracta?

Una clase abstracta es una clase que no puedes instanciar directamente. Existe para ser heredada. Define la estructura —qué métodos deben existir— pero delega la implementación a las subclases. Es, en esencia, un contrato: quien firme el contrato (quien herede) se compromete a implementar lo que se le exige.

Manos firmando un documento legal con bolígrafo rojo sobre una mesa de madera
Firmar un contrato impone obligaciones concretas. Heredar de una clase abstracta hace lo mismo: la subclase se compromete a implementar todos los métodos exigidos.

La idea es tan antigua como la programación orientada a objetos, pero en Python cobra un matiz especial: el lenguaje no obliga a declarar tipos, y sin clases abstractas cualquiera puede heredar de tu clase y olvidarse de implementar un método crítico. El error aparecerá en tiempo de ejecución, en el peor momento posible. Las clases abstractas convierten ese error tardío en un error inmediato y descriptivo en el momento de la instanciación.

Medallón de Platón
Omnia, Paulle, nostri ingenii acumina in formis rerum posita sunt
«Todo el filo de nuestro ingenio reside en las formas de las cosas»
Platón · Teoría de las Ideas · s. IV a.C. (adaptación)
La clase abstracta es la "Idea platónica": la forma perfecta que las implementaciones concretas deben materializar. Circulo y Rectangulo son sombras en la caverna; FiguraGeometrica es la forma real.

Python implementa las clases abstractas a través del módulo abc (Abstract Base Classes) de la biblioteca estándar. Su uso es elegante y muy pythonico.

ABC y @abstractmethod

Para crear una clase abstracta necesitas dos cosas: heredar de ABC y marcar los métodos obligatorios con el decorador @abstractmethod.

from abc import ABC, abstractmethod

class FiguraGeometrica(ABC):
    """Clase abstracta para todas las figuras geométricas."""

    @abstractmethod
    def area(self) -> float:
        """Devuelve el área de la figura."""
        ...

    @abstractmethod
    def perimetro(self) -> float:
        """Devuelve el perímetro de la figura."""
        ...

Los tres puntos (...) son la convención para indicar que el cuerpo del método es intencionalmente vacío. También puedes usar pass. Algunos desarrolladores prefieren incluir un docstring en lugar del ... para documentar qué debe hacer el método.

Intenta instanciar esta clase y Python te detiene en seco:

f = FiguraGeometrica()
# TypeError: Can't instantiate abstract class FiguraGeometrica
# with abstract methods area, perimetro

El mensaje de error es explícito: te dice exactamente qué métodos faltan. Eso es mucho más útil que un AttributeError genérico apareciendo tres llamadas después.

Diagrama de jerarquía ABC en Python con FiguraGeometrica abstracta y subclases Circulo, Rectangulo y FiguraIncompleta
La clase abstracta FiguraGeometrica actúa como contrato. Circulo y Rectangulo implementan todos los métodos: instanciables. FiguraIncompleta omite perimetro(): Python lanza TypeError.

Implementar una subclase concreta

Una subclase es "concreta" cuando implementa todos los métodos abstractos. Hasta ese momento, sigue siendo abstracta de facto.

import math

class Circulo(FiguraGeometrica):
    def __init__(self, radio: float):
        self.radio = radio

    def area(self) -> float:
        return math.pi * self.radio ** 2

    def perimetro(self) -> float:
        return 2 * math.pi * self.radio


class Rectangulo(FiguraGeometrica):
    def __init__(self, ancho: float, alto: float):
        self.ancho = ancho
        self.alto = alto

    def area(self) -> float:
        return self.ancho * self.alto

    def perimetro(self) -> float:
        return 2 * (self.ancho + self.alto)

Ahora sí puedes instanciarlas:

c = Circulo(5)
print(c.area())       # 78.53981633974483
print(c.perimetro())  # 31.41592653589793

r = Rectangulo(4, 6)
print(r.area())       # 24
print(r.perimetro())  # 20

Y lo mejor: puedes usarlas de forma polimórfica. Cualquier función que espere una FiguraGeometrica funcionará con cualquier subclase sin cambiar una línea de código:

def imprimir_info(figura: FiguraGeometrica):
    print(f"Área: {figura.area():.2f}, Perímetro: {figura.perimetro():.2f}")

figuras = [Circulo(3), Rectangulo(2, 5), Circulo(7)]
for f in figuras:
    imprimir_info(f)

Varios métodos abstractos

Puedes declarar tantos métodos abstractos como necesites. Una clase puede incluso heredar de múltiples ABCs, acumulando todos sus contratos:

from abc import ABC, abstractmethod

class Serializable(ABC):
    @abstractmethod
    def to_dict(self) -> dict:
        ...

    @abstractmethod
    def to_json(self) -> str:
        ...


class Validable(ABC):
    @abstractmethod
    def es_valido(self) -> bool:
        ...


class Producto(FiguraGeometrica, Serializable, Validable):
    # Debe implementar: area(), perimetro(), to_dict(), to_json(), es_valido()
    ...

Si olvidas alguno, Python te lo dice en el momento de instanciar, con la lista completa de los métodos pendientes. Es como tener un compilador de tipos de forma completamente dinámica.

Métodos concretos en una ABC

Una clase abstracta puede contener métodos perfectamente implementados. De hecho, este es uno de sus superpoderes respecto a las interfaces puras de otros lenguajes: puedes definir comportamiento compartido que todas las subclases heredan automáticamente.

class Animal(ABC):
    def __init__(self, nombre: str):
        self.nombre = nombre

    @abstractmethod
    def hablar(self) -> str:
        ...

    # Método concreto: todas las subclases lo heredan
    def presentarse(self) -> str:
        return f"Soy {self.nombre} y digo: {self.hablar()}"


class Perro(Animal):
    def hablar(self) -> str:
        return "¡Guau!"


class Gato(Animal):
    def hablar(self) -> str:
        return "¡Miau!"


p = Perro("Rex")
print(p.presentarse())  # Soy Rex y digo: ¡Guau!

g = Gato("Misi")
print(g.presentarse())  # Soy Misi y digo: ¡Miau!

Fíjate en el patrón: presentarse() llama a hablar(), que es abstracto. El método concreto delega en el abstracto. Este es el núcleo del patrón Template Method: defines el esqueleto del algoritmo en la clase base y dejas que las subclases rellenen los pasos específicos.

Mano de persona rellenando un formulario con líneas en blanco sujetado en un portapapeles
Un formulario en blanco define exactamente qué información se necesita. La clase abstracta hace lo mismo: especifica qué métodos deben implementarse, sin dictar cómo.

@property abstracta

Puedes combinar @property con @abstractmethod para exigir que las subclases expongan un atributo como propiedad. El orden de los decoradores importa: primero @property, luego @abstractmethod.

from abc import ABC, abstractmethod

class Vehiculo(ABC):

    @property
    @abstractmethod
    def velocidad_maxima(self) -> int:
        """Velocidad máxima en km/h."""
        ...

    @property
    @abstractmethod
    def tipo_combustible(self) -> str:
        ...


class Coche(Vehiculo):

    @property
    def velocidad_maxima(self) -> int:
        return 220

    @property
    def tipo_combustible(self) -> str:
        return "gasolina"


c = Coche()
print(c.velocidad_maxima)   # 220
print(c.tipo_combustible)   # gasolina

Esto es útil cuando quieres que ciertos datos sean obligatoriamente accesibles como atributos (sin paréntesis), no como llamadas a método.

Verificar la jerarquía

Las funciones isinstance() e issubclass() funcionan con ABCs exactamente igual que con clases normales:

c = Circulo(5)

isinstance(c, Circulo)          # True
isinstance(c, FiguraGeometrica) # True — es un Circulo, pero TAMBIÉN una FiguraGeometrica
issubclass(Circulo, FiguraGeometrica)  # True

# Puedes ver qué métodos abstractos tiene pendientes una clase
print(FiguraGeometrica.__abstractmethods__)
# frozenset({'area', 'perimetro'})

El atributo __abstractmethods__ es un frozenset con los nombres de los métodos aún no implementados. Si la subclase los implementa todos, el frozenset queda vacío y la clase es instanciable.

También existe el método register() de ABCMeta, que permite registrar una clase como "virtual subclass" sin herencia real. Es útil para integrar código heredado o de terceros:

# Una clase que NO hereda de FiguraGeometrica pero queremos que pase isinstance
class FiguraExterna:
    def area(self): return 0.0
    def perimetro(self): return 0.0

FiguraGeometrica.register(FiguraExterna)

f = FiguraExterna()
print(isinstance(f, FiguraGeometrica))  # True

Ojo: register() no verifica que la clase realmente implemente los métodos. Es un contrato de honor, no forzado.

collections.abc

La biblioteca estándar de Python incluye un módulo lleno de clases abstractas predefinidas para tipos de datos: collections.abc. Cuando creas una colección personalizada, heredar de ellas es la forma correcta de hacerlo:

from collections.abc import Sequence

class MiLista(Sequence):
    def __init__(self, datos):
        self._datos = list(datos)

    def __getitem__(self, index):
        return self._datos[index]

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

    # ¡Ya tienes gratis: __contains__, __iter__, __reversed__, index, count!

ml = MiLista([3, 1, 4, 1, 5])
print(len(ml))       # 5
print(3 in ml)       # True
print(ml.count(1))   # 2

Implementando solo dos métodos abstractos (__getitem__ y __len__), obtienes 5 métodos concretos de regalo. Las ABCs de collections.abc no son solo contratos: son mixins con implementaciones inteligentes.

Las más útiles: Sequence, MutableSequence, Mapping, MutableMapping, Set, Iterable, Iterator, Callable.

Ficha de referencia con patrones de uso de ABC en Python: definición, implementación, abstractproperty, herencia múltiple, TypeError, register, collections.abc y tabla de decisión
Referencia rápida de clases abstractas en Python: desde la importación hasta la tabla de decisión ABC vs Protocol.

¿Cuándo usarlas?

Las clases abstractas son la herramienta adecuada cuando:

  • Diseñas un framework o biblioteca que otros van a extender. Defines la arquitectura, ellos ponen la carne.
  • Necesitas polimorfismo garantizado: varias clases que se usan de forma intercambiable y que deben tener la misma interfaz.
  • Quieres compartir lógica común entre subclases (métodos concretos) sin dejar de exigir implementación de los pasos específicos (Template Method).
  • El error de "olvidé implementar un método" tiene consecuencias graves en producción y quieres detectarlo lo antes posible.

Cuándo no usarlas: si solo necesitas un "contrato de comportamiento" sin herencia, considera los Protocol de typing (introducidos en Python 3.8). Los Protocols permiten duck typing estructural sin herencia explícita. La diferencia clave: ABC fuerza herencia; Protocol no.

from typing import Protocol

class Serializable(Protocol):
    def to_dict(self) -> dict: ...

# Cualquier clase con to_dict() pasa el check, sin heredar de nada

❓ Preguntas frecuentes

❓ Preguntas frecuentes sobre Clases Abstractas en Python

Las dudas más comunes respondidas de forma clara y directa.

Python no tiene interfaces como concepto del lenguaje, pero las clases abstractas (ABC) cumplen ese papel. Una ABC puede además tener métodos concretos (con implementación) que las subclases heredan directamente, algo que las interfaces puras de otros lenguajes no permiten.
No. Python lanza TypeError si intentas instanciar una clase que tiene uno o más métodos marcados con @abstractmethod. Solo puedes crear objetos de subclases que implementen todos esos métodos.
La subclase también se vuelve abstracta de facto. Python no lanzará error al definirla, pero sí al intentar instanciarla, indicando exactamente qué métodos faltan.
Técnicamente no: puedes usar ABCMeta como metaclase directamente con class MiClase(metaclass=ABCMeta). Pero heredar de ABC es la forma idiomática y más limpia. ABC es simplemente una clase vacía que ya tiene ABCMeta como metaclase.
Es un módulo de la biblioteca estándar con clases abstractas predefinidas para tipos de datos: Sequence, Mapping, Iterable, Callable, etc. Si creas una colección personalizada, heredar de ellas garantiza que implementas todos los métodos necesarios y que tu clase pasa isinstance correctamente.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Clases Abstractas en Python? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!