Ir al contenido
  1. Blog/

Resumen esencial de Programación Orientada a Objetos en Python

··6 mins
La Programación Orientada a Objetos (POO) es uno de los paradigmas más utilizados en el desarrollo de software. En esta guía, exploraremos desde los bloques de construcción básicos hasta conceptos avanzados y pythonicos para dominar la POO en Python.

1. Fundamentos: Del concepto al código #

Antes de entrar en los pilares de la POO, es vital entender las piezas que componen este paradigma y cómo se diferencian de la programación estructurada tradicional.

Función vs. Método #

Una función es un bloque de código independiente y reutilizable que realiza una tarea específica. Un método es esencialmente una función, pero que “vive” dentro de una clase y opera sobre los datos (estado) de los objetos creados a partir de esa clase.

# Esto es una función (independiente)
def saludar(nombre):
    return f"¡Hola, {nombre}!"

class Persona:
    def __init__(self, nombre):
        self.nombre = nombre

    # Esto es un método (pertenece a la clase)
    def saludar_metodo(self):
        return f"Hola, soy {self.nombre}"

Clase, Objeto y Constructor #

  • Clase: Es el molde o plano arquitectónico. Define qué atributos (datos) y métodos (comportamientos) tendrán los objetos.
  • Objeto (Instancia): Es la materialización de esa clase. Si la clase es el plano de una casa, el objeto es la casa ya construida.
  • Constructor (__init__): Es un método especial que Python ejecuta automáticamente cada vez que creas un nuevo objeto. Se usa para inicializar el estado del objeto.
class Auto:
    # El constructor inicializa el estado
    def __init__(self, marca, modelo):
        self.marca = marca   # Atributo
        self.modelo = modelo # Atributo

# Instanciación: creando objetos a partir del molde
mi_auto = Auto("Toyota", "Corolla")
print(mi_auto.marca) # Salida: Toyota

Conceptos de diseño:

  • Entidad: El concepto abstracto en tu lógica de negocio (ej. un Auto).
  • Propiedad: La característica conceptual de esa entidad (ej. el color).
  • Atributo: La variable real en el código que almacena esa propiedad (self.color).

2. Los 4 Pilares de la POO #

La Programación Orientada a Objetos se sostiene sobre cuatro conceptos fundamentales que garantizan un código modular, seguro y reutilizable.

A. Encapsulamiento #

El encapsulamiento consiste en proteger el estado interno de un objeto, evitando que sea modificado directamente desde el exterior de forma incontrolada.

En Python, no existen modificadores de acceso estrictos como private o public en Java, pero se usa la convención de guiones bajos (_ para protegido, __ para privado). Para interactuar con estos datos, usamos Getters y Setters.

La forma moderna y pythonic de implementarlos es usar el decorador @property:

class Producto:
    def __init__(self, precio_inicial):
        self.__precio = precio_inicial # Atributo "privado"

    @property
    def precio(self):
        """Getter: Devuelve el valor."""
        return self.__precio

    @precio.setter
    def precio(self, nuevo_precio):
        """Setter: Valida antes de modificar."""
        if nuevo_precio < 0:
            raise ValueError("El precio no puede ser negativo")
        self.__precio = nuevo_precio

p = Producto(100)
p.precio = 150  # Llama al setter automáticamente
print(p.precio) # Llama al getter automáticamente

B. Herencia y Sobreescritura #

La herencia permite crear nuevas clases (hijas) basadas en clases existentes (padres), heredando sus atributos y métodos.

La sobreescritura (Overriding) ocurre cuando la clase hija redefine un método de la clase padre para darle un comportamiento específico, a menudo usando super() para aprovechar la lógica original.

class Empleado:
    def __init__(self, nombre, salario):
        self.nombre = nombre
        self.salario = salario

    def calcular_bono(self):
        return self.salario * 0.10

class Gerente(Empleado):
    # Sobreescritura del método
    def calcular_bono(self):
        # Usamos super() para obtener el bono base y le sumamos un extra
        bono_base = super().calcular_bono()
        return bono_base + 500

g = Gerente("Ana", 1000)
print(g.calcular_bono()) # Salida: 600.0 (100 base + 500 extra)

Herencia Múltiple #

Python soporta herencia múltiple, lo que significa que una clase puede heredar de varias clases a la vez. Python maneja posibles conflictos de métodos utilizando el MRO (Method Resolution Order).

class Volador:
    def volar(self): return "Volando alto"

class Nadador:
    def nadar(self): return "Nadando profundo"

class Pato(Volador, Nadador): # Hereda de ambas
    pass

donald = Pato()
print(donald.volar(), "y", donald.nadar())

C. Abstracción e Interfaces #

La abstracción consiste en exponer solo los detalles esenciales y ocultar la complejidad interna. Se implementa mediante Clases Abstractas. Una clase abstracta no puede ser instanciada y sirve como un molde base que las subclases deben implementar.

Una Interfaz es una forma estricta de abstracción. Funciona como un “contrato”: define qué debe poder hacer un objeto (sus métodos), pero no incluye ninguna lógica sobre cómo debe hacerlo. Garantiza que cualquier clase que implemente esa interfaz tendrá esos métodos disponibles.

En Python, esto se logra con el módulo abc (Abstract Base Classes). Si una clase abstracta solo contiene métodos abstractos (sin ninguna implementación), actúa funcionalmente como una interfaz.

from abc import ABC, abstractmethod

# Esto actúa como una Interfaz
class ProcesadorPago(ABC):
    @abstractmethod
    def procesar(self, monto):
        pass

class PagoTarjeta(ProcesadorPago):
    # Obligados a implementar el método procesar
    def procesar(self, monto):
        print(f"Procesando ${monto} con Tarjeta de Crédito")

class PagoPayPal(ProcesadorPago):
    def procesar(self, monto):
        print(f"Procesando ${monto} a través de PayPal")

D. Polimorfismo #

El polimorfismo (“muchas formas”) permite que diferentes objetos respondan al mismo método a su propia manera. Se combina perfectamente con la abstracción.

Usando el ejemplo anterior, podemos crear una función que procese pagos sin importarle el tipo exacto de objeto, confiando en el contrato de la interfaz:

def realizar_cobro(procesador: ProcesadorPago, monto: float):
    # Polimorfismo en acción: 'procesador' puede ser Tarjeta o PayPal
    procesador.procesar(monto)

realizar_cobro(PagoTarjeta(), 1500)
realizar_cobro(PagoPayPal(), 500)

3. Conceptos Avanzados y Herramientas #

Para dominar la POO en Python, debes conocer ciertas herramientas que el lenguaje ofrece para estructurar mejor las clases y asegurar el tipado.

Decoradores #

Un decorador es una función que envuelve a otra función (o método) para extender o modificar su comportamiento sin alterar su código fuente. En POO, los usamos constantemente (como vimos con @property).

# Creando un decorador personalizado
def registrar_auditoria(metodo):
    def envoltorio(*args, **kwargs):
        print(f"-> LOG: Ejecutando el método '{metodo.__name__}'")
        return metodo(*args, **kwargs)
    return envoltorio

class Sistema:
    @registrar_auditoria
    def iniciar(self):
        print("El sistema ha iniciado correctamente.")

s = Sistema()
s.iniciar()
# Salida esperada:
# -> LOG: Ejecutando el método 'iniciar'
# El sistema ha iniciado correctamente.

Métodos de Instancia, de Clase y Estáticos #

Python ofrece diferentes formas de vincular métodos dentro de una clase:

  1. Método de Instancia (default): Recibe self como primer parámetro. Modifica o lee el estado del objeto.
  2. Método de Clase (@classmethod): Recibe cls (la clase) como primer parámetro. Se usa para modificar el estado general de la clase o como constructores alternativos (Factory methods).
  3. Método Estático (@staticmethod): No recibe ni self ni cls. Es básicamente una función normal que se coloca dentro de la clase por una cuestión de organización lógica, ya que no necesita acceder a propiedades de la clase ni del objeto.
class Fecha:
    def __init__(self, dia, mes, año):
        self.dia = dia
        self.mes = mes
        self.año = año

    @classmethod
    def desde_string(cls, fecha_str):
        """Constructor alternativo. Recibe 'cls' en lugar de 'self'."""
        dia, mes, año = map(int, fecha_str.split('-'))
        return cls(dia, mes, año)

    @staticmethod
    def es_bisiesto(año):
        """Lógica pura. No necesita acceder a 'self' ni 'cls'."""
        return año % 4 == 0 and (año % 100 != 0 or año % 400 == 0)

# Uso del classmethod (Factory)
hoy = Fecha.desde_string("22-07-2025")

# Uso del staticmethod
print(Fecha.es_bisiesto(2024)) # True

Generics (Tipado Genérico) #

Con la evolución de Python hacia el tipado estático opcional, podemos usar Generics para crear clases que puedan trabajar con cualquier tipo de dato, manteniendo la seguridad de tipos en el análisis estático (ideal para IDEs o herramientas como mypy).

from typing import TypeVar, Generic

# Declaramos una variable de tipo T
T = TypeVar('T')

class Caja(Generic[T]):
    def __init__(self, contenido: T):
        self.contenido = contenido

    def obtener_contenido(self) -> T:
        return self.contenido

# Al instanciar, especificamos el tipo esperado
caja_enteros = Caja[int](123)
caja_strings = Caja[str]("Un mensaje")
Gerardo Catalas
Autor
Gerardo Catalas
Me especializo en el desarrollo backend, con experiencia práctica en C# (.NET) y SQL Server. Actualmente continúo ampliando mis conocimientos en nuevas tecnologías mediante formación y proyectos personales.