Python: Variables et espaces de noms

Pour une lecture plus agréable (page plus large), je vous invite à cliquer sur ce lien et à lire ce chapitre dans la rubrique consacrée au langage Python.

Introduction

Dans le chapitre précédent, nous avons pu constater que grâce à la programmation orientée objet, deux développeurs, Briface et Jobriel, pouvaient coder un module chacun de leur côté et que ces deux modules pouvaient être « assemblés » in fine pour donner naissance au programme de notre choix.

Même si ces deux modules renfermaient un objet ou une variable dont le nom était identique, il n’y avait pas de risques que le programme se comporte de manière erratique puisque chaque objet évoluait dans son propre espace de noms. C’est la raison pour laquelle  une commande telle que self.random_choice.random_choice ne levait pas d’exception. 

La notion d’espace de noms dans le langage Python

La notion d’espace de noms est très importante dans le langage Python. Lorsque nous définissons une fonction avec des variables à l’intérieur, ces dernières ne peuvent vivre que dans l’espace de noms de cette fonction. Dès que celle-ci a exécuté sa dernière ligne d’instruction, les variables qui y séjournent deviennent hors de portée. Voici un exemple :

#!/usr/bin/env python3
# -*- coding: utf8 -*-

def first_names():
    lady_1 = "Gabiche"
    print(lady_1)

first_names() #appel de fonction

Résultat : Gabiche.

La fonction affiche bien la valeur contenue dans la variable définie localement.

Maintenant, nous allons modifier la dernière ligne du programme. Au lieu d’appeler la fonction, nous allons tenter de faire un print() de la variable lady_1.

#!/usr/bin/env python3
# -*- coding: utf8 -*-

def first_names():
    lady_1 = "Gabiche"
    print(lady_1)

print(lady_1)

Traceback (most recent call last):
File « /home/ordinosor/1_exemple.py », line 8, in
print(lady_1)
NameError: name ‘lady_1’ is not defined

Godverdomme! Keskispasse? Eh bien, nous avons tout simplement essayé d’afficher la valeur d’une variable qui est hors de portée puisqu’elle est définie à l’intérieur de la fonction.

À présent, nous allons essayer de faire le contraire. Nous allons définir une variable lady_2  dite globale c’est-à-dire à l’extérieur de toute fonction, et dans la fonction first_names, nous allons tenter de faire un print(lady_2).

#!/usr/bin/env python3
# -*- coding: utf8 -*-

lady_2 = "Amandale"

def first_names():
    print(lady_2)

first_names() #Appel de fonction

Résultat : Amandale.

Là, dans cet exemple, lady_2 n’est pas définie à l’intérieur de la fonction, et pourtant le programme ne plante pas! Warum?

En fait, Python va d’abord chercher dans l’espace local une variable portant le nom de lady_2. S’il ne la trouve pas, alors il va poursuivre sa recherche à l’extérieur de la fonction, c’est-à-dire dans l’espace global où il existe bien une variable définie avec le nom lady_2. Par conséquent, la fonction print() peut récupérer la valeur de cette variable dite globale.

Maintenant, définissons deux variables portant le même nom mais ne stockant pas la même valeur… Une variable globale lady_2 et une variable locale lady_2 dans la fonction. Reprenons le code précédent:

#!/usr/bin/env python3
# -*- coding: utf8 -*-

lady_2 = "Amandale"

def first_names():
    lady_2 = "Mandy-Bulle"
    print(lady_2)

first_names() #Appel de fonction

Résultat : Mandy-Bulle.

Python a d’abord effectué une recherche en local, dans l’espace de noms de la fonction. Il a réussi à trouver une variable nommé lady_2. Donc, la recherche s’est immédiatement interrompue et la fonction print() a récupéré la valeur contenue dans cette variable locale, à savoir Mandy-Bulle.

Le mot-clé global

Il n’est pas possible de modifier la valeur de la variable globale lady_2 à l’intérieur de la fonction… A moins de lui affecter au préalable le mot-clé global mais cette technique est parfaitement déconseillée car dans un gros programme, si quelqu’un modifie la valeur stockée dans la variable, il y a un gros risque que ce changement impacte sans le vouloir d’autres parties du programme. Gros risque de bugs en perspective!

#!/usr/bin/env python3
# -*- coding: utf8 -*-

lady_2 = "Amandale"

def first_names():
    global lady_2
    lady_2 = "Mandy-Bulle"

print(lady_2) #Variable globale

Résultat : Mandy-Bulle.

Amandale a disparu du programme…

Et avec la méthode append() ?

Créons une liste ladies_list et essayons voir…

#!/usr/bin/env python3
# -*- coding: utf8 -*-

ladies_list = ["Amandale", "Gabiche"] 

def first_names():
    ladies_list.append("Mandy-Bulle")

first_names() #Appel de fonction
print(ladies_list) #Variable globale

Résultat : [« Amandale », « Gabiche », « Mandy-Bulle »].

Nous avons réussi à modifier la liste malgré le fait que la méthode append() soit présente à l’intérieur de la fonction. Pourquoi donc? Parce que nous avons fait appel à une méthode qui appartient à l’objet ladies_list. En agissant ainsi, aucune barrière d’espace de noms ne peut nous empêcher de mener à bien notre modification.

Mais il est impossible de modifier la variable globale ladies_list en lui affectant une nouvelle valeur à l’intérieur de la fonction.

#!/usr/bin/env python3
# -*- coding: utf8 -*-

ladies_list = ["Amandale", "Gabiche"] 

def first_names():
    ladies_list = ["Mandy-Bulle", "Dominille"]
    print(ladies_list)

first_names() #Appel de fonction
print(ladies_list) #Variable globale

[« Mandy-Bulle », « Dominille »] : Résutat qui correspond au print() de la variable locale ladies_list dans la fonction first_names (à la ligne n° 10).

[« Amandale », « Gabiche »] : Résultat qui correspond au print() de la variable globale ladies_list (à la ligne n° 11). Nous n’avons pas réussi à modifier cette dernière.

Avec le slicing

#!/usr/bin/env python3
# -*- coding: utf8 -*-

ladies_list = ["Amandale", "Gabiche"] 

def first_names():
    ladies_list[1:1] = ["Mandy-Bulle"]

first_names() #Appel de fonction
print(ladies_list) #Variable globale

Résultat : [« Amandale », « Mandy-Bulle », « Gabiche »]

Avec la technique du slicing, le résultat est positif car en fait, sans le savoir, nous utilisons la méthode constructeur de la classe slice. Elle agit directement sur l’objet et là encore, aucune barrière d’espace de noms ne peut nous empêcher de mener à bien notre modification. C’est comme si nous avions écrit ce code (ligne n° 7) :

#!/usr/bin/env python3
# -*- coding: utf8 -*-

ladies_list = ["Amandale", "Gabiche"] 

def first_names():
    ladies_list[slice(1,1)] = ["Mandy-Bulle"]

first_names() #Appel de fonction
print(ladies_list) #Variable globale

Attributs d’instance et attributs de classe

Il existe également une hiérarchie entre les espaces de noms des instances et des classes.

  • Les instances et les classes peuvent utiliser les variables définies au niveau global mais elles ne peuvent pas les modifier.

Dans le code ci-dessous, nous avons trois types de variables :

  • Une variable globale
  • Un attribut de classe
  • Un attribut d’instance
#!/usr/bin/env python3
# -*- coding: utf8 -*-

man_1 = "Dentifritz_1" #Variable globale

class FirstName :
    """Test avec des prénoms"""

    man_1 = "Dentifritz_2" #Attribut de classe

    def first_name(self):
        """test des variables"""

        man_1 = "Sébanislas"
        self.man_1 = "Barnabulle"

#===== MAIN ===================================================================

if __name__ == '__main__':

    test = FirstName()
    test.first_name()
    print(man_1) #Variable globale
    print(FirstName.man_1) #Attribut(ou variable) de classe
    print(test.man_1) #Attribut (ou variable) d'instance

Ligne n° 23 : Dentifritz_1

Ligne n° 24 : Dentifritz_2

Ligne n° 25 : Barnabulle

Je vous invite à modifier ce code et à faire autant de tests que nécessaire pour bien comprendre le concept d’espaces de noms et la hiérarchie qui les gouverne.

#Python : classes et méthodes (partie 2) – mise en pratique

Pour une lecture plus agréable (page plus large), je vous invite à cliquer sur ce lien et à lire ce chapitre dans la rubrique consacrée au langage Python.

Nous allons confier à deux programmeurs en herbe, Briface et Jobriel, le soin de rédiger le code d’une petite application à caractère pédagogique. Elle consiste à faire apparaître des formes générées aléatoirement.

Jobriel va rédiger le code nécessaire à la création de l’environnement graphique, c’est-à-dire les widgets tandis que Briface, de son côté, va rédiger le module permettant de faire apparaître de manière aléatoire, des formes qui sont soit des triangles, soit des cercles, soit des carrés. La couleur de ces formes est également définie de manière aléatoire.

Au final, voici ce que nos deux gugusses doivent produire :

Le code de Jobriel

Commençons par le code de notre ami Jobriel :


#!/usr/bin/env python3
# -*- coding: utf8 -*-

from tkinter import*
import choice

class MainProg :
    """Création de l'environnement graphique de l'application"""

    def __init__(self, main_window, h = 300, w = 300, color = 'white'):
        """Méthode constructeur"""

        self.main_window = main_window
        self.h = h
        self.w = w
        self.color = color

    def widgets(self):
        """Création des widgets"""

        self.main_frame = Frame(self.main_window, height=self.h,\
                      width=self.w, bg=self.color)
        self.main_frame.pack()

        self.canevas = Canvas(self.main_frame, height=400, width=self.w,\
                   bg=self.color)
        self.canevas.pack(side=TOP)

        self.frame_1 = Frame(self.main_frame, height=100, width=self.w,\
                   bg=self.color)
        self.frame_1.pack(side=BOTTOM)

        self.random_choice = choice.RandomObjects(self.canevas)

        self.button = Button(self.frame_1, height=1, width=5, text='Afficher',\
                  relief='ridge', bd=3, bg='navy', fg='white',\
                  font=('Times', 14, 'bold'), padx=20,\
                  command = self.random_choice.random_choice)
        self.button.pack(padx=20, pady=10)

#===== MAIN ===================================================================

if __name__ == '__main__':

    main_window = Tk()
    main_window.title('Une forme au hasard')

    main_page = MainProg(main_window, 500, 500)
    main_page.widgets()

    main_window.mainloop()

ligne n° 4 : importation du module tkinter.
ligne n° 5 : importation du module choice qui va nous permettre de faire apparaître de manière aléatoire, différentes formes telles que des carrés, des ronds, des triangles. Les couleurs de ces formes sont également définies de manière aléatoire. Pour les deux formes d’importation, je vous renvoie au chapitre sur les modules. Personnellement, je préfère la forme d’importation ci-dessous car il n’y a pas de risques de conflits entre des variables portant le même nom. En outre, elle permet de raccourcir le nom du module. tkinter devient tk :

import tkinter as tk 

Pour de plus amples renseignements sur la bibliothèque tkinter, je vous invite à consulter cette documentation en français.

Ligne n° 43 : Nous trouvons une condition introduisant du code qui va s’exécuter uniquement si le programme est lancé de manière indépendante et non pas comme un module. Ici, c’est le cas. Par conséquent, cette partie du code s’exécute.

Ligne n° 45 : Création de l’objet main_window (fenêtre principale) par instanciation de la classe Tk() issue du module tkinter. Si j’avais écrit ceci à la ligne n° 4 : import tkinter as tk, alors la ligne n° 45 aurait été : main_window = tk.Tk().

Ligne n° 46 : application de la méthode title() sur l’objet main_window. Elle donne un titre à la fenêtre.

À ce stade, si on exécute le programme, voici ce qu’on obtient :

main_window

Ligne n° 48 : Création de l’objet main_page par instanciation de la classe MainProg(). Nous lui passons trois arguments qui sont main_window (la fenêtre mère), 500 (la hauteur de l’application) et 500 (la largeur). Nous choisissons de ne pas lui passer de paramètre de couleur. Ce sera donc la couleur par défaut qui va s’appliquer.
Ligne n° 49 : Application de la méthode widgets() sur l’objet main_page.

Ligne n° 51 : Application de la méthode mainloop() sur l’objet main_window ce qui a pour effet de déclencher le réceptionnaire d’événements et de permettre l’exécution du programme.

Ligne n° 7 : On remonte tout en haut! Définition d’une classe fondamentale baptisée MainProg
Ligne n° 10 : Méthode constructeur qui prend quatre paramètres:

  • self qui correspond à la référence de l’instance, c’est-à-dire l’objet main_page que nous avons instancié à la ligne n° 48. je rappelle qu’une méthode d’instance prend toujours au moins cet argument et que par convention, il est nommé self.
  • main_window qui correspond à la fenêtre principale et dont nous avons besoin pour instancier les widgets enfants.
  • h, w et color sont des paramètres qui possèdent une valeur par défaut. Si nous décidons de les ignorer, ce seront donc ces valeurs qui vont s’appliquer. C’est ce que nous avons fait avec color. Nous avons ignoré ce paramètre. Pour ce qui est des autres valeurs par défaut (h=300, w=300), nous avons choisi de les remplacer par 500 (voir ligne n° 48). Si nous décidons de ne pas respecter l’ordre de déclaration, il faut alors préciser le paramètre. Par exemple :

main_page = MainProg(main_window, color = ‘blue’, w=260, h=600)

Ligne n° 11 : Docstring

Lignes n° 13 à 16 : Création des attributs d’instance
Ligne n° 18 : Méthode widgets (création de tous les widgets de l’application)
Lignes n° 21 et 22 : Création de l’objet self.main_frame par instanciation de la classe Frame. Cette dernière créé un cadre que l’on peut remplir avec d’autres widgets. Nous pouvons lui passer plusieurs arguments. Notez bien la largeur (width) et la hauteur (height). Normalement, vous devez être capables de retrouver leurs valeurs. Notez également la barre oblique (antislash) qui permet d’écrire une instruction sur plusieurs lignes. La fondation Python préconise de ne pas dépasser 79 caractères par ligne, ce qui est assez strict. Dans la mesure du possible, il faut respecter cette convention.

Ligne n° 23 : Application de la méthode pack(). Cette dernière place le widget. Elle peut prendre des paramètres tels que TOP, BOTTOM, LEFT ou RIGHT qui poussent le widget dans la direction donnée.

À ce stade, si nous exécutons le programme, voici ce que nous obtenons. J’ai volontairement et provisoirement coloré en rouge, le fond du widget :

main_frame

Lignes n° 25 et 26 : Création de l’objet self.canevas par instanciation de la classe Canvas. C’est ce widget qui va nous permettre de placer les formes aléatoires grâce à différentes méthodes qui lui sont liées.
Ligne n° 27 : Méthode de placement pack(). Cette fois-ci, elle prend un paramètre (side=TOP) pour pousser le widget vers le haut.
Lignes n° 29 à 31: Nous créons un nouvel objet de type Frame que nous plaçons tout en bas de sa fenêtre parente (side=BOTTOM). Dans ce widget, nous placerons le bouton Afficher.

Ligne n° 33: Création de l’objet random_choice par instanciation de la classe RandomObjects du module importé choice. Nous lui passons un paramètre qui est self.canevas. Donc, cela nous donne:

self.random_choice = choice.RandomObjects(self.canevas)

Je rappelle que l’utilisation du point en Python signifie l’appartenance. Ainsi, la classe RandomObjects appartient au module choice.

Lignes n° 35 à 38: Création du bouton afficher par instanciation de la classe Button. Celle-ci prend plusieurs paramètres qui sont :

  • La hauteur
  • La largeur
  • Le texte à afficher
  • Le relief de la bordure
  • L’épaisseur de la bordure
  • La couleur de fond
  • La couleur du texte
  • La police et la fonte de caractères
  • L’espacement horizontal du texte à l’intérieur du widget
  • La commande que le bouton déclenche.

Pour ce dernier paramètre, nous appliquons la méthode random_choice issue du module importé choice, sur l’objet que nous avons créé (random_choice). Cela nous donne :

command=self.random_choice.random_choice

Alors là, vous allez me dire : « Mais Ordinosor! Tu bé, tu bé-bé, tu bégayes? »

Absolument pas! Il n’y a aucun risque de collision ou de confusion puisque ces deux noms identiques n’évoluent pas dans les mêmes espaces.

Notez bien que la commande déclenche une méthode lorsque nous cliquons sur le bouton. Il ne faut donc pas mettre les parenthèses () à la fin de la commande car cela aurait pour effet de shunter le bouton et d’activer la méthode dès le lancement du programme! Donc, il ne faut pas écrire :

command=self.random_choice.random_choice()

Ligne n° 39: La méthode pack() place le bouton dans sa fenêtre parente en réservant un espace vertical (pady=20) et horizontal (padx=20).

J’ai coloré provisoirement les différents widgets pour mieux les visualiser. Si nous exécutons le programme à ce stade, voici ce que nous obtenons:

  • En rouge, le widget self.main_frame
  • En blanc, le widget self.canevas
  • En jaune, le widget self.frame_1
  • En bleu, le widget self.button

button2

Merci pour ce travail, Jobriel! À présent, remettons tous les fonds des widgets en blanc Cliquons sur le bouton et voyons ce que notre ami Briface a codé…

Le code de Briface


#!/usr/bin/env python3
# -*- coding: utf8 -*-

from tkinter import*
import random

class RandomObjects :
    """Instancie aléatoirement les formes (triangles, cercles, carrés).
    Les couleurs de ces formes sont également définies de manière aléatoire"""

    def __init__(self, canevas):
        """Constructeur"""

        self.canevas = canevas

    def square(self):
        """Création du carré"""

        self.square_object = self.canevas.create_rectangle(200, 150, 300, 250,\
                             fill=self.random_color)

    def circle(self):
        """Création du cercle"""

        self.circle_object = self.canevas.create_oval(200, 150, 300, 250,\
                             fill=self.random_color)

    def triangle(self):
        """Création du triangle"""

        self.polygon = self.canevas.create_polygon(250, 150, 200, 250, 300,\
                       250,fill=self.random_color)

    def random_choice(self):
        """cf docstring class RandomObjects"""

        self.canevas.delete('all')
        self.random_color=random.choice(['green','red','yellow','blue','black'])
        random.choice([self.square, self.circle, self.triangle])()

Ligne n° 5 : importation du module random qui va nous permettre de faire des choix aléatoires.
Ligne n° 7: Création la classe RandomObjects.
Ligne n° 11: Méthode constructeur avec deux paramètres, la référence d’instance self et canevas.
Ligne n° 13: Déclaration d’une variable d’instance.
Ligne n° 16: Définition d’une méthode qui dessine un carré.
Ligne n° 19: Pour cela, nous utilisons la méthode create_rectangle qui prend quatre coordonnés en arguments ainsi qu’une couleur de remplissage.
Ligne n° 22: Définition d’une méthode qui dessine un cercle plein.
Ligne n° 25: Pour cela, nous utilisons la méthode create_oval qui prend quatre coordonnés en arguments ainsi qu’une couleur de remplissage.
Ligne n° 28: Définition d’une méthode qui dessine un triangle.
Ligne n° 31: Pour cela, nous utilisons la méthode create_polygon qui prend six coordonnés en arguments ainsi qu’une couleur de remplissage.

Pour de plus amples détails, je vous invite à consulter cette documentation en français.

Ligne n° 34: Définition de la méthode random_choice.
Ligne n° 37: Nous appliquons la méthode delete(‘all’) sur le widget self.canevas pour effacer une éventuelle forme avant que la suivante ne prenne sa place.
Ligne n° 38: Déclaration de la variable self.random_color. La valeur qui lui est affectée est une couleur choisie au hasard dans la liste passée en argument à la méthode random.choice.

Là encore, attention de ne pas confondre la méthode random_choice que nous avons nous-même définie et la méthode random.choice, c’est-à-dire la méthode choice du module importé random (ligne n° 5)!

Ligne n° 39: Cette fois-ci, random.choice choisit au hasard la méthode qui va matérialiser la forme. Pour que cette méthode exécute ses instructions, nous rajoutons tout à la fin, deux parenthèses. Sans ces deux parenthèses, rien ne se passerait:
random.choice([self.square, self.circle, self.triangle])()

Merci à toi , Briface, pour ce module qui exécute à la perfection la tâche qui lui a été impartie.

Ce diaporama nécessite JavaScript.

Python : classes et méthodes (partie 1), avec un peu de menuiserie en prime.

Pour une lecture plus agréable (page plus large), je vous invite à cliquer sur ce lien et à lire ce chapitre dans la rubrique consacrée au langage Python.

En programmation orientée objet (POO) et notamment dans le langage Python, tout est objet ! En fait, c’est un peu comme dans le monde réel… Nous sommes entourés d’objets de toutes sortes. Continuer à lire … « Python : classes et méthodes (partie 1), avec un peu de menuiserie en prime. »

Python : les fichiers (3/3) – La sérialisation des données avec le module pickle

Pour une lecture plus agréable (page plus large), je vous invite à cliquer sur ce lien et à lire ce chapitre dans la rubrique consacrée au langage Python.

Dans ce chapitre, nous allons découvrir un module très intéressant puisqu’il permet d’enregistrer des données dans un fichier binaire et de les restituer ultérieurement avec leur type initial. Continuer à lire … « Python : les fichiers (3/3) – La sérialisation des données avec le module pickle »

Python: les fichiers (2/3) – le module os

Pour une lecture plus agréable (page plus large), je vous invite à cliquer sur ce lien et à lire ce chapitre dans la rubrique consacrée au langage Python.

Lorsque j’ouvre un fichier en mode écriture et que ce dernier n’existe pas, alors il est créé dans le répertoire courant, c’est-à dire à l’endroit ou le script est lancé. Prenons un exemple concret. Continuer à lire … « Python: les fichiers (2/3) – le module os »

Python: les fichiers (1/3)

Pour une lecture plus agréable (page plus large), je vous invite à cliquer sur ce lien et à lire ce chapitre dans la rubrique consacrée au langage Python.

Introduction

Dans le chapitre 2, nous avons vu que les variables étaient des objets comparables à des tiroirs dans lesquels on pouvait déposer des données de différents types (nombres entiers, chaînes de caractères, etc…). Mais lorsque nous fermons notre programme, ces variables qui sont créées dans la RAM, c’est-à dire la mémoire vive de l’ordinateur, disparaissent corps et âmes. Il ne sert donc à rien d’y stocker des données que l’on souhaite conserver après la fermeture du programme puisque de toute façon, elles seront perdues. Comment faire alors? Continuer à lire … « Python: les fichiers (1/3) »

#Python: 3126 pages, 13 cm d’épaisseur… Du lourd!

Lundi dernier, le facteur a eu bien du mal à glisser dans ma boite à lettres, le courrier qui m’était destiné. Et pour cause! Il s’agissait de deux grimoires de Mark Lutz intitulés Learning Python et Programming Python que j’avais commandés quelques jours auparavant. 3126 pages, 13 cm d’épaisseur, tout en anglais! Joie!

Ce diaporama nécessite JavaScript.

Continuer à lire … « #Python: 3126 pages, 13 cm d’épaisseur… Du lourd! »