# programacion-orientada-a-objetos-en-python
Programación Orientada a Objetos en Python
Volver a la portada del cursoPráctica con clases y objetos
Sección 2 · Clases y Objetos
Práctica con clases y objetos
En esta práctica construyes las piezas básicas de PyShop, la tienda en línea del curso: la clase Producto, varios objetos con su propio estado, una Linea de pedido y un Carrito que funciona con len() y con for. Descarga el notebook adjunto y ejecútalo celda por celda; solo necesitas Python 3.13 y la biblioteca estándar.
1. Clase, objeto y self
La clase es la plantilla; cada llamada Producto(...) crea un objeto con sus propios atributos. __init__ inicializa ese estado y self es el objeto que recibe la llamada. Por eso taza.subtotal(2) equivale a Producto.subtotal(taza, 2), y ambas dan 298.0.
class Producto:
moneda = "MXN" # atributo de clase
_siguiente_sku = 100
def __init__(self, nombre, precio, stock=0):
self.nombre = nombre # atributos de instancia
self.precio = precio
self.stock = stock
self.sku = Producto._siguiente_sku
Producto._siguiente_sku += 1
def subtotal(self, cantidad):
return round(self.precio * cantidad, 2)
Con esta clase, la taza (149.0, 20 piezas), el teclado (899.0, 3 piezas) y el ebook (299.0) reciben los SKU 100, 101 y 102.
2. Identidad, igualdad y referencias
is pregunta si dos nombres apuntan al mismo objeto; == pregunta si son iguales. Si la clase no define __eq__, == compara identidad: dos tazas creadas con los mismos datos dan False con ambos operadores. En cambio, alias = taza_a no copia nada: es otro nombre para el mismo objeto, y reponer 5 piezas con alias deja a taza_a con 25.
3. __repr__ y __str__
__repr__ es la vista para desarrolladores y __str__ la vista para el usuario. En el notebook, repr(taza) da Producto('Taza', precio=149.0, stock=20) y print(taza) da Taza — $149.00 MXN. Ojo: al imprimir una lista de productos, Python usa __repr__ para cada elemento.
4. Atributos de clase y la trampa de la lista mutable
Leer taza.moneda busca primero en el objeto y luego en la clase. Asignar taza.moneda = "USD" crea un atributo de instancia que oculta al de la clase solo para la taza. La misma regla explica la trampa más común del tema:
class CarritoConError:
lineas = [] # UNA lista para todos los carritos
def agregar(self, producto, cantidad=1):
self.lineas.append((producto.nombre, cantidad))
Si Ana agrega dos tazas, el carrito de Luis también las muestra, porque append modifica la lista compartida. La corrección es crear la lista en __init__ con self.lineas = [].
5. Métodos de clase y estáticos
Un @classmethod recibe la clase como cls; el caso típico es un constructor alternativo como desde_dict, que convierte {"nombre": "Mouse", "precio": "349.5", "stock": "12"} en un producto. Como termina con cls(...), llamado desde ProductoDigital crea un ProductoDigital. Un @staticmethod, como es_precio_valido, no recibe ni self ni cls: es una función normal agrupada en la clase.
6. Dataclass y métodos especiales
Linea se define con @dataclass(frozen=True): Python genera __init__, __repr__ y un __eq__ que compara campo por campo, así que Linea(taza, 2) == Linea(taza, 2) da True. Asignar linea.cantidad = 5 lanza FrozenInstanceError. El carrito tiene una lista de líneas (composición) y define dos métodos especiales:
class Carrito:
def __init__(self, cliente):
self.cliente = cliente
self._lineas = [] # composición: el carrito TIENE líneas
def __len__(self):
return sum(l.cantidad for l in self._lineas)
def __iter__(self):
return iter(self._lineas)
Con 2 tazas, 1 teclado y 1 ebook, len(carrito) da 4 artículos y el subtotal es 1496.0.
Trampas comunes
- Olvidar
selfen la definición de un método: Python pasa el objeto de todas formas y la llamada falla conTypeError. - Incrementar un contador con
self.contador += 1: crea un atributo de instancia y el de la clase no cambia. - Usar una lista o un diccionario como atributo de clase para datos de cada objeto.
- Esperar que
==compare datos en una clase sin__eq__. Si necesitas igualdad por valor, define__eq__o usa@dataclass.
Qué hacer con el notebook
Ejecuta las secciones 1 a 6 y cambia valores para ver qué ocurre. Después resuelve los cuatro ejercicios: valor_inventario() (la taza debe dar 2980.0), un constructor desde_texto con el formato "Libreta;59.9;40", el operador in en el carrito con __contains__ y la prueba de que dos carritos no comparten su lista. Cada solución trae sus assert: escribe tu versión antes de leerla y verifica que pase las mismas pruebas.