python modules

Identification

Infoforall

33 - Création de modules


Exercices de révision de début de classe terminale : sur le site

Nous allons voir aujourd'hui ce qui se cache derrière le mot-clé import.

Nous allons voir qu'on peut dissimuler des instructions à l'intérieur d'un module, les encapsuler afin que l'utilisateur ne les voit pas.

Que pourra alors en faire l'utilisateur ? Uniquement les utiliser comme une simple boîte à outils...

Prérequis :

  • un peu de Turtle
  • Le programme de Python de 1er (fonctions, branchements, tableaux, dictionnaires...)

Documents de cours PDF : .PDF

Sources latex : .TEX et entete.tex et licence.tex

Evaluation ✎ :

1 - Sans module : un seul programme

Imaginons que vous vouliez créer pour un enfant un ensemble de fonctions permettant de dessiner des formes simples. Pas facile de lui faire comprendre rapidement et facilement le principe de Turtle...

Construisons d'abord notre fichier Python en l'organisant de cette manière  :

  1. La documentation globale du fichier
  2. Les importations
  3. Les déclarations des fonctions privées que l'enfant ne devra pas utiliser directement
  4. Les déclarations des fonctions publiques : les fonctions que l'enfant pourra utiliser pour dessiner.
  5. Les déclaration des fonctions personnelles créées par cet enfant.
  6. Le programme principal

01° Enregistrer ce programme dans Thonny en le nommant prog1.py et en le plaçant dans un répertoire activite_module.

📁 activite_module

📄 prog1.py

Tester le programme. 135 lignes pour deux pauvres formes. Tranquille.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
"""Ce fichier permet de dessiner deux formes à l'aide des deux fonctions suivantes + triangle_equilateral(cote, info_feutre, coordonnees) + arc_de_cercle(rayon, angle, info_feutre, coordonnees) Exemples d'utilisation : >>> informations_feutre = {'écriture':'blue', 'fond':'#FF88FF', 'épaisseur':5} >>> triangle_equilateral(50, informations_feutre, (50,100)) >>> arc_de_cercle(75, 360, informations_feutre, (200,-200)) """ # Importation import turtle as trt import random as rd # Déclaration des fonctions privées : pas d'appel direct de l'utilisateur def nouveau_stylo(ecriture, fond, largeur): """Renvoie la référence d'un stylo configuré :: param ecriture(str) :: la couleur d'écriture ('red', '#FF0000') :: param fond(str) :: la couleur de fond pour ce stylo :: param largeur(int) :: la largeur du trait :: return (Turtle) :: renvoie un objet de la classe Turtle """ feutre = trt.Turtle() feutre.color(ecriture) feutre.fillcolor(fond) feutre.pensize(largeur) feutre.speed(5) return feutre def deplacer(feutre, x, y): """Lève le feutre, déplace le feutre et abaisse le feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param x(int) :: coordonnée horizontale (abscisse) :: param y(int) :: coordonnée verticale (ordonnée) :: return (None) :: c'est une fonction sans retour .. effet de bord :: modifie l'état de feutre """ feutre.penup() # On lève la pointe feutre.goto(x, y) # On déplace le crayon feutre.pendown() # On abaisse la pointe def trace_triangle_equilateral(feutre, cote): """Trace un triangle (equilatéral) à l'aide du crayon feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param cote(int) :: la valeur en pixel des côtés :: return (None) :: fonction sans retour .. effet de bord :: modifie l'état de feutre """ feutre.begin_fill() for x in range(3): feutre.forward(cote) feutre.left(120) feutre.end_fill() feutre.hideturtle() def trace_arc(feutre, rayon, angle): """Trace un arc de cercle à l'aide du crayon feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: l'angle à tracer (360 pour un cercle) :: return (None) :: fonction sans retour .. effet de bord :: modifie l'état de feutre """ feutre.begin_fill() feutre.circle(rayon, angle) feutre.end_fill() feutre.hideturtle() # Déclarations des fonctions publiques : appel direct possible def triangle_equilateral(cote, info_feutre, coordonnees): """Trace un triangle (equilatéral) à partir des info_feutre et aux bonnees coordonnées :: param cote(int) :: la valeur en pixel des côtés :: param info_feutre(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple[int,int]) :: un tuple (x,y) """ ecriture = info_feutre['écriture'] fond = info_feutre['fond'] epaisseur = info_feutre['épaisseur'] x = coordonnees[0] # ou x,y = coordonnees y = coordonnees[1] feutre = nouveau_stylo(ecriture, fond, epaisseur) deplacer(feutre, x, y) trace_triangle_equilateral(feutre, cote) return feutre def arc_de_cercle(rayon, angle, info_feutre, coordonnees): """Trace un arc de triangle à partir des info_feutre et aux bonnees coordonnées :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: la valeur en ° de l'angle :: param info_feutre(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple[int,int]) :: un tuple (x,y) """ ecriture = info_feutre['écriture'] fond = info_feutre['fond'] epaisseur = info_feutre['épaisseur'] x = coordonnees[0] # ou x,y = coordonnees y = coordonnees[1] feutre = nouveau_stylo(ecriture, fond, epaisseur) deplacer(feutre, x, y) trace_arc(feutre, rayon, angle) return feutre # Pas de fonctions personnelles pour le moment # Instructions du programme principal informations_feutre = {'écriture':"blue", 'fond':'#FF88FF', 'épaisseur':5} triangle_equilateral(50, informations_feutre, (50,100)) arc_de_cercle(75, 360, informations_feutre, (200,-200))
(Rappel) 1.1 Décomposition d'un problème : une fonction pour une tâche

On résout un problème complexe à l'aide d'une fonction "complexe" qui fait appel à des fonctions "moyennes" qui elles même font appel à des fonctions "simples".

On donc crée des fonctions relativement courtes à lire.

Considérez qu'à partir de plus de 20 lignes d'instructions, votre fonction devrait certainement être retravaillée en encapsulant certaines lignes dans d'autres fonctions.

Le principe général est donc :

  • De concevoir de petites fonctions simples
  • De créer des fonctions moyennes faisant appel aux fonctions simples
  • De créer des fonctions complexes faisant appel aux fonctions moyennes
  • ...
1.2   Décomposition d'un problème : exemple

Si on analyse le programme précédent, on voit qu'il existe :

Les fonctions "basiques" qui ne vont pas appel à d'autres fonctions du programme

  1. nouveau_stylo() permet juste de créer un nouvel objet Turtle en utilisant le constructeur Turtle() et en modifiant son état à l'aide de différentes méthodes : color(), fillcolor()...
  2. deplacer() permet juste de déplacer le stylo (en levant la pointe avant pour ne pas tracer quelque chose lors du déplacement).
  3. trace_triangle_equilateral() permet juste de tracer un triangle mais a besoin de recevoir une instance de Turtle pour tracer.
  4. trace_arc() permet juste de tracer un arc de cercle mais a besoin de recevoir une instance de Turtle pour tracer.

Les fonctions "plus complexes" qui font appel à d'autres fonctions pour réaliser la tâche qu'on leur demande de faire.

  1. triangle_equilateral() trace un triangle en utilisant les fonctions basiques précédentes. Elle renvoie l'instance du crayon utilisé pour tracer.
  2. arc_de_cercle() trace un arc de cercle en utilisant les fonctions basiques précédentes. Elle renvoie l'instance du crayon utilisé pour tracer.
1.3 Fonction publique ou privée

Pour chacune des fonctions d'un module, on doit se demander si elles sont PRIVEES ou PUBLIQUES.

  • les fonctions dites PUBLIQUES sont celles que l'utilisateur d'un module a le droit d'appeler directement. Ainsi :
    • la fonction randint() est une fonction d'interface du module random et
    • la fonction forward() est une fonction d'interface du module turtle.

    Puisque les paramètres de ces fonctions contiennent des données qui proviennent de l'extérieur, il peut être important de vérifier que leur contenu soit conforme avant de continuer le traitement.

  • les fonctions dites PRIVEES sont celles qui ne doivent pas être appelées directement par un utilisateur du module.
  • Leurs paramètres sont normalement surs puisqu'ils proviennent de l'intérieur du programme ou de données externes qui ont été vérifiées.

graph TD a(utilisateur) --autorisé--> b(fonction publique) b --autorisé--> d(fonction privée) a --NON AUTORISE--> d
1.4 Exemples de fonctions publiques ou privées

Dans notre module de dessin :

  • PUBLIQUES : triangle_equilateral() et arc_de_cercle()
  • PRIVEES : toutes les autres.

02° Au vu de ce qu'on a écrit dans la partie importation de ce programme, que doit-on taper pour faire appel à la fonction randint() présente dans le module random en important les modules de cette manière ?

13 14 15 16 17
# Importation import turtle as trt import random as rd

...CORRECTION...

Sur la ligne 16, on voit qu'on a donné l'alias rd au module.

Il faudra donc taper : rd.randint(?, ?) pour activer cette fonction.

D'ailleurs, avez-vous déjà vu les instructions de cette fonction randint() ? Non. C'est normal, elle est "cachée" à l'intérieur du module random. Nous allons voir aujourd'hui comme cela fonctionne vraiment de façon à pouvoir créer nos propres modules.

2 - Création d'un module

Soyons honnête : 135 lignes pour quelqu'un qui débute, c'est un peu effrayant. Or, notre jeune dessinateur veut simplement utiliser des fonctions qui permettent de dessiner, pas les modifier.

Nous allons donc voir comment cacher une partie des instructions dans un module.

2.1 Qu'est qu'un module en Python ?

Un module Python est simplement un fichier .py auquel on accède indirectement depuis un autre fichier Python en utilisant le mot-clé import.

Imaginons deux fichiers, nommés par exemple programme.py et module_exemple.py, situés dans le même répertoire.

📁 un répertoire

📄 programme.py

📄 module_exemple.py

L'utilisation des fonctions du module est très simple dans le programme. Voici un extrait du code-source de programme.py :

1 2 3
import module_exemple ... module_exemple.fonction_dans_module(...)

Ligne 1 : on importe le module en utilisant simplement le nom sans l'extension .py.

Ligne 3 : on note le nom du module, un point puis le nom de la fonction voulue.

Dans le cadre de notre programme de dessin facile, nous allons donc :

  • cacher dans dessiner.py les déclaration des fonctions de dessin (qui ne devront pas être modifiées par l'enfant).
  • placer l'appel des fonctions de dessin dans prog2.py, qui sera moins impressionnant.

📁 activite_module

📄 prog1.py

📄 prog2.py

📄 dessiner.py

03° Enregistrer le programme suivant dans Thonny en le nommant dessiner.py et en le plaçant dans un répertoire activite_module.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
"""Ce fichier permet de dessiner deux formes à l'aide des deux fonctions suivantes + triangle_equilateral(cote, info_feutre, coordonnees) + arc_de_cercle(rayon, angle, info_feutre, coordonnees) Exemples d'utilisation : >>> informations_feutre = {'écriture':'blue', 'fond':'#FF88FF', 'épaisseur':5} >>> triangle_equilateral(50, informations_feutre, (50,100)) >>> arc_de_cercle(75, 360, informations_feutre, (200,-200)) """ # Importation import turtle as trt import random as rd # Déclaration des fonctions privées def nouveau_stylo(ecriture, fond, largeur): """Renvoie la référence d'un stylo configuré :: param ecriture(str) :: la couleur d'écriture ('red', '#FF0000') :: param fond(str) :: la couleur de fond pour ce stylo :: param largeur(int) :: la largeur du trait :: return (Turtle) :: renvoie un objet de la classe Turtle """ feutre = trt.Turtle() feutre.color(ecriture) feutre.fillcolor(fond) feutre.pensize(largeur) feutre.speed(5) return feutre def deplacer(feutre, x, y): """Lève le feutre, déplace le feutre et abaisse le feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param x(int) :: coordonnée horizontale (abscisse) :: param y(int) :: coordonnée verticale (ordonnée) :: return (None) :: c'est une fonction sans retour .. effet de bord :: modifie l'état de feutre """ feutre.penup() # On lève la pointe feutre.goto(x, y) # On déplace le crayon feutre.pendown() # On abaisse la pointe def trace_triangle_equilateral(feutre, cote): """Trace un triangle (equilatéral) à l'aide du crayon feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param cote(int) :: la valeur en pixel des côtés :: return (None) :: fonction sans retour .. effet de bord :: modifie l'état de feutre """ feutre.begin_fill() for x in range(3): feutre.forward(cote) feutre.left(120) feutre.end_fill() feutre.hideturtle() def trace_arc(feutre, rayon, angle): """Trace un arc de cercle à l'aide du crayon feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: l'angle à tracer (360 pour un cercle) :: return (None) :: fonction sans retour .. effet de bord :: modifie l'état de feutre """ feutre.begin_fill() feutre.circle(rayon, angle) feutre.end_fill() feutre.hideturtle() # Déclarations des fonctions publiques def triangle_equilateral(cote, info_feutre, coordonnees): """Trace un triangle (equilatéral) à partir des info_feutre et aux bonnees coordonnées :: param cote(int) :: la valeur en pixel des côtés :: param info_feutre(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple[int,int]) :: un tuple (x,y) """ ecriture = info_feutre['écriture'] fond = info_feutre['fond'] epaisseur = info_feutre['épaisseur'] x = coordonnees[0] # ou x,y = coordonnees y = coordonnees[1] feutre = nouveau_stylo(ecriture, fond, epaisseur) deplacer(feutre, x, y) trace_triangle_equilateral(feutre, cote) return feutre def arc_de_cercle(rayon, angle, info_feutre, coordonnees): """Trace un arc de cercle à partir des info_feutre et aux bonnees coordonnées :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: la valeur en ° de l'angle :: param info_feutre(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple[int,int]) :: un tuple (x,y) """ ecriture = info_feutre['écriture'] fond = info_feutre['fond'] epaisseur = info_feutre['épaisseur'] x = coordonnees[0] # ou x,y = coordonnees y = coordonnees[1] feutre = nouveau_stylo(ecriture, fond, epaisseur) deplacer(feutre, x, y) trace_arc(feutre, rayon, angle) return feutre # Instructions du programme principal if __name__ == '__main__': informations_feutre = {'écriture':"blue", 'fond':'#FF88FF', 'épaisseur':5} triangle_equilateral(50, informations_feutre, (50,100)) arc_de_cercle(75, 360, informations_feutre, (200,-200))
2.2 if __name__ == '__main__'

Principe

Les fonctions ne s'activent que lorsqu'on les appelle mais les instructions du programme principal se lancent elles automatiquement.

Cela peut être gênant dans le cadre d'un module.

L'instruction conditionnelle if __name__ == '__main__' veut dire que les instructions tabulées sous ce IF ne se lanceront que lorsqu'on active ce fichier directement (en utilisant la flèche verte dans Thonny par exemple).

Si ce fichier est activé via une importation depuis un autre fichier, les instructions sous le IF ne seront pas exécutées, ni même lues par l'interpréteur.

Utilité

On utilise cette instruction conditionnelle pour placer un programme de tests dans le module :

  • ces instructions permettent de tester les fonctions du module pendant leurs réalisations (plutôt que de taper 500 fois les mêmes tests dans la console)
  • puis on les laisse en place car elles montrent, par l'exemple, comment utiliser le module une fois qu'il est finalisé.

04° Lancer directement ce programme dessiner.py pour vérifier qu'il fonctionne : vous devriez visualiser l'apparition d'un triangle et d'un cercle.

05° Enregistrer le petit programme suivant en le nommant prog2.py et en le plaçant dans le même répertoire que le programme précédent  (dans le répertoire activite_module normalement). Cette fois, on veut tracer deux triangles et ils auront une couleur différente du triangle précédent.

1 2 3
informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} triangle_equilateral(50, informations_feutre, (-50,100)) triangle_equilateral(50, informations_feutre, (0,0))

📁 activite_module

📄 prog1.py

📄 prog2.py

📄 dessiner.py

Lancez ! ..échec...

NameError: name 'triangle_equilateral' is not defined

Pourquoi est-ce que cela ne fonctionne pas ? Tout simplement car la fonction triangle_equilateral() ne se trouve pas dans le fichier qu'on vient de lancer : nous avons lancé le script prog2.py mais la fonction voulue se trouve dans dessiner.py.

Comme les deux fichiers sont au même endroit, nous allons pouvoir utiliser des adresses relatives. Nous allons importer le contenu de dessiner.py depuis l'autre script.

(Rappel) 2.3 Adresses absolues, adresses relatives

Adresse absolue

Une adresse absolue est une adresse donnée à partir du point de départ du système.

Au niveau d'un établissement scolaire, cela correspond au descriptif pour rejoindre une salle depuis le hall d'entrée.

Exemple avec une URL pointant vers une page le Web :

www.infoforall.fr/act/python/creation-de-modules/.

Même principe mais avec l'URL d'une page du site www.infoforall.fr pointant vers une de ses propres ressources.

/act/python/creation-de-modules/.

Exemple avec un fichier depuis la racine / d'un ordinateur sous Linux 

/home/alice/Documents/Lycee/DS4.md

~Documents/Lycee/DS4.md (le tilde symbolise le répertoire personnel de l'utilisateur sous Linux)

Exemple avec un fichier depuis la racine du / du disque C d'un ordinateur sous Windows 

C:/Documents/Lycee/DS4.md

Adresse relative

Qu'est-ce qu'une adresse relative ?

Au niveau d'un établissement scolaire, cela correspond au descriptif pour rejoindre une salle depuis la salle où vous êtes actuellement.

Au niveau d'un programme informatique, il s'agit donc d'une adresse qu'on ne fournit pas depuis la racine du système mais depuis le dossier du fichier en cours d'exécution.

Une adresse est relative si elle ne commence pas

  • par un nom de serveur ou
  • par un nom de mémoire de stockage
  • par un slash (on signale qu'on commence à la racine du système actuel)
Adresse composée d'un nom de fichier uniquement

Si on donne uniquemnet un nom d'un fichier dans l'une des instructions d'un programme, c'est que le programme et ce fichier devront être stockés dans le même répertoire exactement.

06° Enregistrer le programme prog3.py en le plaçant dans le répertoire activite_module.

1 2 3 4 5
import dessiner informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} dessiner.triangle_equilateral(50, informations_feutre, (-50,100)) dessiner.triangle_equilateral(50, informations_feutre, (0,0))

📁 activite_module

📄 prog1.py

📄 prog2.py

📄 prog3.py

📄 dessiner.py

Si vous lancez le fichier prog3.py, vous devriez voir que cette fois cela fonctionne et qu'on obtient deux triangles. Il s'agit donc bien des instructions provoquées par notre programme prog3.py à l'aide des fonctions qui sont situées dans le fichier dessiner.py.

07° Pourquoi les deux formes demandées sur les deux dernières lignes lignes de dessiner.py n'ont pas été réalisées ?

...CORRECTION...

A cause du if __name__ == '__main__':.

Lorsqu'on exécute prog3.py, le programme importe les instructions de dessiner.py mais sans qu'il soit le "programme principal" : c'est le programme prog3.py qui a été lancé directement et qui est considéré comme le programme principal (main en anglais).

Nous sommes donc parvenus à "cacher" les fonctions de dessins. On peut encore faire plus discret mais il faudra s'en méfier : nous allons voir une importation permettant de simplement taper le nom des fonctions situées dans le module : arc_de_cercle(...) plutôt que dessiner.arc_de_cercle(...).

08° Enregistrer le court programme suivant dans Thonny en le nommant prog4.py et en le plaçant dans un répertoire activite_module.

1 2 3 4 5
from dessiner import triangle_equilateral, arc_de_cercle informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} arc_de_cercle(50, 90, informations_feutre, (-200,200)) arc_de_cercle(90, 145, informations_feutre, (200,-200))

📁 activite_module

📄 prog1.py

📄 prog2.py

📄 prog3.py

📄 prog4.py

📄 dessiner.py

Si vous lancez l'exécution du fichier prog4.py, vous devriez voir que cette fois deux arcs cercles, qu'on crée juste en donnant le nom de la fonction arc_de_cercle(), sans placer le nom du module devant.

Et voilà : nous avons un tout petit programme qui a l'air de faire en 4 lignes autant que notre programme initial.

graph TD b(prog3.py) --> c(dessiner.py) e(prog4.py) --> c c --> d(turtle.py)

Mais, ce n'est donc qu'une apparence : on importe juste discrètement les lignes qui manquent ce qui a l'avantage de ne pas les montrer. Mais elles sont bien présentes en mémoire au moment où on exécute le programme.

simplicité jusqu'à ce qu'on regarde les codes importés
2.4 Choisir entre from module import x et import module

La différence ?

Il faut savoir que le mot-clé import permet d'accéder directement à ce qu'on place derrière.

Importation d'un module (méthode à privilégier en NSI)

Avec import module, on importe donc le module. On dispose donc de son espace des noms : pour faire appel à l'une de ses fonctions, il faut taper nom_module.nom_fonction.

1 2 3 4 5
import dessiner informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} dessiner.triangle_equilateral(50, informations_feutre, (50,100)) dessiner.arc_de_cercle(75, 360, informations_feutre, (200,-200))

Avantage : Pas de risque de collision entre deux fonctions portant le même nom dans deux fichiers différents.

Désavantage : c'est plus long à taper que la solution directe.

Importation directe d'un élément d'un module (à utiliser avec prudence en NSI)

Avec from module import x, on importe directement x, une fonction du module par exemple. Elle intègre alors l'espace des noms du programme en cours et on fait en faire l'appel directement. Sans mettre le nom du module devant.

1 2
from dessiner import triangle_equilateral, arc_de_cercle triangle_equilateral(50, informations_feutre, (50,100))

Avantage : très facile d'utilisation.

Désavantage : risque de rendre des fonctions invisibles dans l'espace des noms si plusieurs fonctions portent le même nom.

Remarque 1 : Pas trop d'importations directes d'éléments en NSI

Pourquoi ne pas systématiquement le faire ? Tout simplement car si vous faites cela, Python n'aura plus de possibilité de distinguer deux fonctions portant le même nom mais situées dans des fichiers/modules différents. L'une des versions sera écrasée...

Or, en NSI, nous importons assez souvent plusieurs modules à la fois, sans nécessairement connaître tous les noms de fonctions à l'intérieur de ces modules.

Remarque 2 : ne jamais écrire from module import * en NSI

L'étoile * est le caractère magique, celui qui remplace tous les autres.

Avec from module import *, vous importez dans l'espace des noms de votre programme, tous les noms de variables, de fonctions et de classes qui sont dans le fichier visé. Du coup, les chances sont grandes d'écraser des choses qui portent le même nom.

Conclusion, évitez cela en NSI :

1
from math import *

Dans le cadre d'un cours de Math, c'est différent : vous importez uniquement ce module et vous n'allez pas créer plus d'une ou deux fonctions par exemple. C'est donc un moyen pratique de réduire le nombre de caractères à taper.

La problématique est différente en NSI.

C'est un peu le même principe que le print() et le input(). Nous avons de bonnes raisons d'en limiter l'usage à quelques cas précis en NSI. Par contre, vous les utilisez peut-être beaucoup en sciences physiques ou en sciences de l'ingénieur sans vous souciez de la modularité et de l'interfaçage. Ce n'est pas le même cadre.

(Rappel) 2.5 Désempaquetage

C'est une subtilité pratique de certains langages de programmation.

Si vous avez un conteneur (un tableau, un tuple...) qui contient exactement x cases, vous pouvez placer x variables devant le signe de l'affectation et récupérer automatiquement les valeurs dans les bonnes variables.

Est-ce à connaître ? Non.

Est-ce pratique ? Oui mais il faut savoir le refaire à la main au besoin de toutes manières.

Exemple avec les coordonnées :

>>> coordonnees = (100, 200) >>> coordonnees (100, 200) >>> x, y = coordonnees >>> x 100 >>> y 200

On voit que le 100 c'est bien placé automatiquement au niveau de x.

En étant plus explicite, on pourrait rajouter des parenthèses pour indiquer clairement qu'on affecte les valeurs d'un tuple à partir des valeurs d'un autre tuple :

>>> x, y = (100, 200)

Comment faire sans ce désempaquetage (unpaking en anglais) ? Comme cela :

>>> coordonnees = (100, 200) >>> coordonnees (100, 200) >>> x = coordonnees[0] >>> x 100 >>> y = coordonnees[1] 200
2.6 Attention à vos noms de programmes !

Un module est importé en utilisant simplement son nom.

Il faut donc veiller à ne JAMAIS donnez à vos propres programmes des noms correspondant à des modules qui vous connaissez (random, tkinter, turtle...).

Pourquoi ? Simplement car, sinon, vous ne parviendrez plus à atteindre le module : lors d'une importation de turtle, c'est votre propre petit script nommé turtle.py qui va cacher le vrai turtle et ce sont les instructions de votre turtle.pyqui vont être importées...

3 - Documentation des modules

(Rappel) 3.1 Structure d'un programme

Voici la structure attendue d'un programme Python :

  1. Importation des modules nécessaires au programme.
  2. Déclarations des CONSTANTES : par convention, le nom des CONSTANTES est constitué uniquement de majuscules.
  3. Déclaration des variables globales destinées à être lues depuis des fonctions : à utiliser avec modération. Elles sont sources de nombreux disfonctionnements lorsqu'elles sont mal gérées.
  4. Déclaration des fonctions.
  5. Les instructions du programme en lui-même : on nomme cette partie "programme principal" parfois. On y place également les variables globales qu'on envoie simplement en tant que paramètres.

Convention

Habituellement, on sépare les parties ci-dessous par au moins deux lignes vides.

3.2 Documentation d'un module

Un module diffère par deux éléments d'un programme indépendant :

  1. L'instruction conditionnelle if __name__ == '__main__': est maintenant obligatoire dès que vous voulez y placer un petit programme de tests de façon à vérifier que le module fonctionne correctement
  2. On distingue deux catégories de fonctions :
    • Les fonctions publiques : officiellement destinées à être appelées de l'extérieur du module.
    • Les fonctions internes ou privées : elles réalisent des tâches pour permettre aux fonctions précédentes de fonctionner. Mais elles ne sont pas censées être appelées en accès direct.

La structure d'un module :

  1. La documentation du module
  2. Les importations
  3. Déclarations des CONSTANTES : par convention, le nom des CONSTANTES est constitué uniquement de majuscules.
  4. Déclaration des variables globales destinées à être lues depuis des fonctions : à utiliser avec modération. Elles sont sources de nombreux disfonctionnements lorsqu'elles sont mal gérées.
  5. Les déclarations des classes en séparant les publiques des privées
  6. Les déclarations des fonctions en séparant les publiques des privées
  7. Le programme principal
    • avec OBLIGATOIREMENT un bloc lié à une instruction conditionnelle if __name__ == '__main__' pour y placer tests et codes d'exemples.
(Rappel) 3.3 Fonction native help()

Cette fonction permet à l'utilisateur d'obtenir de l'aide via la documentation présente dans le module. Pourvu que quelqu'un ai tapé cette documentation !

Elle ramène dans la console toute la documentation placée en début de module qui doit expliquer le fonctionnement globale et lister par exemple les fonctions d'interface disponibles.

Elle ramène également toute la documentation sur les fonctions du module.

>>> import turtle >>> help(turtle) Help on module turtle: NAME turtle MODULE REFERENCE https://docs.python.org/3.7/library/turtle The following documentation is automatically generated from the Python ...

On peut également chercher uniquement de la documentation sur l'une des fonctions :

>>> import turtle >>> help(turtle.pendown) Help on function pendown in module turtle: pendown() Pull the pen down -- drawing when moving. Aliases: pendown | pd | down No argument. Example: >>> pendown() >>> help(turtle.left) Help on function left in module turtle: left(angle) Turn turtle left by angle units. Aliases: left | lt Argument: angle -- a number (integer or float) Turn turtle left by angle units. (Units are by default degrees, but can be set via the degrees() and radians() functions.) Angle orientation depends on mode. (See this.) Example: >>> heading() 22.0 >>> left(45) >>> heading() 67.0

09 - option° Utiliser la fonction native help() de façon à visualiser dans la console la documentation du module.

Il faudra penser à vérifier au préalable que le module soit bien importé en mémoire et connaître son alias !

>>> import dessiner >>> help(dessiner)

4 - Optionnel : Module dans un package

Les fonctions contenues dans dessiner.py sont bien cachées, mais peut-être pas encore assez : le fichier Python est directement accessible, dans le même répertoire. Le risque est grand que quelqu'un le modifie par accident.

Nous allons maintenant créer un package : il s'agit du nom donné à un répertoire qui va contenir les fichiers Python qu'on veut dissimuler.

4.1 Création d'un package en Python

Un package est donc un conteneur permettant de stocker des modules.

En Python, la création d'un package contenant un ou plusieurs modules est vraiment facile : il suffit

  1. de créer un répertoire en lui donnant un nom clair et explicite. Ce nom sera celui du package.
  2. de créer dans ce répertoire un fichier-texte vide nommé __init__.py
  3. de placer dans ce répertoire les fichiers des modules que vous désirez rendre accessibles via ce package.

📁 nom_package

📄 module.py

📄 __init__.py

10 - option° Créer maintenant la structure suivante en déplacant simplement le fichier dessiner.py dont le répertoire voulu et en créant le fichier-texte vide __init__.py avec Thonny (il suffit de créer un nouveau fichier avec Thonny et de l'enregistrer immédiatement en lui donnant ce nom).

📁 activite_module

📄 prog1.py

📄 prog2.py

📄 prog3.py

📄 prog4.py

📁 activite

📄 dessiner.py

📄 __init__.py

11 - option° Enregistrer un fichier prog5.py avec Thonny dans le répertoire parent du répertoire du module. Vous y placerez tour à tour les 4 versions suivantes du programme. Elles vous permettront notamment de revoir la notion d'alias : un changement de nom lorsque le nom du module ou de la fonction ne vous convienne pas.

1 2 3 4 5
from activite.dessiner import triangle_equilateral, arc_de_cercle informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} triangle_equilateral(50, informations_feutre, (50,100)) arc_de_cercle(75, 360, informations_feutre, (200,-200))

📁 activite_module

📄 prog1.py

📄 prog2.py

📄 prog3.py

📄 prog4.py

📄 prog5.py

📁 activite

📄 dessiner.py

📄 __init__.py

1 2 3 4 5
from activite import dessiner informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} dessiner.triangle_equilateral(50, informations_feutre, (50,100)) dessiner.arc_de_cercle(75, 360, informations_feutre, (200,-200))
1 2 3 4 5
from activite import dessiner as cc informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} cc.triangle_equilateral(50, informations_feutre, (50,100)) cc.arc_de_cercle(75, 360, informations_feutre, (200,-200))
1 2 3 4 5
import activite.dessiner as cc informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} cc.triangle_equilateral(50, informations_feutre, (50,100)) cc.arc_de_cercle(75, 360, informations_feutre, (200,-200))

Cette fois, c'est mieux : les fichiers du module ne sont plus directement accessibles. Il faut aller fouiller un autre répertoire.

Pourquoi donné l'alias cc ici ?

Pour Code Caché.

Vous pouvez bien entendu faire comme vous le voulez.

4.2 Utilisation d'un package en Python

📁 activite_module

📄 prog5.py

📁 activite

📄 dessiner.py

📄 __init__.py

1 2 3 4 5
from activite import dessiner informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} dessiner.triangle_equilateral(50, informations_feutre, (50,100)) dessiner.arc_de_cercle(75, 360, informations_feutre, (200,-200))
1 2 3 4 5
from activite import dessiner as cc informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} cc.triangle_equilateral(50, informations_feutre, (50,100)) cc.arc_de_cercle(75, 360, informations_feutre, (200,-200))
1 2 3 4 5
import activite.dessiner as cc informations_feutre = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} cc.triangle_equilateral(50, informations_feutre, (50,100)) cc.arc_de_cercle(75, 360, informations_feutre, (200,-200))

5 - Complément optionnel : Bibliothèque

5.1 Bibliothèque

Une bibliothèque est le nom qu'on donne à un ensemble de modules, que ceux-ci soient encapsulés dans un package, ou pas.

Bien entendu, dans 99.9% des cas, les modules d'une bibliothèque sont transmis sous forme de package plutôt qu'en tant que fichiers indépendants.

Si on doit résumer :

  • Un module est un fichier python (analogie : un livre)
  • Une bibliothèque, c'est l'ensemble de plusieurs fichiers Python (analogie : plusieurs livres)
  • Un package, c'est le répertoire qui contient les livres (analogie : le meuble, l'armoire qui contient les livres)

En réalité, c'est même plus compliqué car un package peut contenir des modules ou même d'autres packages.

12 - option° Dans la structure fournie, dire de chaque élément si c'est un package, un module ou une bibliothèque.

📁 images

📄 __init__.py

📄 couleurs.py

📄 pixels.py

📁 rgb

📄 __init__.py

📄 gestionrgb.py

📁 encodage

📄 __init__.py

📄 technique1.py

📄 technique2.py

...CORRECTION...

📁 images : package et bibliothèque car contient plusieurs modules

📄 couleurs.py : module

📄 pixels.py : module

📁 rgb : package (ne contient qu'un module)

📄 gestionrgb.py : module

📁 encodage : package et une bibliothèque car contient deux modules

📄 technique1.py : module

📄 technique2.py : module

13 - option° Comment utiliser une fonction encoder() qui se trouve dans le fichier tehnique1.py si on importe comme cela ?

>>> from images import encodage.technique1 as t1

📁 images

📄 __init__.py

📄 couleurs.py

📄 pixels.py

📁 rgb

📄 __init__.py

📄 gestionrgb.py

📁 encodage

📄 __init__.py

📄 technique1.py

📄 technique2.py

...CORRECTION...

>>> from images import encodage.technique1 as t1 >>> t1.encoder()

14 - option° Comment utiliser une fonction encoder() qui se trouve dans le fichier tehnique1.py si on importe comme cela ?

>>> import images as img

📁 images

📄 __init__.py

📄 couleurs.py

📄 pixels.py

📁 rgb

📄 __init__.py

📄 gestionrgb.py

📁 encodage

📄 __init__.py

📄 technique1.py

📄 technique2.py

...CORRECTION...

>>> import images as img >>> img.encodage.technique1.encoder()

C'est un peu lourd non ?

Mais au moins, par de problème de collision de noms...

15 - option° Utiliser la fonction native help() de façon à visualiser dans la console la documentation du module.

Il faudra penser à vérifier au préalable que le module soit bien importé en mémoire et connaître son alias !

Tout dépend donc où se trouve le module (dans un package, juste dans un fichier...)

Voici quelques exemples avec un fichier-module dessiner.py placé dans un répertoire-package activite.

>>> import activite >>> help(activite) >>> help(activite.dessiner)
>>> from activite import dessiner >>> help(dessiner)
>>> from activite import dessiner as cc >>> help(cc)

...CORRECTION...

>>> import activite >>> help(activite) Help on package activite: NAME activite PACKAGE CONTENTS dessiner FILE /home/rv/Documents/exomodule/activite/__init__.py >>> help(activite.dessiner) Help on module activite.dessiner in activite: NAME activite.dessiner - Ce fichier permet de dessiner deux formes à l'aide des deux fonctions suivantes DESCRIPTION + triangle_equilateral(cote, info_feutre, coordonnees) + arc_de_cercle(rayon, angle, info_feutre, coordonnees) Exemples d'utilisation : >>> informations_feutre = {'écriture':'blue', 'fond':'#FF88FF', 'épaisseur':5} >>> triangle_equilateral(50, informations_feutre, (50,100)) >>> arc_de_cercle(75, 360, informations_feutre, (200,-200)) FUNCTIONS arc_de_cercle(rayon, angle, info_feutre, coordonnees) Trace un arc de cercle à partir des info_feutre et aux bonnees coordonnées :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: la valeur en ° de l'angle :: param info_feutre(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple[int,int]) :: un tuple (x,y) deplacer(feutre, x, y) Lève le feutre, déplace le feutre et abaisse le feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param x(int) :: coordonnée horizontale (abscisse) :: param y(int) :: coordonnée verticale (ordonnée) :: return (None) :: c'est une fonction sans retour .. effet de bord :: modifie l'état de feutre nouveau_stylo(ecriture, fond, largeur) Renvoie la référence d'un stylo configuré :: param ecriture(str) :: la couleur d'écriture ('red', '#FF0000') :: param fond(str) :: la couleur de fond pour ce stylo :: param largeur(int) :: la largeur du trait :: return (Turtle) :: renvoie un objet de la classe Turtle trace_arc(feutre, rayon, angle) Trace un arc de cercle à l'aide du crayon feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: l'angle à tracer (360 pour un cercle) :: return (None) :: fonction sans retour .. effet de bord :: modifie l'état de feutre trace_triangle_equilateral(feutre, cote) Trace un triangle (equilatéral) à l'aide du crayon feutre :: param feutre(Turtle) :: la référence de l'objet Turtle :: param cote(int) :: la valeur en pixel des côtés :: return (None) :: fonction sans retour .. effet de bord :: modifie l'état de feutre triangle_equilateral(cote, info_feutre, coordonnees) Trace un triangle (equilatéral) à partir des info_feutre et aux bonnees coordonnées :: param cote(int) :: la valeur en pixel des côtés :: param info_feutre(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple[int,int]) :: un tuple (x,y) FILE /home/rv/Documents/exomodule/activite/dessiner.py

6 - FAQ

Et comment installer un package pour qu'il soit reconnaissable de partout, comme math par exemple ?

Pour cela, il faut que le package soit installé dans un répertoire dans lequel Python cherche lorsqu'on lui demande d'importer des choses.

Pour connaitre ces répertoires, on peut utiliser le module sys qui permet d'interagir avec le système d'exploitation.

>>> import sys >>> sys.path [ '/home/rv/Documents/exomodule', '/home/rv/apps/thonny/lib/python37.zip', '/home/rv/apps/thonny/lib/python3.7', '/home/rv/apps/thonny/lib/python3.7/lib-dynload', '/home/rv/.local/lib/python3.7/site-packages', '/home/rv/apps/thonny/lib/python3.7/site-packages' ]

On voit que notre interpréteur Python lié à Thonny va chercher :

  • d'abord dans le répertoire en cours (c'est pour cela qu'il peut trouver un module placé au même endroit que le fichier du programme !)
  • Puis dans différents répertoires de Thonny lui-même
  • Puis dans un répertoire de la version Python utilisée par Thonny
  • Puis dans un dernier répertoire de Thonny s'il n'a pas trouvé son bonheur avant

Si vous placez votre module dans un de ces répertoires, vous allez pouvoir importer directement vos modules ou vos packages.

Et on peut le faire ?

Oui mais c'est dangereux.

Lorsque l'interpréteur cherche un module, il s'arrête sur le premier fichier portant le bon nom qu'il trouve.

Si vous mettez vos propres modules dans les répertoires de Python, vous risquez potentiellement de donner à votre package ou votre module, le nom d'un module préexistant. Et... l'un des deux ne sera plus trouvable.

C'est ce qui se passe lorsque vous réalisez un programme qui fait un petit truc au hasard et que vous le nommer test.py ou random.py : vous placez un module dans le répertoire courant qui porte le même nom que le module random ou le module test. Du coup, aucun autre programme de ce répertoire ne pourra accéder au vrai module random tant que votre petit fichier python portera le même nom.

Alors faire ça dans le répertoire global des modules, ce n'est pas très malin...

Prochaine activité : le projet Rue.

Activité publiée le 24 11 2020
Dernière modification : 01 09 2022
Auteur : ows. h.