Clases y objetos
En resumen: Una clase describe un tipo nuevo con atributos, y un objeto es una instancia concreta de ella, creada con una llamada como Product(); la variable solo apunta al objeto.
Problema
Los precios se mezclaron
Estás escribiendo el carrito de una tienda en línea. Cada producto tiene un nombre y un precio, así que los guardas en dos listas. El índice 0 guarda el nombre y el precio del primer producto, el índice 1 los del segundo.
Quieres el más caro primero, así que intercambias los precios:
names = ["Pan", "Leche", "Café"]prices = [1.20, 2.10, 7.50]
# Lo más caro primero: intercambiamos el primer y el tercer precio.prices[0], prices[2] = prices[2], prices[0]
for i in range(len(names)): print(f"{names[i]}: {prices[i]} €")Pan: 7.5 €Leche: 2.1 €Café: 1.2 €Ahora el pan cuesta 7,50 €. Los precios se intercambiaron, pero los nombres no. Las dos listas siempre tienen que cambiar juntos, pero nada te obliga a hacerlo. Añade una tercera lista para las cantidades, y tendrás que vigilar tres.
La idea
Un producto, un lugar
El nombre y el precio no son cosas separadas. Son partes de un producto. Queremos un tipo «producto», igual que existe int para los números y str para el texto.
Un tipo así lo creas tú mismo. Se llama clase:
class Product: def __init__(self): self.name = "" self.price = 0.0 self.quantity = 0self.name, self.price y self.quantity son los campos del producto (en Python también se llaman atributos). Cada producto tendrá su propio nombre, su propio precio y su propia cantidad.
En Python no enumeras los campos por separado. Los creas con un valor inicial en __init__: texto vacío, 0.0 y 0. Por ahora escribe la línea def __init__(self): exactamente así. El código que está debajo se ejecuta para cada producto nuevo, y self es ese producto. Lo entenderás en detalle en la lección «Constructores y self».
La clase es solo una descripción: «cada producto tiene un nombre, un precio y una cantidad». Todavía no hay ningún producto.
El nombre de la clase empieza con mayúscula (Product), y los nombres de los campos con minúscula (name, price). Así ves enseguida qué es un tipo y qué es un valor.
La idea
Product(): de la descripción a un producto
Creas un producto concreto a partir de la clase llamando a la clase como a una función: Product(). Se llama objeto. Llegas a sus campos con un punto:
class Product: def __init__(self): self.name = "" self.price = 0.0 self.quantity = 0
bread = Product()bread.name = "Pan"bread.price = 1.20bread.quantity = 2
coffee = Product()coffee.name = "Café"coffee.price = 7.50coffee.quantity = 1
print(f"{bread.name}: {bread.price * bread.quantity} €")print(f"{coffee.name}: {coffee.price * coffee.quantity} €")Pan: 2.4 €Café: 7.5 €- La clase está encima del código que la usa. Python lee el archivo de arriba abajo y tiene que haberla visto antes. Un archivo puede tener varias clases.
- Python no tiene
new.Product()crea un objeto nuevo y lo devuelve. Cada objeto tiene sus propios campos: el pan y el café no se estorban. - No escribes un tipo delante de
bread, pero el objeto igualmente tiene un tipo:type(bread)da<class '__main__.Product'>. bread.pricees «el precio del pan». Lo lees y lo escribes como una variable normal.
Ahora el nombre y el precio del producto están en un solo lugar. Mueves el producto, y se mueven juntos.
Pruébalo
No se ha rellenado nada
Si creas un producto y no rellenas nada, ¿qué hay en sus campos?
¿Qué imprimirá el programa?
item = Product()print(item.name)print(item.price)print(item.quantity)En Python los campos no tienen un valor «por defecto». Tienen exactamente lo que les dio __init__: texto vacío (se imprime como una línea vacía), 0.0 y 0. Un campo que __init__ no crea simplemente no existe: item.color detiene el programa con AttributeError: 'Product' object has no attribute 'color'.
Pruébalo
¿Qué falta?
¿Qué va en el hueco para que haya un producto nuevo?
milk = ()
milk.name = "Leche"
milk.price = 2.10Product: el nombre de la clase, llamado con paréntesis, crea un objeto. Python no tiene new.Míralo
La variable apunta al objeto
Quieres una oferta de café, así que haces una «copia» para cambiarle el precio:
coffee = Product()coffee.price = 10sale = coffeesale.price = 8print(coffee.price) # 8Paso a paso, en palabras
- Product() crea el objeto, y __init__ da valores iniciales a sus campos. coffee recibe una flecha hacia él.
- Por la flecha de coffee: el precio del objeto pasa a ser 10.
- sale = coffee copia la flecha, no el objeto. Dos flechas, un objeto.
- Por la flecha de sale: el precio del mismo objeto pasa a ser 8.
- coffee apunta al mismo objeto, así que imprime 8.
El precio del café también pasó a ser 8. La razón es que la variable coffee no es el objeto en sí. El objeto vive aparte en la memoria, y coffee guarda dónde está: una flecha al objeto. Una variable así se llama referencia.
sale = coffee copia la flecha, no el objeto. Ahora dos flechas apuntan a un objeto, así que el cambio a través de sale se ve también a través de coffee.
Ya viste lo mismo en la lección sobre listas: sale = prices le daba un segundo nombre a la misma lista. Una lista también es un objeto.
La idea
Ahora el descuento funciona
En el módulo «Funciones», el descuento no funcionaba: la función recibía el precio, cambiaba su propio parámetro, y el cambio se perdía. Con un objeto es distinto:
def discount(p, percent): p.price = p.price * (100 - percent) / 100
coffee = Product()coffee.price = 10discount(coffee, 10)print(coffee.price)9.0El parámetro sigue siendo una copia, pero una copia de la flecha. Apunta al mismo objeto, así que el cambio de p.price se queda también después de la función.
Más precisamente, para curiosos
Si la función pone un objeto nuevo en el parámetro, solo mueve su propia copia de la flecha. El objeto de fuera no cambia:
def reset(p): p = Product() p.price = 0
coffee = Product()coffee.price = 10reset(coffee)print(coffee.price)10=: el parámetro recibe una flecha al mismo objeto. Si la función apunta su flecha a otra parte, eso no llega a la variable de fuera.La idea
None: una flecha a ninguna parte
Una variable puede no apuntar a ningún objeto. Entonces guarda None: «nada». Si intentas leer un campo a través de ella, no hay ningún objeto del que leerlo:
gift = Noneprint(gift.name)El programa arranca, pero se detiene con un error cuando llega a la línea:
AttributeError: 'NoneType' object has no attribute 'name'Es el error más común con objetos. Qué es una «excepción» y cómo se captura, lo verás en el módulo «Errores y excepciones».
Una lista también guarda flechas, no los productos mismos. [None] * 3 crea tres lugares que todavía no apuntan a ningún producto. Cada uno es None hasta que pones ahí Product():
cart = [None] * 3print(cart[0] is None)cart[0] = Product()cart[0].name = "Pan"print(cart[0].name)TruePanPruébalo
Encuentra el error
El programa debería imprimir «Pan» y «Leche». En cambio, imprime «Leche» dos veces. ¿Qué línea tiene el error?
Elige la línea con el error.
milk = bread no crea un producto nuevo. Ahora milk y bread apuntan al mismo objeto, y el nombre «Leche» reemplaza a «Pan». La corrección es milk = Product().
Ejercicio
Tus clases
1. Libro. Una clase Book con campos para el título y el autor. Crea dos libros e imprímelos.
Cien años de soledad de Gabriel García MárquezRayuela de Julio Cortázar2. Coche. Una clase SimpleCar con un campo para la velocidad. Crea un coche (su velocidad es 0), aumenta la velocidad en 10 e imprímela. En la lección «Métodos de instancia» esto se convertirá en un método del coche.
Velocidad tras acelerar: 103. El carrito. El carrito de la primera tarjeta, esta vez con una lista de tres objetos Product: pan 1,20 € (2 uds.), leche 2,10 € (1 ud.), café 7,50 € (1 ud.). Imprime el importe de cada línea y el total. Crea primero cada producto con Product(), y después ponlo en la lista.
Pan x2: 2.4 €Leche x1: 2.1 €Café x1: 7.5 €Total: 12.0 €4. El más caro primero. Ordena el mismo carrito por precio, del más caro al más barato, con el ordenamiento de burbuja de la lección sobre listas. Esta vez intercambias productos enteros, así que el nombre y el precio se mueven juntos.
Café: 7.5 €Leche: 2.1 €Pan: 1.2 €Más tareas
- Estudiante. Una clase
Studentcon un nombre y tres notas (tres campos). Crea dos estudiantes e imprime la nota media de cada uno. - Un libro, dos nombres. Crea un libro, guárdalo en dos variables y cambia el título a través de la segunda. Imprime el título a través de la primera. Explica con palabras por qué funciona así.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Bucles · Listas · Funciones · Parámetros y return
- Continúa en: Constructores y self · Métodos de instancia · Encapsulamiento · Lo que comparten todos los objetos: atributos de clase
- Ejercicios sobre el tema: Clases y objetos