Clases y Objetos¶
En el primer programa vimos una clase en acción: PiezaEnInventario como molde, y bulon, tuerca, arandela como objetos concretos. Ahora formalizamos esa distinción — es la base sobre la que se construye todo lo demás en POO.
La distinción entre clase y objeto es la primera y más fundamental de toda la POO. Confundirlos es el error conceptual más común en los primeros pasos.
La clase: el molde, no la cosa¶
Una clase es una plantilla o molde que describe cómo serán los objetos que se creen a partir de ella. Define qué datos van a tener (atributos) y qué comportamiento van a exhibir (métodos). Por sí sola, una clase no ocupa espacio de datos útil ni ejecuta nada: es solo la descripción.
La analogía clásica es la del plano de arquitectura: el plano de una casa no es una casa. Es la descripción de cómo construir una. Podés construir diez casas idénticas a partir del mismo plano, y cada una va a ser independiente de las demás.
En Python, una clase se define con la palabra reservada class seguida del nombre en PascalCase (también llamado CamelCase: cada palabra empieza con mayúscula):
El objeto: la instancia concreta¶
Un objeto es una instancia concreta de una clase, creada en tiempo de ejecución. Mientras la clase existe en el código fuente, el objeto existe en la memoria de la computadora mientras el programa se ejecuta.
Se crea invocando la clase como si fuera una función, con el operador ():
pieza_a = Pieza() # se crea una instancia de Pieza
pieza_b = Pieza() # una instancia diferente, independiente
pieza_a y pieza_b son dos objetos distintos, cada uno con su propia identidad en memoria.
El constructor: __init__¶
El método __init__ es el constructor de la clase. Python lo llama automáticamente cada vez que se crea un nuevo objeto. Su propósito es inicializar el estado del objeto: asignar valores a los atributos que va a tener.
class Pieza:
"""
Pieza industrial con número de serie, material y peso.
Args:
numero_serie: Identificador único de la pieza.
material: Tipo de material (acero, aluminio, etc.).
peso: Peso en kilogramos.
"""
def __init__(self, numero_serie: str, material: str, peso: float) -> None:
self.numero_serie = numero_serie
self.material = material
self.peso = peso
Cuando escribís Pieza("P-0001", "acero", 2.3), Python crea un objeto vacío y llama a __init__ pasando ese objeto como primer argumento (que dentro del método se llama self).
self: la referencia al objeto actual¶
self es el primer parámetro de cualquier método de instancia y representa al objeto sobre el que se está operando. Python lo pasa automáticamente: cuando llamás pieza.describir(), Python llama internamente a Pieza.describir(pieza).
No es una palabra reservada (podría llamarse diferente), pero es una convención tan universal que nunca deberías cambiarla.
class Pieza:
"""
Pieza industrial con número de serie, material y peso.
Args:
numero_serie: Identificador único de la pieza.
material: Tipo de material (acero, aluminio, etc.).
peso: Peso en kilogramos.
"""
def __init__(self, numero_serie: str, material: str, peso: float) -> None:
self.numero_serie = numero_serie # atributo de instancia
self.material = material # atributo de instancia
self.peso = peso # atributo de instancia
def describir(self) -> str:
"""Retorna una descripción legible de la pieza."""
return f"Pieza {self.numero_serie}: {self.material}, {self.peso:.2f} kg"
Atributos de instancia: el estado de cada objeto¶
Los atributos de instancia son variables que pertenecen a cada objeto individualmente. Se crean asignándolos a través de self en __init__. Cada objeto tiene su propio espacio de memoria para estos atributos: modificar el atributo de un objeto no afecta al otro.
Identidad, estado y comportamiento¶
Todo objeto tiene tres dimensiones:
- Identidad: lo que lo hace único en memoria (su dirección). En Python,
id(objeto)la retorna. - Estado: los valores actuales de sus atributos.
- Comportamiento: las operaciones que puede realizar (sus métodos).
class Pieza:
"""
Pieza industrial con número de serie, material, peso y grosor.
Args:
numero_serie: Identificador único de la pieza.
material: Tipo de material (acero, aluminio, etc.).
peso: Peso en kilogramos.
grosor: Grosor en milímetros.
"""
def __init__(
self, numero_serie: str, material: str, peso: float, grosor: float = 1.0
) -> None:
self.numero_serie = numero_serie
self.material = material
self.peso = peso
self.grosor = grosor
def describir(self) -> str:
"""Retorna una descripción legible de la pieza."""
return f"Pieza {self.numero_serie}: {self.material}, {self.peso:.2f} kg"
def aplicar_desgaste(self, porcentaje: float) -> None:
"""Reduce el grosor por desgaste durante el procesamiento.
Args:
porcentaje: Porcentaje de reducción del grosor (0-100).
"""
if not 0 < porcentaje <= 100:
raise ValueError("El porcentaje debe estar entre 0 y 100")
self.grosor *= (1 - porcentaje / 100)
def __repr__(self) -> str:
return f"Pieza({self.numero_serie!r}, {self.material!r}, {self.peso:.2f}kg)"
# Instanciación: se crean dos objetos independientes
pieza_1 = Pieza("P-0001", "acero", 2.3, grosor=5.0)
pieza_2 = Pieza("P-0002", "aluminio", 0.8, grosor=3.0)
# Identidad: son distintos objetos en memoria
print(id(pieza_1) == id(pieza_2)) # False
# Estado: cada objeto tiene su propio estado
print(pieza_1.describir()) # Pieza P-0001: acero, 2.30 kg
print(pieza_2.describir()) # Pieza P-0002: aluminio, 0.80 kg
# Independencia: modificar uno no afecta al otro
pieza_1.aplicar_desgaste(10)
print(pieza_1.grosor) # 4.5
print(pieza_2.grosor) # 3.0 ← sin cambios
Las instancias son independientes entre sí¶
Este punto merece énfasis: cada objeto es su propio mundo. Crear cien instancias de Pieza produce cien objetos independientes. El estado de uno no interfiere con el de los demás.
# Tres piezas, tres estados independientes
piezas = [
Pieza("P-0010", "acero", 1.5, grosor=4.0),
Pieza("P-0011", "aluminio", 0.9, grosor=2.5),
Pieza("P-0012", "acero", 2.1, grosor=6.0),
]
# Aplicar desgaste solo a la primera
piezas[0].aplicar_desgaste(20)
for p in piezas:
print(p)
# Pieza('P-0010', 'acero', 1.50kg) ← modificada
# Pieza('P-0011', 'aluminio', 0.90kg) ← sin cambios
# Pieza('P-0012', 'acero', 2.10kg) ← sin cambios
En la práctica: un error clásico de quienes empiezan es usar atributos de clase (definidos directamente en el cuerpo de la clase, fuera de
__init__) pensando que son atributos de instancia. Los atributos de clase son compartidos por todas las instancias, lo cual raramente es lo que se quiere. Siempre asigná el estado inicial del objeto dentro de__init__usandoself.
Ver también¶
Con la distinción clase / objeto clara, el siguiente paso es entender cómo los métodos de instancia trabajan con ese estado:
- Métodos de Instancia — cómo
selfreferencia al objeto y qué rol cumplen los métodos - Encapsulamiento — cómo controlar quién puede modificar los atributos