abc: crea clases abstractas y contratos

Actualizado el: 20/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 asíncrono que representa asyncio.eager_task_factory en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    asyncio.eager_task_factory: reduce overhead de tareas

    Aprende asyncio.eager_task_factory en Python para reducir overhead, entender cambios de orden y optimizar corrutinas cortas con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    14/09/2026
    Desarrollador trabajando con timestamps UTC y calendar.timegm en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    calendar.timegm: convierte UTC a timestamp Unix

    Aprende calendar.timegm en Python para convertir fechas UTC en timestamps Unix y evitar errores de zona horaria y unidades.

    Ler mais

    Tempo de leitura: 6 minutos
    14/09/2026
    Programador analizando código para identificar tipos MIME de archivos con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    mimetypes.guess_file_type: detecta tipos MIME

    Aprende mimetypes.guess_file_type en Python para detectar tipos MIME en rutas, URLs, uploads y respuestas HTTP con fallbacks seguros.

    Ler mais

    Tempo de leitura: 4 minutos
    13/09/2026
    Componentes de servidor que representan intérpretes Python aislados ejecutándose en paralelo
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    InterpreterPoolExecutor: paralelismo real en Python

    Aprende InterpreterPoolExecutor en Python para tareas CPU-bound, intérpretes aislados, paralelismo real y concurrencia segura.

    Ler mais

    Tempo de leitura: 6 minutos
    13/09/2026
    Microprocesador que representa las CPU disponibles para un proceso Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    os.process_cpu_count: cuenta CPUs disponibles

    Aprende os.process_cpu_count en Python para dimensionar workers según las CPU disponibles para el proceso.

    Ler mais

    Tempo de leitura: 4 minutos
    12/09/2026
    Portátil con código digital que representa datos BLOB en SQLite
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    sqlite3.Blob: lee BLOBs sin cargar todo en memoria

    Aprende sqlite3.Blob en Python para leer y escribir BLOBs por partes, reducir memoria y manejar datos binarios en SQLite.

    Ler mais

    Tempo de leitura: 5 minutos
    12/09/2026