Saltar al contenido
🐍 Curso de Python
/

Módulo 1 · Lección 52 de 59

Clases y objetos

Qué es una clase, qué es un objeto y por qué juntar datos y funciones en un mismo sitio ordena el programa.

⏱ 12 min de lectura

Cuando un puñado de datos siempre viaja junto y siempre hay las mismas funciones operando sobre él, el diccionario suelto se queda corto. Nada garantiza que las claves existan, nada impide escribir "precios" en vez de "precio", y las funciones que trabajan con ese diccionario acaban repartidas por el fichero sin nada que las relacione con él.

Una clase resuelve justamente eso: empaquetar los datos y las funciones que los manejan en una sola cosa.

El problema, con diccionarios#

Así es como lo harías con lo que sabes hasta ahora:

python
producto = {"nombre": "Teclado mecánico", "precio": 79.90, "unidades": 4}


def valor_en_almacen(producto):
        return producto["precio"] * producto["unidades"]


def describir(producto):
        return f"{producto['nombre']}: {producto['precio']:.2f} € ({producto['unidades']} uds.)"


print(describir(producto))
print(f"{valor_en_almacen(producto):.2f} €")
Salida en pantalla
Teclado mecánico: 79.90 € (4 uds.)
319.60 €

Funciona, pero tiene tres grietas:

  • Nada obliga a que un diccionario "producto" tenga esas tres claves. Si a uno le falta unidades, valor_en_almacen revienta con KeyError.
  • describir es un nombre demasiado genérico. El día que tengas clientes y pedidos necesitarás describir_producto, describir_cliente, describir_pedido.
  • Quien lea el código tiene que deducir, leyendo las funciones, qué forma tiene ese diccionario. No está escrito en ningún sitio.

Lo mismo, con una clase#

python
class Producto:
        def __init__(self, nombre, precio, unidades):
                self.nombre = nombre
        self.precio = precio
        self.unidades = unidades

    def valor_en_almacen(self):
                return self.precio * self.unidades

    def describir(self):
                return f"{self.nombre}: {self.precio:.2f} € ({self.unidades} uds.)"


teclado = Producto("Teclado mecánico", 79.90, 4)
print(teclado.describir())
print(f"{teclado.valor_en_almacen():.2f} €")
Salida en pantalla
Teclado mecánico: 79.90 € (4 uds.)
319.60 €

La salida es idéntica, pero el código dice cosas que antes no decía. La primera línea de __init__ declara, negro sobre blanco, que un producto tiene nombre, precio y unidades. Y describir ya no necesita apellido: es la forma de describir de un producto, y solo se puede llamar sobre uno.

El vocabulario, con precisión#

Estas cuatro palabras se usan todo el rato y conviene no confundirlas:

PalabraQué esEn el ejemplo
ClaseEl molde. Describe qué datos y qué operaciones tendrá algoProducto
Objeto o instanciaLo fabricado con el molde. Un ejemplar concretoteclado
AtributoUn dato que guarda un objetoteclado.precio
MétodoUna función que pertenece al objetoteclado.describir()

"Objeto" e "instancia" significan lo mismo. Se dice que teclado es una instancia de Producto, o que Producto se instancia para obtener teclado.

La distinción importante es la de clase y objeto. La clase se escribe una vez; los objetos se fabrican todos los que quieras, y cada uno con sus propios datos:

python
teclado = Producto("Teclado mecánico", 79.90, 4)
raton = Producto("Ratón inalámbrico", 24.50, 12)

print(teclado.describir())
print(raton.describir())
Salida en pantalla
Teclado mecánico: 79.90 € (4 uds.)
Ratón inalámbrico: 24.50 € (12 uds.)

Dos objetos, el mismo molde, datos independientes. Cambiar teclado.precio no toca a raton.

Llevas todo el curso usando objetos#

Esto no es un tema nuevo que aparece del aire. En Python todo es un objeto, y por eso type() responde siempre con la palabra class:

python
texto = "hola"
numeros = [3, 1, 2]

print(type(texto))
print(type(numeros))
print(type(3.5))
Salida en pantalla
<class 'str'>
<class 'list'>
<class 'float'>

"hola" es una instancia de la clase str. [3, 1, 2] es una instancia de la clase list. Y ahora tiene sentido algo que llevas usando desde el principio sin preguntarte por qué: texto.upper() y numeros.append(5) se escriben con punto porque son métodos, funciones que pertenecen a ese objeto. Es exactamente la misma sintaxis que teclado.describir().

Una clase mínima#

Una clase no necesita nada para existir. Esta es válida:

python
class Vehiculo:
        """Un vehículo de la flota de reparto."""


furgoneta = Vehiculo()
print(type(furgoneta))
print(isinstance(furgoneta, Vehiculo))
Salida en pantalla
<class '__main__.Vehiculo'>
True

Las piezas de esa sintaxis:

  • class es la palabra reservada, seguida del nombre y de dos puntos.
  • El nombre va en CamelCase: primera letra de cada palabra en mayúscula y sin guiones bajos. Producto, Vehiculo, CuentaBancaria. Esto lo distingue a simple vista de las variables y funciones, que van en minúsculas.
  • El cuerpo va sangrado. Si no tienes nada que poner, un docstring sirve; si tampoco quieres docstring, se pone pass.
  • Para crear un objeto se escribe el nombre de la clase con paréntesis, como si fuera una función: Vehiculo(). Eso es lo que se llama instanciar.

El __main__ que aparece en <class '__main__.Vehiculo'> es el nombre del módulo donde está definida la clase, y ya lo conoces de if __name__ == "__main__".

Cada objeto tiene sus propios atributos#

Los atributos se leen y se escriben con el punto, igual que se llaman los métodos:

python
furgoneta = Vehiculo()
motocicleta = Vehiculo()

furgoneta.matricula = "1234 ABC"
motocicleta.matricula = "5678 XYZ"

print(furgoneta.matricula)
print(motocicleta.matricula)
print(furgoneta is motocicleta)
Salida en pantalla
1234 ABC
5678 XYZ
False

furgoneta is motocicleta da False porque son dos objetos distintos, aunque salgan del mismo molde. Dos coches recién salidos de la misma cadena de montaje tampoco son el mismo coche.

Añadir atributos desde fuera así, uno a uno, se puede hacer pero no se hace: es fácil olvidarse de alguno y no queda escrito en ningún sitio qué atributos tiene un vehículo. Para eso está __init__, que es el tema de la lección siguiente.

Qué ves al imprimir un objeto#

Si imprimes un objeto tuyo sin más, Python no sabe cómo mostrarlo y se limita a decirte de qué clase es y en qué posición de memoria está:

python
furgoneta = Vehiculo()
print(furgoneta)
Salida en pantalla
<__main__.Vehiculo object at 0x100d45fd0>

Ese número hexadecimal cambia en cada ejecución, así que el tuyo será distinto. No es un fallo: es la representación por defecto, y se arregla definiendo __str__, que también verás en la lección de métodos especiales.

Cuándo merece la pena una clase#

No todo pide una clase. La señal clara es esta combinación: varios datos que siempre van juntos y un conjunto de funciones que siempre reciben esos mismos datos como argumento. Si tus funciones empiezan todas por def algo(producto, ...), ese producto está pidiendo ser una clase.

Si solo tienes un dato suelto, o una función que hace una cuenta y ya está, una clase solo añade ruido. Una función es una función.