5.5. locals e globals

Python dispone di due funzioni built-in, locals e globals che forniscono un accesso basato sui dizionari alle variabili locali e globali.

Prima però, una parola sui namespace (ndt: spazio dei nomi). È un argomento noioso, ma importante, perciò fate attenzione. Python usa quelli che sono chiamati namespace per tenere traccia delle variabili. Un namespace è come un dizionario nel quale le chiavi sono i nomi delle variabili ed i valori del dizionario sono i valori di quelle variabili. Infatti, potete accedere ad un namespace come ad un dizionario Python, come vedremo fra un minuto.

Ad ogni particolare punto di un programma Python, ci sono alcuni namespace disponibili. Ogni funzione ha il suo namespace, chiamato namespace locale, il quale tiene traccia delle variabili della funzione, inclusi gli argomenti della funzione e le variabili definite localmente. Ogni modulo ha il suo proprio namespace, chiamato namespace globale, che tiene traccia delle variabili del modulo, incluse funzioni, classi, ogni altro modulo importato, variabili e costanti di livello modulo. E c'è anche il namespace built-in, accessibile da ogni modulo, che contiene le funzioni built-in e le eccezioni.

Quando una linea di codice chiede il valore della variabile x, Python ricerca quella variabile in tutti i namespace disponibili, nell'ordine:

  1. namespace locale - specifico alla funzione o metodo di classe corrente. Se la funzione definisce una variabile locale x, o ha un argomento x, Python userà questa e interromperà la ricerca.
  2. namespace globale - specifico al modulo corrente. Se il modulo ha definito una variabile, funzione, o classe chiamata x, Python userà quella ed interromperà la ricerca.
  3. namespace built-in - globale per tutti i moduli. Come il punto precedente, Python considererà quella x come il nome di una variabile o funzione built-in.

Se Python non trova x in nessuno di questi namespace, si fermerà e solleverà un'eccezione NameError con il messaggio There is no variable named 'x', come avete visto nel Capitolo 2, ma senza sapere quanto lavoro facesse Python prima di darvi quell'errore.

Importante
Python 2.2 introdusse un subdolo ma importante cambiamento che modificò l'ordine di ricerca dei namespace: gli scope nidificati. Nelle versioni di Python precedenti al 2.2, quando vi riferite ad una variabile dentro ad una funzione nidificata o una funzione lambda, Python ricercherà quella variabile nel namespace corrente della funzione, e poi nel namespace del modulo. Python 2.2 ricercherà la variabile nel namespace della funzione corrente, poi nel namespace della funzione genitore, e poi nel namespace del modulo. Python 2.1 può lavorare in ogni modo; predefinitamente, lavora come Python 2.0, ma potete aggiugere la seguente linea di codice in cima al vostro modulo per farlo funzionare come Python 2.2:

from __future__ import nested_scopes

Come molte cose in Python, i namespace sono direttamente accessibili a run-time. Specificatamente, il namespace locale è accessibile dalla funzione built-in locals, e quello globale (livello modulo) è accessibile dalla funzione built-in globals.

Esempio 5.10. Introdurre 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 funzione foo ha due variabili nel suo namespace locale: arg, il cui valore è passato nella funzione, ed x, che è definito nella funzione.
2 locals ritorna un dizionario di coppie nome/valore. Le chiavi di questo dizionario sono nomi delle variabili come fossero stringhe; i valori del dizionario sono gli attuali valori delle variabili. Perciò chiamando foo con 7 verrà stampato il dizionario contenente le due variabili della funzione: arg (7) ed x (1).
3 Ricordate, Python ha una tipizzazione dinamica, perciò potreste anche facilmente passare una stringa in arg; la funzione (e la chiamata a locals) funzionerebbe ancora bene. locals funziona con tutte le variabili di tutti i tipi di dato.

Ciò che locals fa per il namespace locale, globals lo fa per il namespace globale. globals è più eccitante quindi, perché il namespace di un modulo è più interessante. [9] Il namespace di un modulo non include solo le variabili e le costanti di livello modulo, include anche tutte le funzioni e le classi definite nel modulo. In più, include ogni cosa che è stata importata nel modulo.

Ricordate la differenza tra from module import e import module? Con import module, il modulo stesso è importato, ma mantiene il suo proprio namespace, visto che dovete usare il nome del modulo per accedere ad ogni sua funzione od attributo: modulo.funzione. Ma con from module import, state realmente importando funzioni ed attributi specifici da un altro modulo nel vostro namespace, perciò potete accedervi senza riferirvi direttamente al modulo da cui provengono. Con la funzione globals, potete vedere come ciò avvenga.

Esempio 5.11. Introdurre globals

Aggiungete il seguente blocco di codice a BaseHTMLProcessor.py:


if __name__ == "__main__":
    for k, v in globals().items():             1
        print k, "=", v
1 Non fatevi intimidire, ricordate che avete già visto tutto questo in precedenza. La funzione globals ritorna un dizionario, e stiamo iterando attraverso il dizionario usando il metodo items e l'assegnamento multi-variabile. L'unica cosa nuova qui è la funzione globals.

Ora lanciare lo script dalla linea di comando darà il seguente output:

c:\docbook\dip\py>python BaseHTMLProcessor.py
SGMLParser = sgmllib.SGMLParser                1
htmlentitydefs = <module 'htmlentitydefs' from 'C:\Python21\lib\htmlentitydefs.py'> 2
BaseHTMLProcessor = __main__.BaseHTMLProcessor 3
__name__ = __main__                            4
[...snip...]
1 SGMLParser è stato importato da sgmllib, usando from module import. Questo significa che è stato importato direttamente nel namespace del nostro modulo, e lì lo troviamo.
2 Al contrario il modulo htmlentitydefs, è stato importato usando import. Questo significa che il modulo htmlentitydefs stesso si trova nel nostro namespace, ma la variabile entitydefs definita nel modulo htmlentitydefs invece no.
3 Questo modulo definisce un'unica classe, BaseHTMLProcessor. Notate che il valore qui è la classe stessa, non una specifica istanza della classe.
4 Ricordate il trucco if __name__? Quando lanciate un modulo (al contrario di importarlo da un altro modulo), l'attributo built-in __name__ ottiene un valore speciale, __main__. Dato che abbiamo lanciato questo modulo come uno script dalla linea di comando, __name__ diventa __main__, questo giustifica il nostro piccolo script di test a stampare la globals appena eseguita.
Nota
Usando le funzioni locals e globals, potete ottenere il valore di variabili arbitrarie dinamicamente, rendendo disponibile il nome della variabile come una stringa. Questo rispecchia la funzionalità della funzione getattr, che vi permette di accedere a funzioni arbitrarie dinamicamente, rendendo disponibile il nome della funzione come stringa.

C'è un'altra importante differenza fra locals e globals che dovreste imparare adesso, prima di pagarne le conseguenze. Vi colpirà ugualmente, ma almeno poi la ricorderete, imparandola.

Esempio 5.12. locals è in sola lettura, 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 Dato che foo è chiamata con 3, stamperà {'arg': 3, 'x': 1}. Questo non dovrebbe essere una sorpresa.
2 Potreste pensare che questo cambierebbe il valore della variabile locale x a 2, ma non lo fa. locals non ritorna realmente il namespace locale, ritorna una copia. Perciò cambiarla non ha rilevanza sulle variabile del namespace locale.
3 Questo stampa x= 1, non x= 2.
4 Dopo essere rimasti scottati da locals, potreste pensare che questo non cambierebbe il valore di z, invece sì. A causa di differenze interne su come Python è implementato (che raramente spiego, dato che non le ho capite pienamente neppure io), globals ritorna l'attuale namespace globale, non una copia: l'esatto opposto del comportamento di locals. Perciò ogni cambiamento al dizionario ritornato da globals ha effetto sulle variabili globali.
5 Questo stampa z= 8, non z= 7.

Footnotes

[9] Non è che io esca poi molto. ;-)