8.5. locals y globals

Hagamos por un minuto un inciso entre tanto procesamiento de HTML y hablemos sobre la manera en que Python gestiona las variables. Python incorpora dos funciones, locals y globals, que proporcionan acceso de tipo diccionario a las variables locales y globales.

¿Se acuerda de locals? La vio por primera vez aquí:

    def unknown_starttag(self, tag, attrs):
        strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])
        self.pieces.append("<%(tag)s%(strattrs)s>" % locals())

No, espere, todavía no puede aprender cosas sobre locals. Antes debe aprender sobre espacios de nombres. Es un material árido pero es importante, así que preste atención.

Python utiliza lo que llamamos espacios de nombres[7] para llevar un seguimiento de las variables. Un espacio de nombres es como un diccionario donde las claves son los nombres de las variables y los valores del diccionario son los de esas variables. En realidad, puede acceder a un espacio de nombres de Python como a un diccionario, como veremos en breve.

En cualquier momento en particular en un programa de Python se puede acceder a varios espacios de nombres. Cada función tiene su propio espacio de nombres, que llamamos espacio local, que contiene las variables que define la función, incluyendo sus argumentos y las variables definidas de forma local. Cada módulo tiene su propio espacio de nombres, denominado espacio global, que contiene las variables del módulo, incluyendo sus funciones, clases y otros módulos que haya importado, así como variables y constantes del módulo. Y hay un espacio de nombres incorporado, accesible desde cualquier módulo, que contiene funciones del lenguaje y excepciones.

Cuando una línea de código solicita el valor de una variable x, Python busca esa variable en todos los espacios de nombres disponibles, por orden:

  1. espacio local, específico a la función o método de clase actual. Si la función define una variable local x, o tiene un argumento x, Python usará ésta y dejará de buscar.
  2. espacio global, específico al módulo actual. Si el módulo ha definido una variable, función o clase llamada x, Python ésta y dejará de buscar.
  3. espacio incorporado, global a todos los módulos. Como último recurso, Python asumirá que x es el nombre de una función o variable incorporada por el lenguaje.

Si Python no encuentra x en ninguno de estos espacios, se rendirá y lanzará una NameError con el mensaje There is no variable named 'x', que ya vio en su momento en Ejemplo 3.18, “Referencia a una variable sin asignar”, pero no podía aún apreciar todo el trabajo que Python estaba realizando antes de mostrarle ese error.

importante
Python 2.2 introdujo un cambio sutil pero importante que afecta al orden de búsqueda en los espacios de nombre: los ámbitos anidados. En las versiones de Python anteriores a la 2.2, cuando hace referencia a una variable dentro de una función anidada o función lambda, Python buscará esa variable en el espacio de la función actual (anidada o lambda) y después en el espacio de nombres del módulo. Python 2.2 buscará la variable en el espacio de nombres de la función actual (anidada o lambda), después en el espacio de su función madre, y luego en el espacio del módulo. Python 2.1 puede funcionar de ambas maneras; por omisión lo hace como Python 2.0, pero puede añadir la siguiente línea al código al principio de su módulo para hacer que éste funcione como Python 2.2:

from __future__ import nested_scopes

¿Ya está confundido? ¡No desespere! Esto es realmente bueno, lo prometo. Como muchas cosas en Python, a los espacios de nombre se puede acceder directamente durante la ejecución. ¿Cómo? Bien, al espacio local de nombres se puede acceder mediante la función incorporada locals, y el espacio global (del módulo) es accesible mediante la función globals.

Ejemplo 8.10. Presentación de locals

>>> def foo(arg): 1
...     x = 1
...     print locals()
...     
>>> foo(7)        2
{'arg': 7, 'x': 1}
>>> foo('bar')    3
{'arg': 'bar', 'x': 1}
1 La función foo tiene dos variables en su espacio de nombres local: arg, cuyo valor se pasa a la función y x, que se define dentro de la función.
2 locals devuelve un diccionario de pares nombre/valor. Las claves de este diccionario son los nombres de las variables como cadenas; los valores del diccionario son los auténticos valores de las variables. De manera que invocar a foo con un 7 imprime el diccionario que contiene las dos variables locales de la función: arg (7) y x (1).
3 Recuerde, Python es de tipado dinámico, así que podría pasar una cadena en arg con la misma facilidad; la función (y la llamada a locals) funcionará igual de bien. locals funciona con todas las variables de todos los tipos.

Lo que hace locals con los espacios de nombres locales (función), lo hace globals para el espacio de nombres global (módulo). globals es más interesante, sin embargo, porque el espacio un módulo es más interesante.[8] El espacio de nombres del módulo no sólo incluye variables y constantes del módulo, también incluye todas las funciones y clases definidas en él. Además, incluye cualquier cosa que se haya importado dentro del módulo.

¿Recuerda la diferencia entre from módulo import e import módulo? Con import módulo se importa el módulo en sí, pero retiene su espacio de nombres, y esta es la razón por la que necesita usar el nombre del módulo para acceder a cualquiera de sus funciones o atributos: módulo.función. Pero con from módulo import, en realidad está importando funciones y atributos específicos de otro módulo al espacio de nombres del suyo propio, que es la razón por la que puede acceder a ellos directamente sin hacer referencia al módulo del que vinieron originalmente. Con la función globals puede ver esto en funcionamiento.

Ejemplo 8.11. Presentación de globals

Mire el siguiente bloque de código al final de BaseHTMLProcessor.py:


if __name__ == "__main__":
    for k, v in globals().items():             1
        print k, "=", v
1 Para que no se sienta intimidado, recuerde que ya ha visto todo esto antes. La función globals devuelve un diccionario y estamos iterando sobre él usando el método items y una asignación multivariable. La única cosa nueva es la función globals.

Ahora ejecutar el script desde la línea de órdenes nos da esta salida (tenga en cuenta que la suya puede ser ligeramente diferente, dependiendo de la plataforma en que haya instalado Python):

c:\docbook\dip\py> python BaseHTMLProcessor.py
SGMLParser = sgmllib.SGMLParser                1
htmlentitydefs = <module 'htmlentitydefs' from 'C:\Python23\lib\htmlentitydefs.py'> 2
BaseHTMLProcessor = __main__.BaseHTMLProcessor 3
__name__ = __main__                            4
... se omite el resto de la salida por abreviar ...
1 SGMLParser se importó desde sgmllib, usando from módulo import. Esto significa que lo importamos directamente en el espacio de nombres del módulo, y aquí está.
2 En contraste con esto tenemos htmlentitydefs, que importamos usando import. Esto significa que es el módulo htmlentitydefs quien se encuentra en el espacio de nombres, pero la variable entitydefs definida en su interior, no.
3 Este módulo sólo define una clase, BaseHTMLProcessor, y aquí la tenemos. Observe que el valor aquí es la propia clase, no una instancia específica de la clase.
4 ¿Recuerda el truco if __name__? Cuando ejecuta un módulo (en lugar de importarlo desde otro), el atributo __name__ que incorpora contiene un valor especial, __main__. Dado que ejecutamos el módulo desde la línea de órdenes, __name__ es __main__, y por eso se ejecuta el pequeño código de prueba que imprime globals.
nota
Puede obtener dinámicamente el valor de variables arbitrarias usando las funciones locals y globals, proporcionando el nombre de la variable en una cadena. Esto imita la funcionalidad de la función getattr, que le permite acceder a funciones arbitrarias de forma dinámica proporcionando el nombre de la función en una cadena.

Hay otra diferencia importante entre las funciones locals y globals que deberá aprender antes de que se pille los dedos con ella. Y se los pillará de todas maneras, pero al menos recordará haberlo aprendido.

Ejemplo 8.12. locals es de sólo lectura, globals no


def foo(arg):
    x = 1
    print locals()    1
    locals()["x"] = 2 2
    print "x=",x      3

z = 7
print "z=",z
foo(3)
globals()["z"] = 8    4
print "z=",z          5
1 Dado que llamamos a foo con 3, esto imprimirá {'arg': 3, 'x': 1}. No debería sorprenderle.
2 locals es una función que devuelve un diccionario, y aquí estamos asignando un valor a ese diccionario. Puede que piense que esto cambiará el valor de la variable local x a 2, pero no es así. locals no devuelve el verdadero espacio de nombres local, sino una copia. Así que cambiarlo no hace nada con los valores de las variables del espacio local.
3 Esto imprime x= 1, no x= 2.
4 Tras habernos quemado con locals podríamos pensar que esto no cambiaría el valor de z, pero sí lo hace. Debido a las diferencias internas en la manera que está implementado Python (en las que no voy a entrar, ya que no las comprendo completamente), globals devuelve el verdadero espacio de nombres global, no una copia: justo lo opuesto que locals. Así que los cambios que haga en el diccionario devuelto por globals afectan directamente a las variables globales.
5 Esto imprime z= 8, no z= 7.

Footnotes

[7] namespaces

[8] La verdad es que no salgo mucho a la calle.