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

    Código y estructuras que representan tipos del runtime con el módulo types en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    types en Python: tipos del runtime

    Aprende types en Python para usar SimpleNamespace, MappingProxyType, tipos del runtime y creación dinámica de clases.

    Ler mais

    Tempo de leitura: 5 minutos
    06/08/2026
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    sched en Python: programa eventos

    Aprende sched en Python para programar eventos, controlar prioridades, cancelar tareas y crear recurrencia con reloj monotónico.

    Ler mais

    Tempo de leitura: 7 minutos
    05/08/2026
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    atexit en Python: ejecuta limpieza al salir

    Aprende atexit en Python para ejecutar limpieza al salir, controlar el orden LIFO y evitar problemas con threads, señales y

    Ler mais

    Tempo de leitura: 7 minutos
    05/08/2026
    Monitor con código binario que representa análisis de opcodes pickle con pickletools en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickletools en Python: analiza pickles

    Aprende pickletools en Python para desmontar pickles, analizar opcodes y optimizar flujos sin ejecutar datos no confiables.

    Ler mais

    Tempo de leitura: 6 minutos
    05/08/2026
    Código fuente en pantalla que representa análisis con tokenize en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tokenize en Python: analiza código fuente

    Aprende tokenize en Python para inspeccionar tokens, comentarios, indentación, codificación, posiciones y reconstruir código de forma segura.

    Ler mais

    Tempo de leitura: 7 minutos
    05/08/2026
    Desarrollador trabajando en automatización de build y compilación de directorios con compileall en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    compileall en Python: compila directorios

    Aprende compileall en Python para compilar directorios, generar pyc en paralelo, filtrar rutas y controlar optimización e invalidación.

    Ler mais

    Tempo de leitura: 6 minutos
    04/08/2026