Clases y objetos
Antes de empezar: tu entorno virtual (venv). Como este es el primer módulo, dejemos listo el entorno. Un entorno virtual es una carpeta aislada donde viven Python y las dependencias de este proyecto, sin contaminar tu sistema. Abre tu terminal en la carpeta del proyecto y ejecuta, paso a paso:
python -m venv .venv # crea el entorno en la carpeta .venv source .venv/bin/activate # actívalo en macOS / Linux # .venv\Scripts\activate # en Windows (PowerShell) python -m pip install --upgrade pipCuando el entorno está activo verás
(.venv)al inicio de la línea de la terminal. Para instalar una dependencia:pip install <paquete>. Para salir:deactivate. En este curso no necesitaremos paquetes externos al inicio (solo la librería estándar), pero quiero que el reflejo de crear y activar venv sea automático en ti.
¿Qué es?
Una clase es una plantilla que describe cómo es una cosa: qué datos guarda y qué comportamiento tiene. Un objeto (o instancia) es una cosa concreta creada a partir de esa plantilla. La clase es el plano; el objeto es la casa construida con ese plano.
La Programación Orientada a Objetos (POO) parte de una idea simple: agrupar los datos y el comportamiento que opera sobre ellos en una misma unidad. En programación estructurada sueles separar datos en diccionarios y lógica en funciones sueltas; la POO los hace vivir juntos, con reglas claras sobre quién toca qué.
¿Cómo funciona?
En Python defines una clase con class. El método especial __init__ es el constructor: se ejecuta automáticamente al crear cada objeto y sirve para inicializar sus datos. El primer parámetro de todo método es self, que es el objeto concreto sobre el que se llama el método. Los datos guardados en self se llaman atributos de instancia.
¿Para qué sirve?
Sirve para modelar el mundo del problema con piezas que se responsabilizan de sí mismas. En vez de un diccionario suelto que cualquiera puede romper con un typo, tienes un objeto que sabe validar y operar sobre sus propios datos.
Qué pieza del sistema construimos
El proyecto final es un Sistema de notas con POO. En este módulo construimos la primera versión de la clase Estudiante: guarda nombre y una lista de notas, permite agregar notas y calcular su promedio. Es el ladrillo sobre el que crecerá todo lo demás.
De diccionario a clase
Así modelaríamos un estudiante sin POO:
estudiante = {"nombre": "Ana", "notas": []}
def promedio(est):
if not est["notas"]:
return 0.0
return sum(est["notas"]) / len(est["notas"])
Funciona, pero nada impide escribir estudiante["ntoas"] = [] (un typo silencioso) ni garantiza que la clave notas exista. La clase resuelve esto: define una sola vez la forma del objeto y el comportamiento asociado.
Construyendo la clase Estudiante
Vamos paso a paso.
class Estudiante:
def __init__(self, nombre):
self.nombre = nombre # atributo de instancia
self.notas = [] # cada estudiante tiene su propia lista
Aquí __init__ recibe self (el objeto que se está creando) y nombre. Asignamos self.nombre y self.notas. Cada objeto tendrá su propia copia de esos atributos.
Ahora agregamos comportamiento: un método para registrar una nota y otro para el promedio.
class Estudiante:
def __init__(self, nombre):
self.nombre = nombre
self.notas = []
def agregar_nota(self, valor):
self.notas.append(valor)
def promedio(self):
if not self.notas:
return 0.0
return sum(self.notas) / len(self.notas)
Fíjate: agregar_nota y promedio operan sobre self.notas, el dato del propio objeto. No reciben el estudiante por parámetro como en la versión con diccionario; ya viven dentro de él.
Creando y usando objetos
ana = Estudiante("Ana")
ana.agregar_nota(4.5)
ana.agregar_nota(3.0)
print(ana.nombre) # Ana
print(ana.promedio()) # 3.75
Estudiante("Ana") llama a __init__ por debajo: Python crea el objeto y le pasa "Ana" como nombre. Luego ana.agregar_nota(4.5) es lo mismo que Estudiante.agregar_nota(ana, 4.5): ese ana es el self.
Un atributo definido dentro de __init__ con self. es de instancia (uno por objeto). Un atributo definido directamente bajo class es de clase (compartido por todas las instancias). Para datos que cambian por objeto, usa siempre atributos de instancia.
Error común: poner una lista como valor por defecto en la firma del método, como def __init__(self, notas=[]). Esa lista se comparte entre todas las instancias y produce bugs muy difíciles de ver. Crea la lista dentro del cuerpo con self.notas = [].
Tech English: instance = instancia (objeto concreto), attribute = atributo, method = método, constructor = constructor (__init__).
Ejercicios
-
Clase
Curso. Diseña una claseCursocon atributosnombreycreditos, y un métododescripcion()que devuelva una cadena como"Cálculo I (3 créditos)". Crea dos objetosCursodistintos y comprueba que cada uno conserva sus propios datos. -
Extiende
Estudiante. Añade aEstudianteun métodonota_maxima()que devuelva la nota más alta registrada, oNonesi aún no hay notas. Justifica por quéNonees mejor que0.0en este caso.