Objetivo de aprendizaje
Al concluir este tema, el estudiante será capaz de:
- Definir bloque, entorno, alcance y tiempo de vida, y distinguir el alcance (dónde se ve un nombre) del tiempo de vida (cuándo existe un objeto).
- Resolver un nombre con la regla LEGB de Python y predecir el resultado de programas con funciones anidadas,
globalynonlocal. - Contrastar el alcance estático con el dinámico y justificar por qué los lenguajes modernos adoptan el primero.
- Describir la pila de marcos de activación y el montículo, y seguir el ciclo de vida de los objetos.
- Comparar las estrategias de administración de memoria: manual, recolección automática y propiedad.
Ruta de trabajo del tema
| Etapa | Contenido | Producto del estudiante |
|---|---|---|
| 1 | Planteamiento inicial | Hipótesis escrita |
| 2 | Bloque, entorno, alcance y tiempo de vida | Vocabulario preciso |
| 3 | Resolución de nombres: la regla LEGB | Predicciones verificadas |
| 4 | global, nonlocal y cierres |
Reglas propias para cada declaración |
| 5 | Alcance estático frente a dinámico | Argumento sobre el diseño |
| 6 | La pila y los marcos de activación | Lectura de una pila de llamadas |
| 7 | El montículo y la liberación de la memoria | Clasificación de problemas de memoria |
| 8 | Verificación y reformulación | Cuestionario y respuesta revisada |
1. Planteamiento inicial
En el tema anterior, cada nombre estaba ligado a un objeto y, en los programas cortos, bastaba con consultar la tabla de nombres. Los programas reales se dividen en funciones, y dentro de ellas aparecen nombres que se repiten: i, x, total. Cabe preguntar qué nombre se está usando cuando se escribe x y dónde vive lo que designa.
Considere el programa siguiente en Python:
x = 1
def f():
print(x)
x = 2
f()
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Esta hipótesis se retomará al final de la sección 4.
2. Bloque, entorno, alcance y tiempo de vida
Definición — Bloque: región del texto del programa que puede introducir nombres nuevos: el cuerpo de una función, un módulo o, en algunos lenguajes, cualquier secuencia delimitada por llaves.
Definición — Entorno (environment): conjunto de ligaduras vigentes en un punto de la ejecución; es la tabla que responde a la pregunta «¿a qué objeto se refiere este nombre ahora?». En Python, cada llamada a una función crea un entorno propio.
Definición — Alcance (scope): región del programa donde un nombre es visible y designa una ligadura concreta.
Definición — Tiempo de vida: intervalo de la ejecución durante el cual un objeto existe en la memoria.
Alcance y tiempo de vida son conceptos distintos: el primero es una propiedad del texto del programa (dónde se puede escribir un nombre); el segundo, de la ejecución (cuánto dura un objeto). Un objeto puede seguir vivo aunque ya no exista ningún nombre visible que lo designe en cierto lugar, y un nombre puede estar en alcance sin que su objeto haya sido creado todavía.
| Propiedad | Pertenece a | Pregunta que responde |
|---|---|---|
| Alcance | El texto del programa | ¿Dónde puede usarse este nombre? |
| Tiempo de vida | La ejecución | ¿Cuándo existe este objeto en la memoria? |
3. Resolución de nombres: la regla LEGB
Cuando Python encuentra un nombre dentro de una función, lo busca en cuatro ámbitos, en este orden, y se detiene en el primero donde lo encuentra.
Definición — Regla LEGB: orden de búsqueda de un nombre en Python: Local (la función actual), Enclosing (las funciones que la contienen, de adentro hacia afuera), Global (el módulo) y Built-in (los nombres incorporados, como
len).
Esta regla es una forma de alcance estático (o léxico): la búsqueda sigue la estructura del texto, es decir, dónde está escrita la función, y no quién la llama.
Caso de estudio. En el programa de la corrida siguiente, la función interna usa el nombre x sin definirlo. La búsqueda continúa hacia afuera: no está en interna, está en externa; ahí se detiene. El x global no interviene.
Aplicación práctica. La corrida muestra, debajo del diagrama, la búsqueda de nombres de cada paso: cada ámbito consultado se marca con ✓ (encontrado) o ✗ (no encontrado). Active la predicción de la salida antes de cada programa.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Nota: en Python solo crean un ámbito local las funciones (y las clases, y las expresiones de comprensión). En Java y C++ cada bloque delimitado por llaves puede declarar sus propias variables locales, que dejan de existir al cerrarse el bloque. Este es un ejemplo de una decisión de diseño con consecuencias prácticas.
4. global, nonlocal y cierres
Dentro de una función, asignar a un nombre crea, por omisión, una variable local. Python toma esa decisión al leer todo el cuerpo de la función, antes de ejecutarla: si el nombre se asigna en algún punto de la función, es local en todo el cuerpo.
Caso de estudio. En el programa de la sección 1, x = 2 convierte a x en local de f. Por eso print(x), que se ejecuta antes de la asignación, intenta leer una variable local que todavía no tiene valor y provoca un UnboundLocalError. El x global no se consulta.
Para modificar un nombre de otro ámbito se usan dos declaraciones.
| Declaración | Efecto | Cuándo se usa |
|---|---|---|
global x |
Los usos de x en la función se refieren a la variable del módulo |
Para asignar a una variable global desde una función |
nonlocal x |
Los usos de x se refieren a la variable de la función envolvente más cercana |
Para asignar a una variable de una función externa (cierres) |
Definición — Cierre (closure): función que conserva el entorno en que fue definida, aunque se ejecute después de que la función que la contenía haya terminado. En el ejemplo del contador, cada llamada a la función externa produce un entorno propio, y la función interna que se devuelve lo conserva.
Importante: las variables globales modificadas desde muchas funciones dificultan el razonamiento sobre el programa: para saber qué valor tiene una variable hay que conocer todas las funciones que pueden cambiarla. Se prefiere pasar valores como parámetros y devolver resultados.
Aplicación práctica.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Contraste con el planteamiento inicial
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
5. Alcance estático frente a dinámico
Definición — Alcance estático y dinámico: con alcance estático (léxico), el significado de un nombre libre se determina por la estructura del texto: dónde está escrita la función. Con alcance dinámico, se determina por la cadena de llamadas en tiempo de ejecución: quién llamó a quién.
| Criterio | Alcance estático | Alcance dinámico |
|---|---|---|
| Se determina por | La estructura del texto | La secuencia de llamadas en ejecución |
| Previsibilidad | Alta: se lee la función y se sabe qué designa un nombre | Baja: depende de quién la llamó |
| Lenguajes | Python, Java, C, Haskell, Scheme | Variables de entorno de algunos intérpretes de órdenes (shells); versiones antiguas de Lisp; variables «especiales» de Common Lisp |
| Riesgo | Ninguno inherente | Un cambio en una función lejana de la cadena de llamadas altera lo que ve otra función sin que su código cambie |
Caso de estudio. Una función mostrar imprime el nombre libre limite. Si la llama otra función que ha definido su propia variable limite, ¿qué debería imprimir mostrar? Con alcance estático, siempre el limite que está a la vista donde mostrar fue escrita. Con alcance dinámico, el del llamador más reciente que lo defina. Con el segundo criterio, para entender mostrar habría que revisar todas las funciones que pueden llamarla.
Aplicación práctica. El programa se ejecuta con ambas reglas. Anticipe la salida de cada una y compare. Python usa alcance estático; el dinámico se simula aquí solo para contrastar.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Importante: el alcance dinámico dificulta el razonamiento local sobre el código. Esa es la razón principal por la que casi todos los lenguajes modernos adoptan el alcance estático.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
6. La pila y los marcos de activación
Cada vez que se llama a una función, el sistema reserva una zona de memoria para esa llamada.
Definición — Marco de activación (stack frame): registro que contiene los datos de una llamada en curso: los parámetros, las variables locales y la información de retorno (a dónde regresar). Los marcos se apilan en la pila de llamadas: la última llamada es la primera en terminar (orden LIFO).
Definición — Pila de llamadas: estructura en la que cada llamada apila un marco y cada retorno lo desapila. El marco en el tope corresponde a la función que se está ejecutando.
Dos consecuencias importantes: las variables locales dejan de existir cuando el marco se retira, y la recursión apila un marco por cada llamada pendiente, cada uno con sus propios valores.
Caso de estudio. En factorial(3), la llamada con n = 3 queda pendiente a la espera del resultado de factorial(2), que a su vez espera a factorial(1). Hay tantos marcos como llamadas pendientes; al llegar al caso base los marcos se retiran uno por uno, multiplicando en el camino de regreso.
Importante — límite del modelo: en CPython, los marcos son objetos del montículo y la pila de Python es conceptual. La presentación en forma de pila es una forma correcta de razonar sobre el orden de las llamadas y la vida de las variables locales.
Aplicación práctica. En la corrida siguiente aparecen, a la derecha, los marcos (el del tope está resaltado) y los objetos con su conteo de referencias.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
7. El montículo y la liberación de la memoria
Definición — Montículo (heap): zona de memoria de asignación dinámica, cuyos objetos tienen un tiempo de vida que no está ligado al de la función que los crea. Requiere una estrategia para decidir cuándo liberar cada objeto.
En Python todos los objetos viven en el montículo; los marcos solo contienen referencias a ellos. Por eso un objeto creado dentro de una función puede sobrevivir a ella si se devuelve o se guarda en otro lugar, mientras que una lista que solo estaba referenciada desde el marco se libera al retirarse este.
Estrategias de liberación del montículo:
| Estrategia | Cómo funciona | Lenguajes | Ventaja | Riesgo |
|---|---|---|---|---|
| Manual | El programador reserva y libera explícitamente (malloc/free, new/delete) |
C, C++ | Control total y rendimiento predecible | Fugas de memoria, uso de memoria liberada, liberación doble |
| Recolección automática | El entorno de ejecución detecta los objetos sin referencias y los libera | Java, Python, Go, JavaScript | Elimina clases enteras de errores | Costo de ejecución; pausas; la liberación no siempre es inmediata |
| Propiedad (ownership) | El compilador verifica quién es dueño de cada valor y libera de forma determinista | Rust | Seguridad sin recolector | Reglas más estrictas que el programador debe aprender |
Definición — Conteo de referencias: técnica de recolección en la que cada objeto lleva la cuenta de cuántas referencias apuntan a él; al llegar a cero, se libera. CPython la usa como mecanismo principal.
El conteo de referencias tiene un límite: dos objetos que se referencian entre sí (un ciclo) conservan un conteo mayor que cero aunque ya nadie pueda alcanzarlos. Por ello CPython incorpora, además, un recolector de ciclos que los detecta. Los recolectores del tipo «marcar y barrer» (como los de la JVM) no tienen este problema, pues parten de las referencias alcanzables.
Aplicación práctica. Ejecute los programas siguientes. El número «refs» de cada objeto es su conteo de referencias; los objetos liberados aparecen listados en el paso en que se liberan.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
8. Errores conceptuales frecuentes
Error común: pensar que una variable asignada después en la función no afecta a una lectura anterior. Python decide el carácter local de un nombre al leer toda la función.
Error común: creer que
del xlibera el objeto. Elimina el nombre; el objeto se libera solo si ya no queda ninguna referencia.
Error común: confundir alcance con tiempo de vida. Una variable puede estar fuera de alcance y su objeto seguir vivo (porque otro nombre lo referencia).
Error común: pensar que Python usa alcance dinámico porque «las variables se buscan al ejecutar». Los nombres se resuelven siguiendo la estructura del texto (LEGB), no la cadena de llamadas.
Error común: creer que la recolección automática elimina todos los problemas de memoria. Evita las fugas por olvido, pero un programa puede conservar referencias innecesarias y agotar la memoria.
9. Verificación del tema
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
10. Libreta de trabajo
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
11. Preguntas de reflexión
- ¿Qué problemas puede causar el alcance dinámico frente al estático en programas grandes? Ponga un ejemplo con tres funciones.
- ¿Por qué cree que Python decide el carácter local de un nombre al leer toda la función y no línea por línea? ¿Qué ventaja o desventaja tiene?
- ¿Qué ocurriría si una función recursiva no tuviera caso base? ¿Qué mecanismo detiene el programa y por qué?
- ¿Qué estrategia de administración de memoria elegiría para un firmware de una pieza pequeña y cuál para una aplicación web? Justifique con los criterios de la pragmática.
- Describa con sus palabras la diferencia entre «el nombre desaparece», «el objeto desaparece» y «el marco desaparece».
12. Lecturas recomendadas
- Sebesta, R. (2015). Concepts of programming languages (11th ed.), capítulo sobre nombres, ligaduras y alcance, y capítulo sobre la implementación de subprogramas (marcos de activación). Pearson.
- Python Software Foundation. The Python Language Reference, sección «Execution model» (nombres y ligaduras, alcance). Acceso libre en docs.python.org.
- Scott, M. (2015). Programming language pragmatics (4th ed.), capítulo sobre nombres, alcances y ligaduras. Morgan Kaufmann.