Clases y Objetos en Python
La Programación Orientada a Objetos (POO) organiza el código en torno a objetos que combinan datos y comportamiento. Es el paradigma más usado en proyectos reales.
¿Qué es una clase?
Una clase es un molde o plano para crear objetos. Un objeto es una instancia concreta de esa clase. Cada objeto tiene sus propios atributos (datos) y métodos (funciones).
class Persona:
def __init__(self, nombre, edad):
self.nombre = nombre # atributo
self.edad = edad # atributo
def saludar(self): # método
print(f"Hola, soy {self.nombre} y tengo {self.edad} años")
# Crear objetos (instanciar la clase)
ana = Persona("Ana", 28)
carlos = Persona("Carlos", 35)
ana.saludar() # Hola, soy Ana y tengo 28 años
carlos.saludar() # Hola, soy Carlos y tengo 35 años
print(ana.nombre) # Ana
print(ana.edad) # 28
__init__ es el constructor — se ejecuta automáticamente cuando creás un objeto. self es la referencia al objeto actual y debe ser el primer parámetro de todos los métodos de la clase.
Atributos y métodos
Atributos de instancia vs atributos de clase
class Contador:
total = 0 # atributo de clase — compartido entre todas las instancias
def __init__(self, nombre):
self.nombre = nombre # atributo de instancia — único por objeto
Contador.total += 1
def info(self):
print(f"{self.nombre} — total creados: {Contador.total}")
c1 = Contador("A")
c2 = Contador("B")
c3 = Contador("C")
c1.info() # A — total creados: 3
c2.info() # B — total creados: 3
Métodos especiales (dunder methods)
class Producto:
def __init__(self, nombre, precio):
self.nombre = nombre
self.precio = precio
def __str__(self):
"""Representación legible — usada por print()"""
return f"{self.nombre} (${self.precio:.2f})"
def __repr__(self):
"""Representación técnica — usada en la consola"""
return f"Producto('{self.nombre}', {self.precio})"
p = Producto("Laptop", 1200)
print(p) # Laptop ($1200.00) ← usa __str__
print(repr(p)) # Producto('Laptop', 1200) ← usa __repr__
Probá el Ejemplo 2: Atributos y métodos
Herencia
Una clase puede heredar atributos y métodos de otra. La clase que hereda se llama subclase; la que se hereda, superclase.
class Animal:
def __init__(self, nombre, especie):
self.nombre = nombre
self.especie = especie
def describir(self):
print(f"{self.nombre} es un {self.especie}")
def hablar(self):
print("...")
class Perro(Animal): # Perro hereda de Animal
def __init__(self, nombre):
super().__init__(nombre, "perro") # llama al __init__ del padre
self.trucos = []
def hablar(self): # sobreescribe el método del padre
print("¡Guau!")
def aprender(self, truco):
self.trucos.append(truco)
print(f"{self.nombre} aprendió: {truco}")
class Gato(Animal):
def __init__(self, nombre):
super().__init__(nombre, "gato")
def hablar(self):
print("¡Miau!")
rex = Perro("Rex")
misu = Gato("Misu")
rex.describir() # Rex es un perro ← método heredado
rex.hablar() # ¡Guau! ← método sobreescrito
rex.aprender("sentarse") # Rex aprendió: sentarse
misu.hablar() # ¡Miau!
super() llama al método de la clase padre. Es muy útil en __init__ para inicializar los atributos heredados sin repetir código.
Encapsulamiento
El encapsulamiento oculta los detalles internos de un objeto.
En Python se usa por convención: atributos con _ son "privados" por acuerdo,
con __ tienen name mangling real.
class CuentaBancaria:
def __init__(self, titular, saldo_inicial=0):
self.titular = titular
self._saldo = saldo_inicial # "privado" por convención
def depositar(self, monto):
if monto <= 0:
raise ValueError("El monto debe ser positivo")
self._saldo += monto
print(f"Depósito: +${monto} — Saldo: ${self._saldo}")
def retirar(self, monto):
if monto > self._saldo:
raise ValueError("Saldo insuficiente")
self._saldo -= monto
print(f"Retiro: -${monto} — Saldo: ${self._saldo}")
@property
def saldo(self):
"""Getter: permite leer _saldo como si fuera un atributo normal."""
return self._saldo
cuenta = CuentaBancaria("Ana", 1000)
cuenta.depositar(500)
cuenta.retirar(200)
print(f"Saldo final: ${cuenta.saldo}")
El decorador @property permite acceder a un método como si fuera un atributo: cuenta.saldo en vez de cuenta.saldo(). Es útil para controlar el acceso a datos internos.
Ejemplo Completo
Sistema de biblioteca con herencia y encapsulamiento:
class Publicacion:
def __init__(self, titulo, autor, anio):
self.titulo = titulo
self.autor = autor
self.anio = anio
self._prestado = False
def prestar(self):
if self._prestado:
print(f"'{self.titulo}' ya está prestado")
return False
self._prestado = True
print(f"'{self.titulo}' prestado")
return True
def devolver(self):
self._prestado = False
print(f"'{self.titulo}' devuelto")
@property
def disponible(self):
return not self._prestado
def __str__(self):
estado = "disponible" if self.disponible else "prestado"
return f"{self.titulo} — {self.autor} ({self.anio}) [{estado}]"
class Libro(Publicacion):
def __init__(self, titulo, autor, anio, paginas):
super().__init__(titulo, autor, anio)
self.paginas = paginas
def __str__(self):
return f"[Libro] {super().__str__()} — {self.paginas} págs"
class Revista(Publicacion):
def __init__(self, titulo, autor, anio, numero):
super().__init__(titulo, autor, anio)
self.numero = numero
def __str__(self):
return f"[Revista] {super().__str__()} — N°{self.numero}"
# Uso
biblioteca = [
Libro("Python Crash Course", "Eric Matthes", 2019, 544),
Libro("Clean Code", "Robert Martin", 2008, 431),
Revista("Nature", "Varios", 2024, 612),
]
print("=== Catálogo ===")
for pub in biblioteca:
print(f" {pub}")
print("\n=== Operaciones ===")
biblioteca[0].prestar()
biblioteca[0].prestar() # ya prestado
biblioteca[0].devolver()
print("\n=== Disponibles ===")
for pub in biblioteca:
if pub.disponible:
print(f" ✅ {pub.titulo}")
Probá el Ejemplo 4: Sistema de biblioteca
Buenas Prácticas
- Nombres en PascalCase — las clases van con mayúscula inicial:
CuentaBancaria - Una responsabilidad por clase — igual que con las funciones, cada clase debe hacer una cosa bien
- __str__ siempre — definí la representación legible para facilitar el debugging
- Preferí composición sobre herencia — no heredes solo para reutilizar código; usá composición (una clase que tiene instancias de otra)
- No abuses de __ — el guión bajo simple
_attres suficiente en la mayoría de los casos - Docstrings en clases y métodos — documenten el propósito y los parámetros