python modules

Identification

Infoforall

34 - Création de modules


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 Turle
  • Le programme de Python de 1er (fonctions, branchements, tableaux, dictionnaires...)

Logiciel nécessaire pour l'activité : Python 3 : Thonny, IDLE ...

Evaluation ✎ :

Memo Python : open document ou pdf

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 d'interface : 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. 130 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
'''Ce fichier permet de dessiner deux formes à l'aide des deux fonctions suivantes + triangle_equilateral(cote, infos, coordonnees) + arc_de_cercle(rayon, angle, infos, coordonnees) Exemples d'utilisation : >>> infos_generales = {'écriture':'blue', 'fond':'#FF88FF', 'épaisseur':5} >>> triangle_equilateral(50, infos_generales, (50,100)) >>> arc_de_cercle(75, 360, infos_generales, (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 ftr(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 ftr(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 d'interface (aucun paramètre n'est lié au module Turtle) def triangle_equilateral(cote, infos, coordonnees): '''Trace un triangle (equilatéral) à partir des infos et aux bonnees coordonnées :: param cote(int) :: la valeur en pixel des côtés :: param infos(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple (int,int) ) :: un tuple (x,y) ''' ecriture = infos['écriture'] fond = infos['fond'] epaisseur = infos['é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, infos, coordonnees): '''Trace un arc de triangle à partir des infos et aux bonnees coordonnées :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: la valeur en ° de l'angle :: param infos(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple (int,int) ) :: un tuple (x,y) ''' ecriture = infos['écriture'] fond = infos['fond'] epaisseur = infos['é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 infos_generales = {'écriture':'blue', 'fond':'#FF88FF', 'épaisseur':5} triangle_equilateral(50, infos_generales, (50,100)) arc_de_cercle(75, 360, infos_generales, (200,-200))
Rappel  Décomposition d'un problème, une fonction pour une tâche

On crée des fonctions relativement courtes à lire : elles ne doivent réaliser qu'une tâche limitée. Considérez qu'à partir de plus de 20 lignes d'instructions, votre fonction devrait certainenement être retravaillée et ses instructions encapsulées dans d'autres fonctions.

On tente donc de résoudre un problème complexe en le décomposant en sous-étapes de résolution, encapsulée chacune dans une fonction.

Comment réaliser des tâches complexes alors ? En créant des fonctions qui font appel aux fonctions basiques précédentes.

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.

On rappelle qu'il faut également séparer clairement :

  1. Les fonctions qui gèrent les données uniquement, sans se préoccuper de l'interface sur laquelle on va les utiliser pour créer un affichage.
  2. Les fonctions qui gèrent l'interaction entre l'utilisateur et l'interface.

02° 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.

Avez-vous d'ailleur 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 : 130 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. Cela va rendre le programme moins impressionant et évitera également à notre débutant d'être tenté de modifier ce qu'il ne devra pas modifier.

Nous allons couper les instructions en le placant dans deux fichiers distincts. Les fonctions que le dessinateur ne devra qu'utiliser se trouveront dans le fichier dessiner.py (qui contiendra le "code Python caché" aux yeux de l'enfant) et le programme de l'utilisateur sera dans le fichier prog2.py.

📁 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, infos, coordonnees) + arc_de_cercle(rayon, angle, infos, coordonnees) Exemples d'utilisation : >>> infos_generales = {'écriture':'blue', 'fond':'#FF88FF', 'épaisseur':5} >>> triangle_equilateral(50, infos_generales, (50,100)) >>> arc_de_cercle(75, 360, infos_generales, (200,-200)) ''' # Importation import turtle as trt import random as rd # Pas de classes # 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 ftr(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 ftr(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 d'interface (aucun paramètre n'est lié au module Turtle) def triangle_equilateral(cote, infos, coordonnees): '''Trace un triangle (equilatéral) à partir des infos et aux bonnees coordonnées :: param cote(int) :: la valeur en pixel des côtés :: param infos(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple (int,int) ) :: un tuple (x,y) ''' ecriture = infos['écriture'] fond = infos['fond'] epaisseur = infos['é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, infos, coordonnees): '''Trace un arc de cercle à partir des infos et aux bonnees coordonnées :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: la valeur en ° de l'angle :: param infos(dict) :: un dictionnaire {"écriture":str, "fond":str, "épaisseur":int} :: param coordonnees(tuple (int,int) ) :: un tuple (x,y) ''' ecriture = infos['écriture'] fond = infos['fond'] epaisseur = infos['é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__': infos_generales = {'écriture':'blue', 'fond':'#FF88FF', 'épaisseur':5} triangle_equilateral(50, infos_generales, (50,100)) arc_de_cercle(75, 360, infos_generales, (200,-200))
Nouveau : if __name__ == '__main__':

Cette instruction conditionnelle veut dire qu'on ne va appliquer les instructions des lignes 130, 131 et 132 que si on lance ce fichier directement, c'est à dire en lancant le fichier-programme lui-même depuis Thonny et pas en l'important depuis un autre fichier-programme.

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. Cette fois, on veut tracer deux triangles et ils auront une couleur différente du triangle précédent.

1 2 3
infos_generales = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} triangle_equilateral(50, infos_generales, (-50,100)) triangle_equilateral(50, infos_generales, (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 : 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 racinedu / 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 progarmme 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 slash ou
  • la dénomination claire d'un point de départ (un nom de serveur, C: sous windows...)

Ainsi, si on donne juste un nom d'un fichier dans l'une des instructions d'un programme, c'est que le programme et ce fichier sont stockés au même endroit, dans le même répertoire.

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

1 2 3 4 5
import dessiner infos_generales = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} dessiner.triangle_equilateral(50, infos_generales, (-50,100)) dessiner.triangle_equilateral(50, infos_generales, (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 instructiosn provoqués par notre programme prog3.py à l'aide des fonctions qui sont situées dans l'autre 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 infos_generales = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} arc_de_cercle(50, 90, infos_generales, (-200,200)) arc_de_cercle(90, 145, infos_generales, (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. Mais, ce n'est 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
Choisir entre
  from module import des_fonctions et
  import module

La différence ?

Il faut savoir que 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 infos_generales = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} dessiner.triangle_equilateral(50, infos_generales, (50,100)) dessiner.arc_de_cercle(75, 360, infos_generales, (200,-200))

Avantage : Pas de risque de collusion 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, infos_generales, (50,100))

Avantage : très facile d'utilisation.

Désavantage : risque de collusion si plusieurs fonctions portent le mmê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.

Remarque 3 : Désempaquetage

C'est juste 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ésenpaquetage (unpaking en anglais) ? Comme cela :

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

Un module est un fichier contenant des fonctions qu'on pourra importer comme on le veut depuis un autre programme.

Pour importer les fonctions, les constantes et autres documentations contenues dans le module, on utilise le mot-clé import en Python.

Il faut donc veiller à ce point essentiel lorsque vous réalisez vos programmes Python : ne donnez JAMAIS à vos programmes des noms correspondant à des modules qui vous connaissez (random, tkinter, turtle...) sinon vous ne parviendrez plus à atteindre le module : lors d'une importation de turtle, c'est votre propre petit script nommmé turtle.py qui va être importé.

3 - 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.

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 Pyhon des modules que vous désirez rendre accessibles via ce package.

09° 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

10° 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 infos_generales = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} triangle_equilateral(50, infos_generales, (50,100)) arc_de_cercle(75, 360, infos_generales, (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 infos_generales = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} dessiner.triangle_equilateral(50, infos_generales, (50,100)) dessiner.arc_de_cercle(75, 360, infos_generales, (200,-200))
1 2 3 4 5
from activite import dessiner as cc infos_generales = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} cc.triangle_equilateral(50, infos_generales, (50,100)) cc.arc_de_cercle(75, 360, infos_generales, (200,-200))
1 2 3 4 5
import activite.dessiner as cc infos_generales = {'écriture':'yellow', 'fond':'#88FF88', 'épaisseur':5} cc.triangle_equilateral(50, infos_generales, (50,100)) cc.arc_de_cercle(75, 360, infos_generales, (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 - Documentation des modules

Structure d'un programme indépendant

Voici la structure d'un programme tel qu'on les réalise pour le moment :

  1. La documentation globale du fichier
  2. Les importations
  3. Les déclarations de CONSTANTES et autres variables globales éventuelles
  4. Les déclarations des classes (que nous verrons plus tard dans l'année) puis des fonctions et on distingue :
    1. les fonctions gestion de données ne gérant que les données (pas de modif si on change d'interface graphique)
    2. les fonctions liées à l'interface utilisateur (des modifs si on change d'interface graphique)
  5. Le programme principal.
Structure d'un module

La structure d'un module diffère par deux éléments de la documentation 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 moduile fonctionne correctement
  2. Nous distinguerons deux nouvelles catégories de fonctions :
    • Les fonctions d'interface-programmation : officiellement destinées à être appelées de l'extérieur du module. On peux également les nommées fonctions publiques.
    • Les fonctions internes : 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. C'est pour cela qu'on les nommera ici les fonctions internes ou encore fonctions privées.

La structure d'un module :

  1. La documentation du module
  2. Les importations
  3. Les déclarations de CONSTANTES et autres variables globales éventuelles
  4. Les déclarations des classes et fonctions publiques dont
    • les fonctions gestion de données ne gérant que les données
    • les fonctions liées à l'interface utilisateur
  5. Les déclarations des classes et fonctions privées utilisables depuis l'exterieur dont
    • les fonctions gestion de données ne gérant que les données
    • les fonctions liées à l'interface utilisateur
  6. Le programme principal,

    • avec OBLIGATOIREMENT un bloc lié à une instruction conditionnelle if __name__ == '__main__' pour y placer les tests et autres programmes de tests du module.

Lors de la conception du module, on peut donc placer les instructions déclanchant les tests automatiques dans le bloc if __name__ == '__main__' : lorsqu'on lance directement le fichier du module, les tests sont faits. Par contre, lorsqu'un utilisateur va l'utiliser, les tests ne seront pas faits.

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

11° 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, infos, coordonnees) + arc_de_cercle(rayon, angle, infos, coordonnees) Exemples d'utilisation : >>> infos_generales = {'écriture':'blue', 'fond':'#FF88FF', 'épaisseur':5} >>> triangle_equilateral(50, infos_generales, (50,100)) >>> arc_de_cercle(75, 360, infos_generales, (200,-200)) FUNCTIONS arc_de_cercle(rayon, angle, infos, coordonnees) Trace un arc de cercle à partir des infos et aux bonnees coordonnées :: param rayon(int) :: la valeur en pixel du rayon :: param angle(int) :: la valeur en ° de l'angle :: param infos(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 ftr(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 ftr(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, infos, coordonnees) Trace un triangle (equilatéral) à partir des infos et aux bonnees coordonnées :: param cote(int) :: la valeur en pixel des côtés :: param infos(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

5 - Complément optionnel : Bibliothèque

Bibliothèque

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

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 d'autres packages.

Le mot bibliothèque est utilisable dans deux sens :

  1. Ensemble de plusieurs modules
  2. Un package qui contient plusieurs modules ou même d'autres packages

12° 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° 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° 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 collusion de noms...

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.