Objetivo de aprendizaje
Al concluir este tema, el estudiante será capaz de:
- Describir qué ocurre en la memoria al crear un objeto (clase, instancia, constructor y
self). - Explicar el ciclo de vida de un objeto y comparar cómo lo administran Python, Java y C++ (conteo de referencias, recolector de basura, destrucción por ámbito).
- Diferenciar los atributos de clase de los de instancia y evitar la trampa del atributo mutable compartido.
- Distinguir los métodos de instancia, de clase y estáticos, y elegir el adecuado.
- Comparar los modificadores de acceso de Python, Java y C++, y usar propiedades para calcular o proteger atributos.
Ruta de trabajo del tema
| Etapa | Contenido | Producto del estudiante |
|---|---|---|
| 1 | Planteamiento inicial | Hipótesis sobre una lista compartida |
| 2 | Clase e instancia: qué ocurre al crear un objeto | Descripción de los cuatro pasos |
| 3 | Ciclo de vida: constructores y destructores | Predicción de cuándo se libera un objeto |
| 4 | Atributos de clase y de instancia | Regla para evitar la trampa |
| 5 | Métodos de instancia, de clase y estáticos | Criterio de elección |
| 6 | Modificadores de acceso y propiedades | Clasificación en tres lenguajes |
| 7 | Verificación y reformulación | Cuestionario y respuesta revisada |
1. Planteamiento inicial
Considere el siguiente programa. Cada socio de la biblioteca guarda la lista de sus préstamos.
class Socio:
prestamos = []
def __init__(self, nombre):
self.nombre = nombre
def pedir(self, titulo):
self.prestamos.append(titulo)
a = Socio("Ana")
b = Socio("Beto")
a.pedir("Fisica I")
b.pedir("Calculo")
print(a.prestamos)
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Esta hipótesis se retomará en la sección 4.
2. Clase e instancia: qué ocurre al crear un objeto
Definición — Clase e instancia: la clase es una plantilla que describe cómo serán los objetos (sus atributos y métodos). Una instancia es un objeto concreto creado a partir de ella. En Python, la clase es también un objeto: existe en la memoria, tiene un nombre ligado a ella y guarda sus métodos y atributos.
Cuando el programa ejecuta la sentencia class Socio: ..., no se crea ningún socio: se crea un objeto clase y se liga a él el nombre Socio. Esto puede observarse en la corrida: el cuerpo de la clase se ejecuta en un marco propio y, al terminar, sus nombres (métodos y atributos de clase) quedan guardados dentro del objeto clase.
Una llamada como Socio("Ana") ejecuta cuatro pasos:
| Paso | Qué ocurre | Dónde se observa |
|---|---|---|
| 1 | Se busca el nombre Socio y se obtiene el objeto clase |
Marco global |
| 2 | Se crea un objeto nuevo, vacío, ligado a su clase | Aparece una tarjeta de objeto sin atributos |
| 3 | Se ejecuta __init__ con el objeto nuevo como self y los argumentos |
Marco Socio.__init__ en la pila |
| 4 | Se devuelve el objeto y se liga al nombre de la izquierda | El nombre apunta al objeto |
class Socio:
total = 0 # atributo de CLASE: vive en el objeto clase
def __init__(self, nombre): # constructor: recibe el objeto recién creado en "self"
self.nombre = nombre # atributo de INSTANCIA: vive en el objeto de este socio
Socio.total += 1 # se modifica el atributo de la clase
a = Socio("Ana") # pasos 1 a 4
b = Socio("Beto")
print(Socio.total, a.total, b.total)
a.total = 99 # crea un atributo de instancia que OCULTA al de la clase
print(Socio.total, a.total, b.total)
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Active «Predecir la salida» y compare con el programa. En el panel de búsqueda de nombres observe dónde encuentra Python cada atributo.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Error común: creer que
a.total = 99cambia el contador de todos los socios. Asignar por medio de una instancia siempre crea o modifica el atributo de la instancia.
3. Ciclo de vida: constructores y destructores
Definición — Ciclo de vida de un objeto: etapas por las que pasa un objeto: construcción (se reserva y se inicializa), uso y destrucción (se libera su memoria y los recursos que posea). El constructor inicializa el objeto; el destructor (cuando existe) se ejecuta antes de que el objeto desaparezca.
En el tema 2.2 se estudió que la memoria se administra por conteo de referencias o por recolección de basura. Los objetos siguen exactamente esas reglas. En Python (CPython), cuando el conteo de un objeto llega a cero, el objeto se libera y se ejecuta su método __del__, si lo tiene.
class Conexion:
def __init__(self, nombre):
self.nombre = nombre
print("abre", nombre) # constructor: adquiere el recurso
def __del__(self):
print("cierra", self.nombre) # destructor: libera el recurso
a = Conexion("A") # conteo de referencias: 1
b = a # conteo: 2 (dos nombres, un objeto)
a = None # conteo: 1 (el objeto sigue vivo)
print("A sigue viva")
b = None # conteo: 0 -> se ejecuta __del__
print("fin")
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Active «Predecir la salida» y anticipe el orden de los mensajes.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Los ciclos de referencias
Si dos objetos se referencian entre sí, el conteo de cada uno nunca llega a cero aunque el programa ya no pueda alcanzarlos. Python complementa el conteo con un recolector de ciclos; el modelo de esta página, que solo usa conteo, muestra el problema.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Python, Java y C++ frente a frente
| Aspecto | Python (CPython) | Java | C++ |
|---|---|---|---|
| Constructor | __init__ (inicializa; el objeto ya fue creado) |
Constructor con el nombre de la clase | Constructor con el nombre de la clase |
| Destructor | __del__ (momento ligado al conteo; ejecución al salir del intérprete no garantizada) |
No hay destructor fiable; finalize está obsoleto |
Destructor ~Clase(), determinista |
| Quién libera | Conteo de referencias y recolector de ciclos | Recolector de basura de la JVM | Al salir del ámbito (objetos locales) o manualmente (delete) |
| Recursos (archivos, conexiones) | Administrador de contexto with |
try-with-resources |
RAII: el destructor libera el recurso |
Definición — RAII: idioma de C++ (Resource Acquisition Is Initialization): un recurso se adquiere en el constructor de un objeto local y se libera en su destructor, de modo que el recurso se libera automáticamente al salir del bloque, incluso si ocurre un error.
Clasifique el mecanismo que se aplica en cada fragmento. Los fragmentos de Java y C++ son afirmaciones didácticas; no se ejecutan.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Importante: que CPython libere el objeto en cuanto su conteo llega a cero es un detalle de implementación de CPython, no una garantía del lenguaje Python. Otras implementaciones (por ejemplo PyPy) liberan con otra estrategia. Para recursos que deben cerrarse, se utiliza
with, no__del__.
4. Atributos de clase y de instancia
Retome la pregunta del inicio. En el programa del socio, prestamos = [] está en el cuerpo de la clase: la lista se crea una vez, cuando se ejecuta class. Los objetos a y b no tienen un atributo prestamos propio; cuando se evalúa self.prestamos, Python no lo encuentra en el objeto y lo busca en la clase. Ambos encuentran la misma lista, y append la muta.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
La corrección es crear la lista dentro del constructor, de modo que cada objeto tenga la suya:
class Socio:
def __init__(self, nombre):
self.nombre = nombre
self.prestamos = [] # atributo de INSTANCIA: una lista nueva por cada socio
Regla práctica: los atributos de clase son adecuados para constantes y valores compartidos inmutables (LIMITE = 3, PLAZO = 7). Los atributos que cada objeto debe tener por separado, sobre todo si son mutables, se crean en __init__.
Error común:
self.n += 1sobre un atributo de clase. Equivale aself.n = self.n + 1: lee el valor de la clase y crea un atributo de instancia con el resultado. El contador de la clase no cambia. Para incrementarlo se escribeSocio.n += 1.
5. Métodos de instancia, de clase y estáticos
| Clase de método | Recibe | Se declara con | Sirve para |
|---|---|---|---|
| De instancia | El objeto (self) |
def metodo(self, ...) |
Operar sobre el estado de un objeto concreto |
| De clase | La clase (cls) |
@classmethod |
Operaciones de la clase en su conjunto; constructores alternativos |
| Estático | Nada | @staticmethod |
Una función relacionada con la clase que no necesita ni objeto ni clase |
class Socio:
def __init__(self, matricula, nombre):
self.matricula = matricula
self.nombre = nombre
def saludar(self): # de INSTANCIA: usa self
return "Hola, " + self.nombre
@classmethod
def desde_texto(cls, texto): # de CLASE: recibe la clase en "cls"
partes = texto.split(",")
return cls(int(partes[0]), partes[1]) # construye una instancia: constructor alternativo
@staticmethod
def matricula_valida(m): # ESTÁTICO: no recibe ni self ni cls
return m > 2000000
s = Socio.desde_texto("2024001,Ana") # se invoca sobre la clase
print(s.saludar()) # se invoca sobre la instancia
print(Socio.matricula_valida(s.matricula))
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Nota: en Java y en C++ existen los equivalentes: los miembros declarados con
staticpertenecen a la clase y no a cada objeto. Java no tiene un equivalente directo de@classmethod; los métodos estáticos de fábrica (static Socio desdeTexto(String t)) cumplen ese papel.
6. Modificadores de acceso y propiedades
Definición — Modificador de acceso: declaración que indica desde qué código puede utilizarse un miembro de una clase. Los habituales son público (cualquiera), protegido (la clase y sus subclases) y privado (solo la clase).
| Nivel | Python | Java | C++ |
|---|---|---|---|
| Público | Cualquier nombre sin guion bajo inicial | public |
public |
| Protegido | Convención: un guion bajo inicial (_x) |
protected (también acceso por paquete) |
protected |
| Privado | Convención reforzada: doble guion bajo (__x, name mangling) |
private |
private (por omisión en class) |
| Quién lo verifica | Nadie: es disciplina del programador | El compilador | El compilador |
La diferencia esencial es quién hace cumplir la regla. En Java y C++ un acceso indebido no compila. En Python, la regla es de convención: el doble guion bajo transforma el nombre (__pin se almacena como _Cuenta__pin) para evitar colisiones y accesos accidentales, pero basta conocer la regla para acceder.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Los fragmentos siguientes están en tres lenguajes. Determine qué ocurre con cada uno. Los de Java y C++ son afirmaciones didácticas; no se ejecutan.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Propiedades: atributos que ejecutan código
Una propiedad se lee y se escribe como un atributo, pero ejecuta un método. Permite (1) calcular un valor a partir de otros, sin almacenarlo, y (2) validar al asignar, sin cambiar la sintaxis de quien usa la clase.
class Prestamo:
MULTA_DIA = 5
def __init__(self, vence):
self.vence = vence
self.hoy = vence
@property
def retraso(self): # se calcula cada vez que se lee
return max(0, self.hoy - self.vence)
@property
def multa(self): # usa otra propiedad
return self.retraso * Prestamo.MULTA_DIA
p = Prestamo(7)
p.hoy = 12
print(p.retraso, p.multa) # 5 25
p.hoy = 5
print(p.retraso, p.multa) # 0 0
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Error común: guardar como atributo algo que puede calcularse. Si se guardara
multacomo dato, habría que acordarse de actualizarla cada vez que cambiehoyovence.
7. Verificación y reformulación
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
8. Libreta de trabajo
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
9. Preguntas de reflexión
- Explique con el modelo de objetos y referencias por qué dos socios creados con la clase de la sección 1 comparten la lista de préstamos. ¿Qué cambiaría si
prestamosse asignara con una tupla en lugar de una lista? - ¿Qué ventajas tiene la destrucción determinista de C++ frente a la recolección de basura de Java? ¿Qué costo tiene? Relacione su respuesta con los criterios de la pragmática (tema 1.2.3).
- Si Python no verifica el acceso a los atributos «privados», ¿para qué sirve el name mangling? ¿Qué problema concreto evita?
- Proponga una clase de su dominio en la que un método de clase sea un constructor alternativo útil.
- ¿En qué situaciones no debería usarse una propiedad calculada? Considere el costo del cálculo y los efectos secundarios.
10. Lecturas recomendadas
- Sebesta, R. (2015). Concepts of programming languages (11th ed.), capítulo sobre soporte para la programación orientada a objetos (elementos de clases, constructores y destructores, control de acceso). Pearson.
- Python Software Foundation. The Python Language Reference, sección «Data model» (
__init__,__del__, atributos de clase y de instancia). Acceso libre en docs.python.org. - Python Software Foundation. The Python Tutorial, secciones «Classes» y «Private Variables». Acceso libre en docs.python.org.
- Stroustrup, B. (2013). The C++ programming language (4th ed.). Addison-Wesley. (Constructores, destructores y RAII.)