18.1. La pila de ejecución de las funciones
Si miramos el siguiente segmento de código y su ejecución podemos
comprobar que, pese a tener el mismo nombre, la variable de x
de la
función f
y la variable de x
de la función g
no tienen nada que ver:
una y otra se refieren a valores distintos, y modificar una no modifica a la
otra.
def f():
x = 50
a = 20
print "En f, x vale", x
def g():
x = 10
b = 45
print "En g, antes de llamar a f, x vale", x
f()
print "En g, después de llamar a f, x vale", x
Esta es la ejecución de g()
:
>>> g()
En g, antes de llamar a f, x vale 10
En f, x vale 50
En g, después de llamar a f, x vale 10
Este comportamiento lo hemos ido viendo desde el principio, sin embargo, nunca se explicó porqué sucede. Vamos a ver en esta sección cómo se ejecutan las llamadas a funciones, para comprender cuál es la razón de este comportamiento.
Cada función tiene asociado por un lado un código (el texto del
programa) que se ejecutará, y por el otro un conjunto de variables que
le son propias (en este caso x
y a
se asocian con f
y x
y b
se
asocian con g
) y que no se confunden entre sí pese a tener el mismo nombre
(no debería llamarnos la atención ya que después de todo conocemos a
muchas personas que tienen el mismo nombre, en este caso la función a
la que pertenecen funciona como una especie de "apellido").
Estos nombres asociados a una función los va descubriendo el intérprete de Python a medida que va ejecutando el programa (hay otros lenguajes en los que los nombres se descubren todos juntos antes de iniciar la ejecución).
La ejecución del programa se puede modelar por la siguiente tabla:
Instrucción | Contexto de g |
Contexto de f |
Resultado impreso |
---|---|---|---|
g() |
Creado pero vacío | No existe | - |
x = 10 |
x → 10 |
No existe | - |
b = 45 |
x → 10 b → 45 |
No existe | - |
print |
x → 10 b → 45 |
No existe | En g, antes de llamar a f, x vale 10 |
f() |
x → 10 b → 45 |
Creado pero vacío | - |
x = 50 |
x → 10 b → 45 |
x → 50 |
- |
a = 20 |
x → 10 b → 45 |
x → 50 a → 20 |
- |
print |
x → 10 b → 45 |
x → 50 a → 20 |
En f, x vale 50 |
print |
x → 10 b → 45 |
No existe | En g, despues de llamar a f, x vale 10 |
- | No existe | No existe | - |
Se puede observar que:
Cuando se invoca a g
, se arma un contexto vacío para contener las
referencias a las variables asociadas con g
. Ese contexto se apila sobre
una pila vacía.
Cuando se ejecuta dentro de g
la invocación f()
se apila un
contexto vacío que va a alojar las variables asociadas con f
(y se
transfiere el control del programa a la primera instrucción de f
). El
contexto de g
queda debajo del tope de la pila, y por lo tanto el
intérprete no lo ve.
Mientras se ejecuta f
, todo el tiempo el intérprete busca los valores
que necesita usando el contexto que está en el tope de la pila.
Después de ejecutar el final de la ejecución de f
, se desapila el contexto de
f
y reaparece el contexto de g
en el tope de la pila. Sigue ejecutando g
a partir de donde se suspendió por la invocación a f
. g
sólo ve su
contexto en el tope de la pila.
Después de ejecutar todas las instrucciones, se encuentra el final de la ejecución de g
. Se desapila el contexto de g
y queda la pila vacía.
El ámbito de definición de una variable está constituido por todas las partes del programa desde donde esa variable se ve.