• Aucun résultat trouvé

Même si on peut modifier un attribut de classe, nous vous déconseillons de le faire. Une utilité des attributs de classe est par exemple de définir des constantes (mathématique ou autre), donc cela n’a pas de sens de vouloir les modifier ! Il est également déconseillé de créer des attributs de classe avec des objets modifiables comme des listes et des dictionnaires, cela peut avoir des effets désastreux non désirés. Nous verrons plus bas un exemple concret d’attribut de classe qui est très utile, à savoir le concept d’objet de type property.

Si vous souhaitez avoir des attributs modifiables dans votre classe, créez plutôt des attributs d’instance dans le.__init__().

19.2 Espace de noms

Nous faisons ici une petite digression sur le concept d’espace de noms, car il est important de bien le comprendre lorsqu’on étudie les classes. Nous avons déjà croisé ce concept à plusieurs reprises. D’abord dans le chapitre 12 Plus sur les fonctions, puis dans le chapitre 14 Création de modules, et maintenant dans ce chapitre. De quoi s’agit-il ?

Définition

Dans la documentation officielle4, un espace de noms est défini comme suit : « a namespace is a mapping from names to objects». Un espace de noms, c’est finalement une correspondance entre des noms et des objets. Un espace de noms peut être vu aussi comme une capsule dans laquelle on trouve des noms d’objets. Par exemple, le programme principal ou une fonction représentent chacun un espace de noms, un module aussi, et bien sûr une classe ou l’instance d’une classe également.

Différents espaces de noms peuvent contenir des objets de même nom sans que cela ne pose de problème. Parce qu’ils sont chacun dans un espace différent, ils peuvent cohabiter sans risque d’écrasement de l’un par l’autre. Par exemple, à chaque fois que l’on appelle une fonction, un espace de noms est créé pour cette fonction. Python Tutor nous montre cet espace sous la forme d’une zone dédiée (voir les chapitres 9 et 12 sur les fonctions). Si cette fonction appelle une autre fonction, un nouvel espace est créé, bien distinct de la fonction appelante (ce nouvel espace peut donc contenir un objet de même nom). En définitive, ce qui va compter, c’est de savoir quelles règles Python va utiliser pour chercher dans les différents espaces de noms pour finalement accéder à un objet.

Nous allons dans cette rubrique refaire le point sur ce que l’on a appris dans cet ouvrage sur les espaces de noms en Python, puis se pencher sur les spécificités de ce concept dans les classes.

19.2.1 Rappel sur la règle LGI

Comme vu dans le chapitre 9 Fonctions, la règle LGI peut être résumée ainsi : Local > Global > Interne. Lorsque Python rencontre un objet, il utilise cette règle de priorité pour accéder à la valeur de celui-ci. Si on est dans une fonction (ou une méthode), Python va d’abord chercher l’espace de noms local à cette fonction. S’il ne trouve pas de nom il va ensuite chercher l’espace de noms du programme principal (ou celui du module), donc des variables globales s’y trouvant. S’il ne trouve pas de nom, il va chercher dans les commandes internes à Python (on parle des Built-in Functions5et des Built-in Constants6). Si aucun objet n’est trouvé, Python renvoie une erreur.

19.2.2 Gestion des noms dans les modules

Les modules représentent aussi un espace de noms en soi. Afin d’illustrer cela, jetons un coup d’œil à ce programme test_var_module.py : 1 import mod 2 3 i = 1000000 4 j = 2 5

6 print (" Dans prog principal i:", i) 7 print (" Dans prog principal j:", j) 8 9 mod . fct () 10 mod . fct2 () 4. https://docs.python.org/fr/3/tutorial/classes.html#python-scopes-and-namespaces 5. https://docs.python.org/fr/3/library/functions.html%20comme%20par%20exemple%20%60print()%60 6. https://docs.python.org/fr/3/library/constants.html

Chapitre 19. Avoir la classe avec les objets 19.2. Espace de noms

11

12 print (" Dans prog principal i:", i) 13 print (" Dans prog principal j:", j)

Le modulemod.py contient les instructions suivantes :

1 def fct ():

2 i = -27478524

3 print (" Dans module , i local :", i) 4

5

6 def fct2 ():

7 print (" Dans module , j global :", j) 8

9

10 i = 3.14 11 j = -76

L’exécution detest_var_module.py donnera :

1 $ python ./ test_var_module .py 2 Dans prog principal i: 1000000 3 Dans prog principal j: 2

4 Dans module , i local : -27478524 5 Dans module , j global : -76 6 Dans prog principal i: 1000000 7 Dans prog principal j: 2

Lignes 3 et 4. On a bien les valeurs dei et j définies dans le programme principal de test.py.

Lignes 9 et 10. Lorsqu’on exécutemod.fct(), la valeur de i sera celle définie localement dans cette fonction. Lorsqu’on exécutemod.fct2(), la valeur de j sera celle définie de manière globale dans le module.

Lignes 12 et 13. De retour dans notre programme principal, les variablesi et j existent toujours et n’ont pas été modifiées par l’exécution de fonctions du modulemod.py.

En résumé, lorsqu’on lance une méthode d’un module, c’est l’espace de noms de celui-ci qui est utilisé. Bien sûr, toutes les variables du programme principal / fonction / méthode appelant ce module sont conservées telles quelles, et on les retrouve intactes lorsque l’exécution de la fonction du module est terminée. Un module a donc son propre espace de noms qui est bien distinct de tout programme principal / fonction / méthode appelant un composant de ce module. Enfin, les variables globales créées dans notre programme principal ne sont pas accessibles dans le module lorsque celui-ci est en exécution.

19.2.3 Gestion des noms avec les classes

On vient de voir qu’un module avait son propre espace de noms, mais qu’en est-il des classes ? En utilisant les exemples vus depuis le début de ce chapitre, vous avez certainement la réponse. Une classe possède par définition son propre espace de noms qui ne peut être en aucun cas confondu avec celui d’une fonction ou d’un programme principal. Reprenons un exemple simple :

1 class Citron :

2 def __init__ (self , saveur =" acide ", couleur =" jaune "): 3 self . saveur = saveur

4 self . couleur = couleur

5 print (" Dans __init__ (), vous venez de créer un citron :", 6 self . affiche_attributs ())

7

8 def affiche_attributs ( self ):

9 return "{} , {}". format ( self . saveur , self . couleur ) 10

11

12 if __name__ == " __main__ ": 13 saveur = " sucr ée" 14 couleur = " orange "

15 print (" Dans prog principal : {}, {}". format ( saveur , couleur )) 16 citron1 = Citron (" très acide ", " jaune fonc é")

17 print (" Dans citron1 . affiche_attributs ():" , citron1 . affiche_attributs ()) 18 print (" Dans prog principal : {}, {}". format ( saveur , couleur ))

Lorsqu’on exécutera ce code, on obtiendra :

1 Dans prog principal : sucr ée, orange

2 Dans __init__ (), vous venez de créer un citron : jaune fonc é, très acide 3 Dans citron1 . affiche_attributs (): jaune fonc é, très acide

4 Dans prog principal : sucr ée, orange

Les deux variables globales saveur et couleur du programme principal ne peuvent pas être confondues avec les va-riables d’instance portant le même nom. Au sein de la classe, on utilisera pour récupérer ces dernières self.saveur et

19.2. Espace de noms Chapitre 19. Avoir la classe avec les objets

self.couleur. À l’extérieur, on utilisera instance.saveur et instance.couleur. Il n’y a donc aucun risque de confu-sion possible avec les variables globalessaveur et couleur, on accède à chaque variable de la classe avec un nom distinct (qu’on soit à l’intérieur ou à l’extérieur de la classe).

Ceci est également vrai pour les méthodes. Si par exemple, on a une méthode avec un certain nom, et une fonction du module principal avec le même nom, regardons ce qui se passe :

1 class Citron :

2 def __init__ ( self ):

3 self . couleur = " jaune " 4 self . affiche_coucou () 5 affiche_coucou () 6

7 def affiche_coucou ( self ): 8 print (" Coucou interne !") 9

10

11 def affiche_coucou (): 12 print (" Coucou externe ") 13 14 15 if __name__ == " __main__ ": 16 citron1 = Citron () 17 citron1 . affiche_coucou () 18 affiche_coucou ()

Lorsqu’on va exécuter le code, on obtiendra :

1 Coucou interne ! 2 Coucou externe 3 Coucou interne ! 4 Coucou externe

À nouveau, il n’y a pas de conflit possible pour l’utilisation d’une méthode ou d’une fonction avec le même nom. À l’intérieur de la classe on utilise self.affiche_coucou() pour la méthode et affiche_coucou() pour la fonction. À l’extérieur de la classe, on utiliseinstance.affiche_coucou() pour la méthode et affiche_coucou() pour la fonction.

Dans cette rubrique, nous venons de voir une propriété des classes extrêmement puissante : une classe crée automati-quement son propre espace de noms. Cela permet d’encapsuler à l’intérieur tous les attributs et méthodes dont on a besoin, sans avoir aucun risque de conflit de nom avec l’extérieur (variables locales, globales ou provenant de modules). L’utilisation de classes évitera ainsi l’utilisation de variables globales qui, on l’a vu aux chapitres 9 et 12 sur les fonctions, sont à proscrire absolument. Tout cela concourt à rendre le code plus lisible.

Dans le chapitre 20 Fenêtres graphiques et Tkinter, vous verrez une démonstration de l’utilité de tout encapsuler dans une classe afin d’éviter les variables globales.

19.2.4 Gestion des noms entre les attributs de classe et d’instance

Si vous lisez cette rubrique sur l’espace de noms sans avoir lu ce chapitre depuis le début, nous vous conseillons vivement de lire attentivement la rubrique Différence entre les attributs de classe et d’instance. La chose importante à retenir sur cette question est la suivante : si un attribut de classe et un attribut d’instance ont le même nom, c’est l’attribut d’instance qui est prioritaire.

Pour aller plus loin

Il existe d’autres règles concernant les espace de noms. L’une d’elle, que vous pourriez rencontrer, concerne la gestion des noms avec des fonctions imbriquées. Et oui, Python autorise cela ! Par exemple :

1 def fonction1 (): 2 [...]

3

4 def fct_dans_fonction1 ():

5 [...]

Là encore, il existe certaines règles de priorités d’accès aux objets spécifiques à ce genre de cas, avec l’apparition d’un nouveau mot-clé nomménonlocal. Toutefois ces aspects vont au-delà du présent ouvrage. Pour plus d’informations sur les fonctions imbriquées et la directivenonlocal, vous pouvez consulter la documentation officielle7.

D’autres subtilités concerneront la gestion des noms en cas de définition d’une nouvelle classe héritant d’une classe mère. Ces aspects sont présentés dans la rubrique Héritage de ce chapitre.

7. https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces