Saltar al contenido

# programacion-orientada-a-objetos-en-python

Programación Orientada a Objetos en Python

Volver a la portada del curso
Notebook

Prá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 self en la definición de un método: Python pasa el objeto de todas formas y la llamada falla con TypeError.
  • 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.

Recursos