POO en Python: clases, herencia y composición

Actualizado el: 20/08/2026
Tempo de leitura: 4 minutos
logo do python com objetos abaixo do logo

La programación orientada a objetos en Python organiza un programa alrededor de objetos que combinan datos y comportamiento. Una cuenta bancaria puede almacenar un saldo y ofrecer métodos para depositar o retirar. Un producto puede guardar nombre y precio y calcular descuentos. Un personaje de videojuego puede conservar puntos de vida y ejecutar acciones.

Python permite utilizar orientación a objetos sin obligar a que todos los problemas se resuelvan con clases. En esta guía aprenderás clases, instancias, atributos, métodos, __init__, propiedades, métodos de clase, herencia, polimorfismo, composición y dataclasses.

Qué es un objeto

Un objeto es un valor que posee estado y comportamiento. Python ya trabaja con objetos:

nombre = "Python"
numeros = [3, 1, 2]

print(nombre.upper())
numeros.sort()
print(numeros)

El string proporciona upper() y la lista proporciona sort(). La documentación oficial sobre clases explica instancias, métodos e herencia.

Clase e instancia

Una clase define la estructura de un tipo. Una instancia es un objeto concreto creado a partir de esa clase:

class Producto:
    pass


libro = Producto()
curso = Producto()

print(type(libro))
print(libro is curso)

libro y curso son objetos distintos, aunque pertenecen a la misma clase.

Inicializar con __init__

class Producto:
    def __init__(self, nombre: str, precio: float) -> None:
        self.nombre = nombre
        self.precio = precio


libro = Producto("Manual de Python", 39.90)
print(libro.nombre)
print(libro.precio)

self representa la instancia actual. Al ejecutar Producto(...), Python crea el objeto y lo entrega al método __init__.

Atributos de instancia

self.nombre y self.precio pertenecen a cada objeto:

libro = Producto("Manual de Python", 39.90)
curso = Producto("Python Básico", 89.00)

print(libro.nombre)
print(curso.nombre)

Una variable local desaparece al terminar un método. Un atributo permanece unido a la instancia.

Métodos de instancia

class Producto:
    def __init__(self, nombre: str, precio: float) -> None:
        self.nombre = nombre
        self.precio = precio

    def aplicar_descuento(self, porcentaje: float) -> float:
        if not 0 <= porcentaje <= 100:
            raise ValueError("Porcentaje fuera de rango")
        return self.precio * (1 - porcentaje / 100)


libro = Producto("Manual de Python", 40)
print(libro.aplicar_descuento(15))

La validación evita resultados inválidos. La guía de excepciones en Python explica cómo diseñar errores útiles.

Representar objetos con __repr__

class Producto:
    def __init__(self, nombre: str, precio: float) -> None:
        self.nombre = nombre
        self.precio = precio

    def __repr__(self) -> str:
        return (
            f"Producto(nombre={self.nombre!r}, "
            f"precio={self.precio!r})"
        )

Una representación clara ayuda al depurar y registrar objetos. La guía de f-strings para depuración explica !r.

Atributos de clase

class Producto:
    moneda = "EUR"

    def __init__(self, nombre: str, precio: float) -> None:
        self.nombre = nombre
        self.precio = precio

Un atributo de clase representa una constante o valor compartido. Evita colocar listas o diccionarios mutables allí salvo que realmente quieras compartirlos.

El problema del atributo mutable compartido

class Carrito:
    elementos = []  # Compartido por todas las instancias

La alternativa correcta es crear la lista dentro de __init__:

class Carrito:
    def __init__(self) -> None:
        self.elementos = []

Métodos de clase

Un método de clase recibe la clase y puede funcionar como constructor alternativo:

class Producto:
    def __init__(self, nombre: str, precio: float) -> None:
        self.nombre = nombre
        self.precio = precio

    @classmethod
    def desde_texto(cls, valor: str):
        nombre, precio = valor.rsplit(",", maxsplit=1)
        return cls(nombre.strip(), float(precio))


producto = Producto.desde_texto("Manual, 39.90")

Utilizar cls permite que el constructor funcione correctamente con subclases.

Métodos estáticos

class Producto:
    @staticmethod
    def precio_valido(valor: float) -> bool:
        return valor >= 0

Un método estático pertenece conceptualmente a la clase, pero no necesita una instancia. Si la función no está estrechamente relacionada, una función normal del módulo puede resultar más clara.

Encapsulación en Python

Python utiliza convenciones:

  • nombre es público;
  • _nombre indica un detalle interno;
  • __nombre activa name mangling para reducir conflictos accidentales.

El guion bajo no es una barrera de seguridad. Comunica que el código externo debería usar la interfaz pública.

Validar con propiedades

class Producto:
    def __init__(self, nombre: str, precio: float) -> None:
        self.nombre = nombre
        self.precio = precio

    @property
    def precio(self) -> float:
        return self._precio

    @precio.setter
    def precio(self, valor: float) -> None:
        if valor < 0:
            raise ValueError("El precio no puede ser negativo")
        self._precio = float(valor)

El usuario mantiene una sintaxis de atributo normal y la clase aplica reglas. No necesitas propiedades cuando un atributo público sencillo es suficiente.

Herencia

class Empleado:
    def __init__(self, nombre: str) -> None:
        self.nombre = nombre

    def describir_rol(self) -> str:
        return "Empleado"


class Desarrollador(Empleado):
    def __init__(self, nombre: str, lenguaje: str) -> None:
        super().__init__(nombre)
        self.lenguaje = lenguaje

    def describir_rol(self) -> str:
        return f"Desarrollador de {self.lenguaje}"

super() llama a la implementación de la clase base. La subclase especializa el comportamiento.

Polimorfismo

class NotificacionEmail:
    def enviar(self, mensaje: str) -> None:
        print(f"Email: {mensaje}")


class NotificacionSMS:
    def enviar(self, mensaje: str) -> None:
        print(f"SMS: {mensaje}")


def notificar(canal, mensaje: str) -> None:
    canal.enviar(mensaje)

La función depende del comportamiento que necesita, no de una jerarquía concreta. Este enfoque se relaciona con el duck typing de Python.

Composición

class Motor:
    def arrancar(self) -> str:
        return "Motor arrancado"


class Coche:
    def __init__(self, modelo: str, motor: Motor) -> None:
        self.modelo = modelo
        self.motor = motor

    def arrancar(self) -> str:
        return f"{self.modelo}: {self.motor.arrancar()}"

Un coche tiene un motor, por lo que la composición expresa mejor la relación. Reserva la herencia para relaciones “es un” donde la sustitución tiene sentido.

Dataclasses

Cuando una clase almacena principalmente datos, dataclasses reduce código repetitivo:

from dataclasses import dataclass


@dataclass
class Cliente:
    nombre: str
    correo: str
    activo: bool = True


cliente = Cliente("Ana", "ana@example.com")
print(cliente)

Python genera __init__ y __repr__. La documentación oficial de dataclasses explica valores predeterminados y objetos inmutables.

Proyecto: cuenta bancaria

class CuentaBancaria:
    def __init__(
        self,
        titular: str,
        saldo_inicial: float = 0,
    ) -> None:
        if saldo_inicial < 0:
            raise ValueError("Saldo inicial inválido")
        self.titular = titular
        self._saldo = float(saldo_inicial)

    @property
    def saldo(self) -> float:
        return self._saldo

    def depositar(self, cantidad: float) -> None:
        if cantidad <= 0:
            raise ValueError("El depósito debe ser positivo")
        self._saldo += cantidad

    def retirar(self, cantidad: float) -> None:
        if cantidad <= 0:
            raise ValueError("El retiro debe ser positivo")
        if cantidad > self._saldo:
            raise ValueError("Saldo insuficiente")
        self._saldo -= cantidad

La clase mantiene sus reglas en un único lugar y protege el estado.

Probar objetos

import pytest


def test_deposito_aumenta_saldo():
    cuenta = CuentaBancaria("Ana", 100)
    cuenta.depositar(25)
    assert cuenta.saldo == 125


def test_retiro_rechaza_saldo_insuficiente():
    cuenta = CuentaBancaria("Ana", 50)

    with pytest.raises(ValueError, match="Saldo insuficiente"):
        cuenta.retirar(75)

La guía de Pytest explica pruebas de excepciones y fixtures.

Documentar clases

class CuentaBancaria:
    """Representa una cuenta con depósitos y retiros validados."""

    def depositar(self, cantidad: float) -> None:
        """Añade una cantidad positiva al saldo."""
        ...

La guía de docstrings en Python muestra cómo documentar clases, métodos y excepciones.

Cuándo utilizar clases

  • Datos y operaciones pertenecen al mismo concepto.
  • Existen varias instancias con estados independientes.
  • Las reglas deben cumplirse cada vez que cambia el estado.
  • Diferentes implementaciones comparten una interfaz.
  • La composición modela partes que cooperan.

Cuándo una clase es innecesaria

def celsius_a_fahrenheit(celsius: float) -> float:
    return celsius * 9 / 5 + 32

Una transformación pequeña y sin estado puede ser más clara como función. Python permite combinar estilos procedurales, funcionales y orientados a objetos.

Errores frecuentes

  • Olvidar self.
  • Usar atributos de clase para listas de instancia.
  • Crear jerarquías de herencia profundas.
  • Escribir getters y setters para todo.
  • Permitir que una clase tenga demasiadas responsabilidades.
  • No validar el estado.
  • Probar detalles internos en lugar de comportamiento público.
  • Confundir identidad e igualdad; consulta == frente a is en Python.

Conclusión

La programación orientada a objetos en Python combina estado y comportamiento mediante clases e instancias. Empieza con __init__, atributos y métodos. Añade propiedades cuando exista validación, utiliza composición para relaciones “tiene un” y aplica herencia cuando una subclase pueda sustituir realmente a la base. El objetivo no es crear la mayor cantidad posible de clases, sino representar conceptos y reglas de forma comprensible y comprobable.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ftplib en Python: FTP y FTPS seguros

    Aprende ftplib en Python para listar, descargar y subir archivos por FTP o FTPS con TLS, timeouts, límites, reanudación y

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026
    Rack de servidores que representa un endpoint creado con xmlrpc.server en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    xmlrpc.server: crea servidores XML-RPC

    Aprende xmlrpc.server en Python para crear servidores XML-RPC, registrar funciones, limitar métodos y rutas y evitar exposición insegura.

    Ler mais

    Tempo de leitura: 6 minutos
    21/08/2026
    Código de error sobre datos binarios que representa fallos manejados con urllib.error en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    urllib.error en Python: maneja errores HTTP

    Aprende urllib.error en Python para manejar URLError, HTTPError, descargas incompletas, retries selectivos y diagnósticos de red claros.

    Ler mais

    Tempo de leitura: 5 minutos
    21/08/2026
    Teclas con la palabra HTML que representan entidades HTML en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    html.entities: convierte entidades HTML

    Aprende html.entities en Python para consultar entidades HTML, convertir nombres y code points y no confundir decodificación con sanitización.

    Ler mais

    Tempo de leitura: 7 minutos
    21/08/2026
    Carpeta con archivos que representa tipos MIME identificados con mimetypes en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    mimetypes: detecta tipos MIME de archivos

    Aprende mimetypes en Python para identificar tipos de archivo, validar cargas y definir Content-Type con más seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    20/08/2026
    Código HTML en una pantalla que representa análisis con html.parser en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    html.parser en Python: analiza HTML

    Aprende html.parser en Python para extraer texto, enlaces y metadatos, procesar HTML por bloques y no confundir parsing con sanitización.

    Ler mais

    Tempo de leitura: 4 minutos
    20/08/2026