abc en Python: clases abstractas

Publicado el: 06/08/2026
Tempo de leitura: 5 minutos
Arquitectura de software que representa clases abstractas con abc en Python

Los proyectos orientados a objetos suelen necesitar un contrato común para varias implementaciones. Una clase de pagos debe exponer procesar(); un repositorio necesita guardar() y buscar(); un plugin debe declarar métodos obligatorios. El módulo abc en Python proporciona clases base abstractas, métodos requeridos y subclasses virtuales para expresar estos contratos en runtime.

Esta guía explica ABC, ABCMeta, abstractmethod, register(), __subclasshook__() y update_abstractmethods(). Complementa nuestros artículos sobre types en Python, inspect, singledispatch, descriptors y dataclasses.

Qué es una clase abstracta

Una clase abstracta describe comportamiento esperado y puede ofrecer implementación parcial, pero no puede instanciarse mientras existan miembros abstractos sin resolver.

from abc import ABC, abstractmethod

class Almacenamiento(ABC):
    @abstractmethod
    def guardar(self, clave, valor):
        pass

    @abstractmethod
    def buscar(self, clave):
        pass

Llamar a Almacenamiento() genera TypeError. Una subclase concreta debe implementar todos los miembros abstractos.

Crear una implementación concreta

class AlmacenamientoMemoria(Almacenamiento):
    def __init__(self):
        self._datos = {}

    def guardar(self, clave, valor):
        self._datos[clave] = valor

    def buscar(self, clave):
        return self._datos[clave]

repositorio = AlmacenamientoMemoria()
repositorio.guardar("usuario", "Ana")
print(repositorio.buscar("usuario"))

La clase base documenta el contrato e impide crear implementaciones incompletas por accidente.

ABC como clase auxiliar

ABC usa ABCMeta como metaclass. Heredar de ABC es la forma más sencilla de declarar una base abstracta.

Una clase que ya necesita otra metaclass puede tener conflicto. Crea una metaclass compatible derivada de ABCMeta o reconsidera una jerarquía demasiado compleja.

abstractmethod

@abstractmethod marca comportamiento obligatorio. Debe ser el decorator más interno al combinarse con classmethod, staticmethod o property.

class Fabrica(ABC):
    @classmethod
    @abstractmethod
    def crear(cls, configuracion):
        pass

    @staticmethod
    @abstractmethod
    def validar(configuracion):
        pass

El orden importa porque abstractmethod marca el descriptor subyacente.

Propiedades abstractas

class Documento(ABC):
    @property
    @abstractmethod
    def titulo(self):
        pass

La subclase implementa otra propiedad.

class Reporte(Documento):
    def __init__(self, titulo):
        self._titulo = titulo

    @property
    def titulo(self):
        return self._titulo

Helpers antiguos como abstractproperty, abstractclassmethod y abstractstaticmethod continúan por compatibilidad, pero se prefieren las combinaciones modernas.

Los métodos abstractos pueden tener implementación

Un método abstracto no necesita contener solo pass. Puede proporcionar comportamiento común llamado mediante super().

class Exportador(ABC):
    @abstractmethod
    def exportar(self, datos):
        self._validar(datos)

    def _validar(self, datos):
        if not datos:
            raise ValueError("datos vacíos")

class ExportadorJSON(Exportador):
    def exportar(self, datos):
        super().exportar(datos)
        return json.dumps(datos)

Este patrón evita repetir validaciones y facilita herencia cooperativa.

Implementaciones parciales

Una subclase puede seguir siendo abstracta.

class AlmacenamientoConLog(Almacenamiento):
    def guardar(self, clave, valor):
        print("guardando", clave)
        return self._guardar_impl(clave, valor)

    @abstractmethod
    def _guardar_impl(self, clave, valor):
        pass

Implementa parte del contrato e introduce una obligación nueva.

Inspeccionar el estado abstracto

La metaclass mantiene __abstractmethods__.

print(Almacenamiento.__abstractmethods__)
print(AlmacenamientoMemoria.__abstractmethods__)

Los frameworks pueden usar inspect.isabstract() como API más clara.

ABCMeta directamente

from abc import ABCMeta

class Servicio(metaclass=ABCMeta):
    @abstractmethod
    def ejecutar(self):
        pass

La sintaxis explícita ayuda a comprender metaclasses, pero heredar de ABC suele ser más legible.

Subclases virtuales

register() permite considerar una clase existente como subclase sin modificar su herencia.

class Lector(ABC):
    @abstractmethod
    def leer(self):
        pass

class LectorLegado:
    def leer(self):
        return "datos"

Lector.register(LectorLegado)

print(issubclass(LectorLegado, Lector))
print(isinstance(LectorLegado(), Lector))

El registro no añade métodos, no ejecuta código de la ABC ni obliga a cumplir el contrato. Solo modifica issubclass() e isinstance().

Riesgos del registro virtual

class Incompleto:
    pass

Lector.register(Incompleto)
print(isinstance(Incompleto(), Lector))  # True

Registra únicamente clases que ya cumplen el contrato de forma semántica y conserva tests compartidos de conformidad.

register() como decorator

@Lector.register
class OtroLector:
    def leer(self):
        return "otro"

La intención queda cerca de la implementación, aunque crea dependencia directa de la ABC.

__subclasshook__()

Una ABC puede reconocer subclasses estructurales sin registro explícito.

class IterablePersonalizado(ABC):
    @classmethod
    def __subclasshook__(cls, C):
        if cls is IterablePersonalizado:
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

Las clases que definen __iter__ en su jerarquía pueden ser reconocidas por issubclass().

True, False o NotImplemented

__subclasshook__() puede devolver:

  • True para aceptar;
  • False para rechazar de forma fuerte;
  • NotImplemented para continuar con las reglas normales.

Prefiere NotImplemented cuando el hook no puede decidir.

Hooks conservadores

La presencia de un nombre no demuestra firma ni semántica compatible. Un método llamado leer puede hacer algo distinto.

Usa hooks en protocolos conocidos y simples. Los contratos ricos son más seguros con herencia explícita, Protocol y tests.

ABC frente a Protocol

Las ABC se centran en relaciones nominales de runtime e implementación compartida. typing.Protocol expresa tipado estructural para verificadores estáticos.

from typing import Protocol

class PuedeLeer(Protocol):
    def leer(self) -> str: ...

Una clase con método compatible satisface el protocolo sin herencia. Los protocols runtime-checkable ofrecen verificaciones limitadas y no validan firmas profundamente.

ABC frente a duck typing

Python puede simplemente llamar objeto.leer() y manejar AttributeError. Esto reduce acoplamiento.

Usa una ABC cuando aportan valor la documentación central, la implementación compartida, la prohibición de clases incompletas o las verificaciones de runtime.

Herencia múltiple

class Legible(ABC):
    @abstractmethod
    def leer(self): ...

class Escribible(ABC):
    @abstractmethod
    def escribir(self, datos): ...

class ArchivoVirtual(Legible, Escribible):
    def leer(self):
        return self.datos

    def escribir(self, datos):
        self.datos = datos

Mantén métodos cooperativos con super() y evita estado conflictivo.

Actualizar abstracción dinámicamente

Añadir una implementación después de crear la clase no recalcula automáticamente su estado. Usa update_abstractmethods().

from abc import update_abstractmethods

class Dinamica(ABC):
    @abstractmethod
    def ejecutar(self):
        pass

Dinamica.ejecutar = lambda self: "ok"
update_abstractmethods(Dinamica)

print(Dinamica().ejecutar())

La documentación oficial de abc recomienda esta función para modificaciones dinámicas.

Caches de ABC

ABCMeta guarda caches para acelerar issubclass(). get_cache_token() devuelve un token que cambia cuando se registran subclasses virtuales.

from abc import get_cache_token

antes = get_cache_token()
Lector.register(NuevoLector)
despues = get_cache_token()
assert antes != despues

Los frameworks con caches propios pueden invalidarlos cuando cambia el token.

Contratos de plugins

class Plugin(ABC):
    nombre: str

    @abstractmethod
    def iniciar(self, contexto):
        pass

    @abstractmethod
    def detener(self):
        pass

Al cargar plugins, confirma que la clase sea concreta, valida metadatos y maneja fallos. La herencia no proporciona aislamiento.

Dependencias y tests

class Aplicacion:
    def __init__(self, almacenamiento: Almacenamiento):
        self.almacenamiento = almacenamiento

Los tests pueden proporcionar un fake pequeño. Evita mocks demasiado permisivos que aceptan errores tipográficos.

Evolucionar la API

Añadir un método abstracto nuevo rompe todas las subclasses concretas. Es una modificación incompatible.

Considera una implementación predeterminada, una ABC nueva o una capability separada antes de volver obligatorio un comportamiento.

Errores frecuentes

  • Colocar @abstractmethod fuera de @classmethod o @property.
  • Suponer que una subclase virtual recibe métodos.
  • Registrar clases incompatibles.
  • Escribir hooks demasiado amplios.
  • Añadir métodos abstractos sin plan de compatibilidad.
  • Crear jerarquías profundas para compartir poco.
  • Tratar una ABC como aislamiento de plugins.
  • Modificar una clase sin update_abstractmethods().

Buenas prácticas

  • Mantén contratos pequeños y cohesivos.
  • Proporciona implementación común útil.
  • Usa super() en herencia cooperativa.
  • Registra subclasses virtuales tras probarlas.
  • Devuelve NotImplemented ante duda.
  • Compara ABC, Protocol y duck typing.
  • Trata nuevos abstractos como breaking changes.
  • Ejecuta una suite compartida de conformidad.

Conclusión

El módulo abc en Python define contratos de runtime, impide construir clases incompletas y comparte comportamiento entre implementaciones. ABC y abstractmethod cubren la mayoría de casos; el registro virtual y __subclasshook__() permiten integración estructural.

Las clases abstractas funcionan mejor con contratos pequeños, estables y claros. Hooks conservadores, tests de conformidad y evolución cuidadosa mantienen las extensiones explícitas sin convertir el proyecto en una jerarquía rígida.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Detailed image of a Burmese Python being held. Captured in Toluca, Mexico.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    urllib.parse en Python: maneja URLs

    Aprende urllib.parse en Python para dividir URLs, crear queries, codificar componentes y evitar riesgos con urljoin, redirects, logs y SSRF.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ipaddress en Python: redes IPv4 e IPv6

    Aprende ipaddress en Python para validar IPv4 e IPv6, calcular redes CIDR, dividir subredes y crear políticas de acceso más

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    queue en Python: coordina hilos

    Aprende queue en Python para coordinar hilos con FIFO, prioridad, backpressure, tracking, reintentos y shutdown seguro.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Chic portrait of a woman wearing trendy sunglasses reflecting numbers, captured in a modern setting.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    struct en Python: datos binarios

    Aprende struct en Python para empaquetar datos binarios, controlar endianness, reutilizar buffers y validar protocolos externos.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile en Python: crea TAR seguro

    Aprende tarfile en Python para crear TAR comprimido, inspeccionar miembros y extraer con filtros, límites y protección de rutas.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Row of colorful office binders neatly arranged on a shelf, ideal for organization concepts.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    gzip en Python: comprime archivos .gz

    Aprende gzip en Python para leer y escribir .gz, crear streams reproducibles, procesar datos grandes y limitar la expansión externa.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026