8.5. locals et globals

Laissons de coté le traitement du HTML une minute pour parler de la manière dont Python gère les variables. Python a deux fonctions prédéfinies permettant d’accéder aux variables locales et globales sous forme de dictionnaire : locals et globals.

Vous vous rappelez de locals ? Vous l'avez vu pour la première fois ici :

    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())

Mais vous ne pouvez rien apprendre sur locals pour le moment. Vous devez d'abord apprendre les espaces de noms. C’est un concept un peu aride mais important, lisez donc attentivement.

Python utilise ce que l’on appelle des espaces de noms pour suivre les variables. Un espace de noms est semblable a un dictionnaire dans lequel les clés sont les noms des variables et les valeurs du dictionnaire sont les valeurs des variables. En fait, on accède à un espace de noms comme à un dictionnaire Python, comme nous le verrons un peu plus loin.

A n’importe quel point dans un programme Python, il y a plusieurs espaces de noms disponibles. Chaque fonction a son propre espace de noms, appelé espace de noms local, qui suit les variables de la fonction, y compris ses arguments et les variables définies localement. Chaque module a son propre espace de noms, appelé l’espace de noms global, qui suit les variables du module, y compris les fonctions, les classes, les modules importés et les variables et constantes du module. Il y a également un espace de noms prédéfini, accessible de n’importe quel module et qui contient les fonctions et exceptions du langage.

Lorsqu’une ligne de code demande la valeur d’une variable x, Python recherche cette variable dans tous les espaces de noms disponibles dans l’ordre suivant :

  1. Espace de noms local - spécifique à la fonction ou méthode de classe en cours. Si la fonction a défini une variable locale x, ou si elle a un argument x, Python l’utilise et arrête sa recherche.
  2. Espace de noms global - spécifique au module en cours. Si le module a défini une variable, une fonction ou une classe nommée x, Python l’utilise et arrête sa recherche.
  3. Espace de noms prédéfini - global à tous les modules. En dernière instance, Python considère que x est le nom d’une fonction ou variable du langage.

Si Python ne trouve x dans aucun de ces espaces de noms, il abandonne et déclenche une exception NameError avec le message There is no variable named 'x', que vous avez vu tout au début au chapitre 1, mais à ce moment là vous ne pouviez pas savoir tout le travail que Python fait avant de vous renvoyer cette erreur.

Important
Python 2.2 a introduit une modification légère mais importante qui affecte l’ordre de recherche dans les espaces de noms : les portées imbriquées. Dans les versions précédentes de Python, lorsque vous référenciez une variable dans une fonction imbriquée ou une fonction lambda, Python recherchait la variable dans l’espace de noms de la fonction (imbriquée ou lambda) en cours, puis dans l’espace de noms du module. Python 2.2 recherche la variable dans l’espace de noms de la fonction (imbriquée ou lambda) en cours, puis dans l’espace de noms de la fonction parente, puis dans l’espace de noms du module. Python 2.1 peut adopter l'un ou l'autre de ces comportements, par défaut il fonctionne comme Python 2.0, mais vous pouvez ajouter la ligne de code suivante au début de vos modules pour les faire fonctionner comme avec Python 2.2 :

from __future__ import nested_scopes

Vous êtes perdu ? Ne vous inquiétez pas, je vous promet que c'est très utile. Comme beaucoup de chose en Python, les espaces de noms sont directement accessibles durant l’exécution. L’espace de noms local est accessible par la fonction prédéfinie locals et l’espace de noms global (du module) est accessible par la fonction prédéfinie globals.

Exemple 8.10. Présentation 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 fonction foo a deux variables dans son espace de noms local : arg, dont la valeur est passée à la fonction et x, qui est définie dans la fonction.
2 locals retourne un dictionnaire de paires nom/valeur. Les clés du dictionnaire sont les noms des variables sous forme de chaînes, les valeurs du dictionnaire sont les valeurs des variables. Donc, appeler foo avec 7 affiche un dictionnaire contenant les deux variables locales de la fonction : arg (7) et x (1).
3 Rappelez-vous que Python est à typage dynamique, vous pouvez donc passer une chaîne pour arg, la fonction (et l’appel à locals) fonctionne tout aussi bien. locals fonctionne avec toutes les variables de tous les types.

Ce que fait locals pour l’espace de noms local (de la fonction), globals le fait pour l’espace de noms global (du module). globals est cependant plus intéressant, parce que l’espace de noms d’un module est plus intéressant. L’espace de noms d’un module ne comprend pas seulement les variables et constantes du module mais aussi l’ensemble des fonctions et classes définies dans le module. De plus, il contient tout ce qui a été importé dans le module.

Vous rappelez-vous de la différence entre from module import et import module ? Avec import module, le module lui-meme est importé mais il garde son propre espace de noms, c’est pourquoi vous devez utiliser le nom du module pour accéder à ses fonctions ou attributs : module.fonction. Mais avec from module import, vous importez des fonctions et des attributs spécifiques d’un autre module dans votre propre espace de noms, c’est pourquoi vous y accédez directement sans référence au module dont ils viennent. Avec la fonction globals, vous pouvez voir ce qui se passe.

Exemple 8.11. Présentation de globals

Regardez me bloc de code suivant, à la fin de BaseHTMLProcessor.py:


if __name__ == "__main__":
    for k, v in globals().items():             1
        print k, "=", v
1 Ne soyez pas intimidé, vous avez déjà vu tout cela. La fonction globals retourne un dictionnaire et nous parcourons le dictionnaire à l’aide de la méthode items et de l’assignement multiple. Le seul élément nouveau ici est la fonction globals.

Maintenant, exécuter le programme de la ligne de commande nous donne la sortie suivante (notez qu'elle peut être légèrement différente en fonction de votre plate-forme et de l'endroit où vous avez installé 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
... rest of output omitted for brevity...
1 SGMLParser a été importé de sgmllib, en utilisant from module import. Cela veut dire qu’il a été importé directement dans l’espace de noms du module, et nous le voyons donc s’afficher.
2 Comparez avec htmlentitydefs, qui a été importé avec import. Le module htmlentitydefs lui-même est dans notre espace de noms, mais la variable entitydefs définie dans htmlentitydefs ne l’est pas.
3 Ce module ne définit qu’une classe, BaseHTMLProcessor et la voici. Notez que la valeur est ici la classe elle-même et non une instance quelconque de cette classe.
4 Vous rappelez-vous de l’astuce if __name__ ? Lorsque vous exécutez un module (plutôt que de l’importer d’un autre module), l’attribut prédéfini __name__ a une valeur spéciale, __main__. Comme nous avons exécuté ce module comme un programme de la ligne de commande, __name__ vaut __main__, c’est pourquoi notre petit code de test qui affiche globals est exécuté.
NOTE
A l’aide des fonctions locals et globals, vous pouvez obtenir la valeur d’une variable quelconque dynamiquement, en fournissant le nom de la variable sous forme de chaîne. C’est une fonctionnalité semblable à celle de la fonction getattr, qui vous permet d’accéder à une fonction quelconque dynamiquement en fournissant le nom de la fonction sous la forme d’une chaîne.

Il y a une différence importante entre locals et globals que vous devez apprendre maintenant pour ne pas qu’elle vous joue des tours plus tard. Elle vous jouera des tours de toute manière mais au moins vous vous souviendrez que vous l’avez appris.

Exemple 8.12. locals est en lecture seule, globals ne l'est pas


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 Puisque foo est appelé avec 3 en paramètre, cela affichera {'arg': 3, 'x': 1}. Cela ne devrait pas surprendre.
2 locals est une fonction qui retourne un dictionnaire et ici vous changez une valeur dans ce dictionnaire. Vous pourriez penser que cela change la valeur de la variable locale x à 2, mais ce n’est pas le cas. locals ne retourne pas vraiment l’espace de noms local mais une copie, modifier le dictionnaire retourné ne change pas les variables de l’espace de noms local.
3 Ceci affiche x= 1, pas x= 2.
4 Après avoir été déçu par locals, vous pourriez penser que cela ne va pas changer la valeur de z, mais ce serait une erreur. Pour des raisons ayant trait à l’implémentation de Python (dans le détail desquelles je ne rentrerais pas, ne les comprenant pas totalement), globals retourne l’espace de noms global lui-même et non une copie, le comportement inverse de locals. Donc, toute modification du dictionnaire retourné par globals affecte les variables globales.
5 Ceci affiche z= 8, pas z= 7.