CAP 09 · LEC 07·Programación orientada a objetos

Clases abstractas: ABC y abstractmethod como contratos

Una clase abstracta define una interfaz que las subclases deben implementar. No se puede instanciar directamente — existe solo para ser heredada. ABC es la forma idiomática en Python.

● AVANZADO9 min lectura4 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

ABC y ABCMeta

ABC (Abstract Base Class) del módulo abc es la forma estándar de declarar una clase abstracta en Python. Cualquier clase que herede de ABC y tenga métodos abstractos no puede instanciarse.

from abc import ABC, abstractmethod class Shape(ABC): """Clase base abstracta para todas las figuras geométricas.""" # No tiene __init__ propio — las subclases definen sus atributos @abstractmethod def area(self) -> float: """Todas las subclases deben implementar area().""" ... @abstractmethod def perimeter(self) -> float: """Todas las subclases deben implementar perimeter().""" ... # Método concreto: disponible en todas las subclases sin reimplementar def describe(self) -> str: return ( f"{type(self).__name__}: " f"área={self.area():.2f}, perímetro={self.perimeter():.2f}" ) # Intentar instanciar la clase abstracta lanza TypeError try: s = Shape() except TypeError as e: print(f"TypeError: {e}") # TypeError: Can't instantiate abstract class Shape # with abstract methods area, perimeter
SalidaTypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter
ABC vs metaclass=ABCMeta

Heredar de ABC es equivalente a escribir class Shape(metaclass=ABCMeta). ABC es simplemente una clase auxiliar que ya tiene ABCMeta como metaclase, lo que hace el código más legible. Usa siempre class Shape(ABC).

@abstractmethod y @abstractproperty

@abstractmethod marca métodos que las subclases deben implementar. Se puede combinar con @property para propiedades abstractas. Si la subclase no implementa todos los métodos abstractos, tampoco se puede instanciar.

from abc import ABC, abstractmethod import math class Shape(ABC): @abstractmethod def area(self) -> float: ... @abstractmethod def perimeter(self) -> float: ... @property @abstractmethod def name(self) -> str: """Nombre de la figura — propiedad abstracta.""" ... class Circle(Shape): def __init__(self, radius: float) -> None: self.radius = radius def area(self) -> float: return math.pi * self.radius ** 2 def perimeter(self) -> float: return 2 * math.pi * self.radius @property def name(self) -> str: return "Círculo" class Rectangle(Shape): def __init__(self, width: float, height: float) -> None: self.width = width self.height = height def area(self) -> float: return self.width * self.height def perimeter(self) -> float: return 2 * (self.width + self.height) @property def name(self) -> str: return "Rectángulo" figuras: list[Shape] = [Circle(5), Rectangle(4, 3)] for figura in figuras: print(figura.describe())
SalidaCírculo: área=78.54, perímetro=31.42 Rectángulo: área=12.00, perímetro=14.00

Métodos concretos en clases abstractas

Las clases abstractas pueden tener métodos completamente implementados. Son los métodos que comparten todas las subclases y no tiene sentido que cada una reimplemente.

from abc import ABC, abstractmethod class Serializable(ABC): """Contrato: los objetos deben poder convertirse a dict y desde dict.""" @abstractmethod def to_dict(self) -> dict: """Las subclases definen cómo serializarse.""" ... @classmethod @abstractmethod def from_dict(cls, data: dict) -> "Serializable": """Las subclases definen cómo deserializarse.""" ... # Métodos concretos: usan to_dict() — funcionan para todas las subclases def to_json(self) -> str: import json return json.dumps(self.to_dict(), ensure_ascii=False) def keys(self) -> list[str]: return list(self.to_dict().keys()) class User(Serializable): def __init__(self, name: str, email: str) -> None: self.name = name self.email = email def to_dict(self) -> dict: return {"name": self.name, "email": self.email} @classmethod def from_dict(cls, data: dict) -> "User": return cls(data["name"], data["email"]) u = User("Ana", "ana@ejemplo.com") print(u.to_json()) # {"name": "Ana", "email": "ana@ejemplo.com"} print(u.keys()) # ['name', 'email'] u2 = User.from_dict({"name": "Bob", "email": "bob@ejemplo.com"}) print(u2.name) # Bob
Salida{"name": "Ana", "email": "ana@ejemplo.com"} ['name', 'email'] Bob

Herencia múltiple con ABC

Los ABC son ideales como mixins — clases que añaden capacidades ortogonales a la jerarquía principal. La herencia múltiple con ABC es el patrón más común en librerías como collections.abc.

from abc import ABC, abstractmethod class Drawable(ABC): """Mixin: las subclases pueden dibujarse.""" @abstractmethod def draw(self) -> str: ... class Resizable(ABC): """Mixin: las subclases pueden redimensionarse.""" @abstractmethod def resize(self, factor: float) -> None: ... # Widget hereda de ambos ABC — debe implementar ambos contratos class Widget(Drawable, Resizable): def __init__(self, label: str, size: int) -> None: self.label = label self.size = size def draw(self) -> str: return f"[Widget '{self.label}' size={self.size}]" def resize(self, factor: float) -> None: self.size = int(self.size * factor) w = Widget("botón", 100) print(w.draw()) # [Widget 'botón' size=100] w.resize(1.5) print(w.draw()) # [Widget 'botón' size=150] # isinstance funciona con cada ABC por separado print(isinstance(w, Drawable)) # True print(isinstance(w, Resizable)) # True
Salida[Widget 'botón' size=100] [Widget 'botón' size=150] True True

Practica