Programación orientada a objetos en Python

Publicado el: 11/07/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", "[email protected]")
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

    Programação assíncrona com asyncio em Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    asyncio en Python: guía práctica para principiantes

    Aprende asyncio en Python con coroutines, await, tasks, TaskGroup, timeouts, cancelación, colas, bloqueos y manejo de errores.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Uso de expressões regulares regex para manipulação de texto em Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Regex en Python: guía de expresiones regulares

    Aprende regex en Python con search, fullmatch, findall, grupos, cuantificadores, sustituciones, flags, patrones compilados y ejemplos prácticos.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026