Python w5
Python w5
https ://www.fun-mooc.fr
2
Page
i
Chapitre 5
5.1 w5-s1-c1-break-et-continue
— continue : pour abandonner l’itération courante, et passer à la suivante, en restant dans la boucle ;
— break : pour abandonner complètement la boucle.
on traite l'entier 0
on traite l'entier 10
on traite l'entier 20
on traite l'entier 30
on traite l'entier 40
on traite l'entier 50
on est sorti de la boucle
1
MOOC Python 3 Semaine 5, Séquence 1
5.2 w5-s1-c2-ne-pas-modifier-sujet-boucle-for
Ainsi par exemple il ne faut pas faire quelque chose comme ceci :
# NOTE
# auto-exec-for-latex has skipped execution of this cell
[2]: {'albert'}
C’est sans doute la meilleure solution. Par contre, évidemment, on n’a pas modifié l’objet ensemble
initial, on a créé un nouvel objet. En supposant que l’on veuille modifier l’objet initial, il nous faut faire
la boucle sur une shallow copy de cet objet. Notez qu’ici, il ne s’agit d’économiser de la mémoire, puisque
l’on fait une shallow copy.
print(ensemble)
{'albert'}
Avertissement
Dans l’exemple ci-dessus, on voit que l’interpréteur se rend compte que l’on est en train de modifier
l’objet de la boucle, et nous le signifie.
w5-s1-c2-ne-pas-modifier-sujet-boucle-for 2
MOOC Python 3 Semaine 5, Séquence 1
Ne vous fiez pas forcément à cet exemple, il existe des cas – nous en verrons plus loin dans ce document –
où l’interpréteur peut accepter votre code alors qu’il n’obéit pas à cette règle, et du coup essentiellement
se mettre à faire n’importe quoi.
On ne doit pas modifier la composition de l’objet en tant qu’itérable, mais on peut sans souci modifier
chacun des objets qui constitue l’itération.
Dans cet exemple, les modifications ont lieu sur les éléments de liste, et non sur l’objet liste lui-même,
c’est donc tout à fait légal.
Considérons par exemple la situation d’une liste qui a 10 éléments, sur laquelle on ferait une boucle et
que, par exemple au 5ème élément, on enlève le 8ème élément. Quel serait le comportement attendu dans
ce cas ? Faut-il ou non que la boucle envisage alors le 8-ème élément ?
La situation serait encore pire pour les dictionnaires et ensembles pour lesquels l’ordre de parcours n’est
pas spécifié ; ainsi on pourrait écrire du code totalement indéterministe si le parcours d’un ensemble
essayait :
On le voit, il n’est déjà pas très simple d’expliciter sans ambiguïté le comportement attendu d’une boucle
for qui serait autorisée à modifier son propre sujet.
Difficulté d’implémentation
Voyons maintenant un exemple de code qui ne respecte pas la règle, et qui modifie le sujet de la boucle
en lui ajoutant des valeurs
w5-s1-c2-ne-pas-modifier-sujet-boucle-for 3
MOOC Python 3 Semaine 5, Séquence 1
Nous avons volontairement mis ce code dans une cellule de texte et non de code : vous ne pouvez pas
l’exécuter dans le notebook. Si vous essayez de l’exécuter sur votre ordinateur vous constaterez que la
boucle ne termine pas : en fait à chaque itération on ajoute un nouvel élément dans la liste, et du coup
la boucle a un élément de plus à balayer ; ce programme ne termine théoriquement jamais. En pratique,
ce sera le cas quand votre système n’aura plus de mémoire disponible (sauvegardez vos documents avant
d’essayer !).
5.3 w5-s1-c3-itertools
Itérateurs
Le module itertools
À ce stade, j’espère que vous savez trouver la documentation du module que je vous invite à avoir sous
la main.
Comme vous le voyez dans la doc, les fonctionnalités de itertools tombent dans 3 catégories :
— chain qui permet de concaténer plusieurs itérables sous la forme d’un itérateur :
1
2
3
4
— islice qui fournit un itérateur sur un slice d’un itérable. On peut le voir comme une généralisation
de range qui parcourt n’importe quel itérable.
w5-s1-c3-itertools 4
MOOC Python 3 Semaine 5, Séquence 2
support=abcdefghijklmnopqrstuvwxyz
[4]: # range
for x in range(3, 8):
print(x)
3
4
5
6
7
[5]: # islice
for x in itertools.islice(support, 3, 8):
print(x)
d
e
f
g
h
5.4 w5-s2-c1-fonctions
Programmation fonctionnelle
On peut calculer la liste des résultats d’une fonction sur une liste (plus généralement un itérable) d’entrées
par :
w5-s2-c1-fonctions 5
MOOC Python 3 Semaine 5, Séquence 2
reduce
La fonction reduce permet d’appliquer une opération associative à une liste d’entrées. Pour faire simple,
étant donné un opérateur binaire ⊗ on veut pouvoir calculer
x1 ⊗ x2 ... ⊗ xn
De manière un peu moins abstraite, on suppose qu’on dispose d’une fonction binaire f qui implémente
l’opérateur ⊗, et alors
En fait reduce accepte un troisième argument - qu’il faut comprendre comme l’élément neutre de l’opé-
rateur/fonction en question - et qui est retourné lorsque la liste en entrée est vide.
Par exemple voici - encore - une autre implémentation possible de la fonction factoriel.
On utilise ici le module operator, qui fournit sous forme de fonctions la plupart des opérateurs du
langage, et notamment, dans notre cas, operator.mul ; cette fonction retourne tout simplement le produit
de ses deux arguments.
[1]: # la fonction reduce dans Python 3 n'est plus une built-in comme en Python 2
# elle fait partie du module functools
from functools import reduce
def factoriel(n):
return reduce(mul, range(1, n+1), 1)
0 -> 1
1 -> 1
2 -> 2
3 -> 6
4 -> 24
print('sum', sum(entrees))
print('min', min(entrees))
print('max', max(entrees))
sum 81
min 4
max 45
w5-s2-c2-tris-de-listes-2 6
MOOC Python 3 Semaine 5, Séquence 2
5.5 w5-s2-c2-tris-de-listes-2
Tri de listes
Cas général
Dans le cas général, on est souvent amené à trier des objets selon un critère propre à l’application.
Imaginons par exemple que l’on dispose d’une liste de tuples à deux éléments, dont le premier est la
latitude et le second la longitude :
Il est possible d’utiliser la méthode sort pour faire cela, mais il va falloir l’aider un peu plus, et lui
expliquer comment comparer deux éléments de la liste.
coordonnees.sort(key=longitude)
print("coordonnées triées par longitude", coordonnees)
coordonnées triées par longitude [(46, -7), (46, 0), (43, 7)]
Comme on le devine, le procédé ici consiste à indiquer à sort comment calculer, à partir de chaque
élément, une valeur numérique qui sert de base au tri.
Pour cela on passe à la méthode sort un argument key qui désigne une fonction, qui lorsqu’elle est
appliquée à un élément de la liste, retourne la valeur qui doit servir de base au tri : dans notre exemple,
la fonction longitude, qui renvoie le second élément du tuple.
On aurait pu utiliser de manière équivalente une fonction lambda ou la méthode itemgetter du module
operator
# méthode operator.getitem
import operator
coordonnees = [(43, 7), (46, -7), (46, 0)]
coordonnees.sort(key=operator.itemgetter(1))
print("coordonnées triées par longitude", coordonnees)
coordonnées triées par longitude [(46, -7), (46, 0), (43, 7)]
coordonnées triées par longitude [(46, -7), (46, 0), (43, 7)]
w5-s2-c2-tris-de-listes-2 7
MOOC Python 3 Semaine 5, Séquence 2
Nous avons qualifié sorted de fonction de commodité car il est très facile de s’en passer ; en effet on
aurait pu écrire à la place du fragment précédent :
Alors que sort est une fonction sur les listes, sorted peut trier n’importe quel itérable et retourne le
résultat dans une liste. Cependant, au final, le coût mémoire est le même. Pour utiliser sort on va créer
une liste des éléments de l’itérable, puis on fait un tri en place avec sort. Avec sorted on applique
directement le tri sur l’itérable, mais on crée une liste pour stocker le résultat. Dans les deux cas, on a
une liste à la fin et aucune structure de données temporaire créée.
w5-s2-x1-multi-tri 8
MOOC Python 3 Semaine 5, Séquence 2
# NOTE
# auto-exec-for-latex has skipped execution of this cell
Modifiez votre code pour qu’il accepte cette fois deux arguments listes que l’on suppose de tailles égales.
Comme tout à l’heure le premier argument est une liste de listes à trier.
À présent le second argument est une liste (ou un tuple) de booléens, de même cardinal que le premier
argument, et qui indiquent l’ordre dans lequel on veut trier la liste d’entrée de même rang. True signifie
un tri descendant, False un tri ascendant.
Comme dans l’exercice multi_tri, il s’agit de modifier en place les données en entrée, et de retourner
la liste de départ.
À vous de jouer :
# NOTE
# auto-exec-for-latex has skipped execution of this cell
— ont tous les clés 'n' et 'p' (pensez nom et prénom, je choisis des noms courts pour que la présen-
tation des données soit plus compacte)
w5-s2-x1-multi-tri 9
MOOC Python 3 Semaine 5, Séquence 2
Indice on peut bien sûr utiliser list.sort() pour faire ce travail en quelques lignes ; voyez notamment
le paramètre key.
# NOTE
# auto-exec-for-latex has skipped execution of this cell
— une fonction f, dont vous savez seulement que le premier argument est numérique, et qu’elle ne
prend que des arguments positionnels (sans valeur par défaut) ;
— un nombre quelconque - mais au moins 1 - d’arguments positionnels args, dont on sait qu’ils
pourraient être passés à f.
Et on attend en retour le résultat de f appliqués à tous ces arguments, mais avec le premier d’entre eux
multiplié par deux.
Voici d’abord quelques exemples de ce qui est attendu. Pour cela on va utiliser comme fonctions :
w5-s2-x2-doubler-premier 10
MOOC Python 3 Semaine 5, Séquence 2
[1]: 5.0
[2]: 8.0
[ ]: exo_doubler_premier.correction(doubler_premier)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
La fonction doubler_premier_kwds que l’on vous demande d’écrire maintenant prend donc un premier
argument f qui est une fonction, un second argument positionnel qui est le premier argument de f (et
donc qu’il faut doubler), et le reste des arguments de f, qui donc, à nouveau, peuvent être nommés ou
non.
Vous remarquerez que l’on n’a pas mentionné dans cette liste d’exemples
w5-s2-x2-doubler-premier 11
MOOC Python 3 Semaine 5, Séquence 2
que l’on ne demande pas de supporter puisqu’il est bien précisé que doubler_premier a deux arguments
positionnels.
[ ]: exo_doubler_premier_kwds.correction(doubler_premier_kwds)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
5.6 w5-s2-x3-compare
Comparaison de fonctions
exo_compare_all.correction(compare_all)
dans lequel compare_all est l’objet fonction que vous écrivez en réponse à cet exercice.
— deux fonctions f et g ; imaginez que l’une d’entre elles fonctionne et qu’on cherche à valider l’autre ;
dans cette version simplifiée toutes les fonctions acceptent exactement un argument ;
— une liste d’entrées entrees ; vous pouvez supposer que chacune de ces entrées est dans le domaine
de f et de g (dit autrement, on peut appeler f et g sur chacune des entrées sans craindre qu’une
exception soit levée).
Le résultat attendu pour le retour de compare est une liste qui contient autant de booléens que d’éléments
dans entrees, chacun indiquant si avec l’entrée correspondante on a pu vérifier que f(entree) ==
g(entree).
Dans cette première version de l’exercice vous pouvez enfin supposer que les entrées ne sont pas modifiées
par f ou g.
w5-s2-x3-compare 12
MOOC Python 3 Semaine 5, Séquence 2
Ce qui, dit autrement, veut tout simplement dire que fact et factorial coïncident sur les entrées 0, 1
et 5, alors que broken_fact et factorial ne renvoient pas la même valeur avec l’entrée 0.
# NOTE
# auto-exec-for-latex has skipped execution of this cell
Comme ci-dessus on attend en retour une liste retour de booléens, de même taille que argument_tuples,
telle que, si len(argument_tuples) vaut n :
[ ]: exo_compare_args.correction(compare_args)
# NOTE
w5-s2-x3-compare 13
MOOC Python 3 Semaine 5, Séquence 3
5.7 w5-s3-c1-comprehensions
Pour l’introduire en deux mots, disons que la compréhension de liste est à l’instruction for ce que
l’expression conditionnelle est à l’instruction if, c’est-à-dire qu’il s’agit d’une expression à part entière.
Le résultat de cette expression est donc une liste, dont les éléments sont les résultats de l’expression x**2
pour x prenant toutes les valeurs de depart.
Remarque : si on prend un point de vue un peu plus mathématique, ceci revient donc à appliquer une
certaine fonction (ici x → x2 ) à une collection de valeurs, et à retourner la liste des résultats. Dans les
langages fonctionnels, cette opération est connue sous le nom de map, comme on l’a vu dans la séquence
précédente.
Digression
[2]: # profitons de cette occasion pour voir
# comment tracer une courbe avec matplotlib
%matplotlib inline
import matplotlib.pyplot as plt
plt.ion()
w5-s3-c1-comprehensions 14
MOOC Python 3 Semaine 5, Séquence 3
qui cette fois ne contient que les carrés des éléments pairs de depart.
Remarque : pour prolonger la remarque précédente, cette opération s’appelle fréquemment filter dans
les langages de programmation.
Autres types
On peut fabriquer une compréhension à partir de tout objet itérable, pas forcément une liste, mais le
résultat est toujours une liste, comme on le voit sur ces quelques exemples :
w5-s3-c1-comprehensions 15
MOOC Python 3 Semaine 5, Séquence 3
[8]: {9604}
5.8 w5-s3-c2-comprehensions-imbriquees
Compréhensions imbriquées
Bien sûr on peut aussi restreindre ces compréhensions, comme par exemple :
[2]: [n + p for n in [2, 4] for p in [10, 20, 30] if n*p >= 40]
Observez surtout que le résultat ci-dessus est une liste simple (de profondeur 1), à comparer avec :
qui est de profondeur 2, et où les résultats atomiques apparaissent dans un ordre différent.
Un moyen mnémotechnique pour se souvenir dans quel ordre les compréhensions imbriquées produisent
leur résultat, est de penser à la version “naïve” du code qui produirait le même résultat ; dans ce code
les clause for et if apparaissent dans le même ordre que dans la compréhension :
w5-s3-c2-comprehensions-imbriquees 16
MOOC Python 3 Semaine 5, Séquence 3
[5]: n = 4
Et dans ce cas, très logiquement, l’évaluation se fait en commençant par la fin, ou si on préfère “par
l’extérieur”, c’est-à-dire que le code ci-dessus est équivalent à :
Avec if
Lorsqu’on assortit les compréhensions imbriquées de cette manière de clauses if, l’ordre d’évaluation est
tout aussi logique. Par exemple, si on voulait se limiter - arbitrairement - aux lignes correspondant à j
pair, et aux diagonales où i+j est pair, on écrirait :
w5-s3-c2-comprehensions-imbriquees 17
MOOC Python 3 Semaine 5, Séquence 3
Le point important ici est que l’ordre dans lequel il faut lire le code est naturel, et dicté par l’imbrication
des [ .. ].
[10]: i, j
[10]: (4, 4)
C’est pourquoi, afin de comparer les deux formes de compréhension imbriquées nous allons explicitement
retirer les variables i et j de l’environnement
[11]: del i, j
Avertissement méfiez-vous toutefois, car il est facile de ne pas voir du premier coup d’oeil qu’ici on évalue
les deux clauses for dans un ordre différent.
Pour mieux le voir, essayons de reprendre la logique de notre tout premier exemple, mais avec une forme
de double compréhension à plat :
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w5-s3-c2-comprehensions-imbriquees 18
MOOC Python 3 Semaine 5, Séquence 3
On obtient une erreur, l’interpréteur se plaint à propos de la variable j (c’est pourquoi nous l’avons
effacée de l’environnement au préalable).
Ce qui se passe ici, c’est que, comme nous l’avons déjà mentionné en semaine 3, le code que nous avons
écrit est en fait équivalent à :
# NOTE
# auto-exec-for-latex has skipped execution of this cell
Et dans cette version * dépliée* on voit bien qu’en effet on utilise j avant qu’elle ne soit définie.
Conclusion
La possibilité d’imbriquer des compréhensions avec plusieurs niveaux de for dans la même compréhension
est un trait qui peut rendre service, car c’est une manière de simplifier la structure des entrées (on passe
essentiellement d’une liste de profondeur 2 à une liste de profondeur 1).
Mais il faut savoir ne pas en abuser, et rester conscient de la confusion qui peut en résulter, et en
particulier être prudent et prendre le temps de bien se relire. N’oublions pas non plus ces deux phrases
du Zen de Python : “Flat is better than nested” et surtout “Readability counts”.
5.9 w5-s3-x1-comprehensions
Compréhensions
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w5-s3-x1-comprehensions 19
MOOC Python 3 Semaine 5, Séquence 3
[3]: # exemple
from corrections.exo_alternat import exo_alternat
exo_alternat.example()
Indice pour cet exercice il peut être pertinent de recourir à la fonction built-in zip.
# NOTE
# auto-exec-for-latex has skipped execution of this cell
(entier, valeur)
On vous demande d’écrire une fonction intersect qui retourne l’ensemble des objets valeur associés
(dans A ou dans B) à un entier qui soit présent dans (un tuple de) A et dans (un tuple de) B.
[5]: # un exemple
from corrections.exo_intersect import exo_intersect
exo_intersect.example()
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w5-s3-x2-vigenere 20
MOOC Python 3 Semaine 5, Séquence 3
5.10 w5-s3-x2-vigenere
Le code de Vigenère
Les deux méthodes fonctionnent par simple décalage dans l’alphabet modulo 26.
Je précise tout de suite que les conventions que nous avons adoptées dans cet exercice sont légèrement
différentes de celles décrites dans les deux pages wikipédia citées ci-dessus.
Dans le chiffre de César, on se donne une clé constituée d’un seul caractère, disons par exemple C (la
3-ième lettre de l’alphabet), et avec cette clé on chiffre l’alphabet par un décalage de 3, ce qui donne :
clé : C
clair : ABCDEFGHIJKLMNOPQRSTUVWXYZ
chiffré : DEFGHIJKLMNOPQRSTUVWXYZABC
clé : L
clair : ABCDEFGHIJKLMNOPQRSTUVWXYZ
chiffré : MNOPQRSTUVWXYZABCDEFGHIJKL
clé : E
clair : ABCDEFGHIJKLMNOPQRSTUVWXYZ
chiffré : FGHIJKLMNOPQRSTUVWXYZABCDE
La méthode de Vigenère consiste à choisir cette fois pour clé un mot, qui est utilisé de manière répétitive.
Ainsi par exemple si je choisis la clé CLE, on va chiffrer un message en appliquant la méthode de César
Le but de cet exercice est d’écrire une fonction qui implémente la méthode de Vigenère pour, à partir
d’une clé connue, coder ou décoder des messages.
Je rappelle par ailleurs l’existence en Python de deux fonctions qui peuvent être très utiles dans ce
contexte :
w5-s3-x2-vigenere 21
MOOC Python 3 Semaine 5, Séquence 3
ord('a')
[1]: 97
[2]: 'a'
Une fois qu’on a dit ça, il est intéressant de constater que les caractères minuscules et majuscules auxquels
nous nous intéressons sont, fort heureusement, contigus dans l’espace des codepoints.
[3]: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
[4]: string.ascii_lowercase
[4]: 'abcdefghijklmnopqrstuvwxyz'
[5]: COLUMNS = 7
A→ 65 B→ 66 C→ 67 D→ 68 E→ 69 F→ 70 G→ 71
H→ 72 I→ 73 J→ 74 K→ 75 L→ 76 M→ 77 N→ 78
O→ 79 P→ 80 Q→ 81 R→ 82 S→ 83 T→ 84 U→ 85
V→ 86 W→ 87 X→ 88 Y→ 89 Z→ 90
Forts de ces observations, vous devez pouvoir à présent écrire une première fonction qui implémente le
décalage de César.
Comme par ailleurs les opérations d’encodage et de décodage sont symétriques l’une de l’autre, on choisit
pour éviter d’avoir à dupliquer du code, d’écrire une fonction dont la signature est :
w5-s3-x2-vigenere 22
MOOC Python 3 Semaine 5, Séquence 3
# NOTE
# auto-exec-for-latex has skipped execution of this cell
Donc pour calculer le chiffrement de ce message avec la clé cle, on va se souvenir que
clé : C
clair : ABCDEFGHIJKLMNOPQRSTUVWXYZ
chiffré : DEFGHIJKLMNOPQRSTUVWXYZABC
clé : L
clair : ABCDEFGHIJKLMNOPQRSTUVWXYZ
chiffré : MNOPQRSTUVWXYZABCDEFGHIJKL
clé : E
clair : ABCDEFGHIJKLMNOPQRSTUVWXYZ
chiffré : FGHIJKLMNOPQRSTUVWXYZABCDE
et du coup faire
w5-s3-x2-vigenere 23
MOOC Python 3 Semaine 5, Séquence 4
indices
— Bien entendu vous êtes invités à utiliser la fonction cesar pour implémenter vigenere.
— Par ailleurs, pour cet exercice je vous recommande d’aller voir ou revoir le module itertools qui
contient des outils qui sont exactement adaptés à ce traitement.
C’est-à-dire, pour être encore plus explicite, qu’il est possible d’écrire cette fonction sans recourir
à aucun indice entier sur le texte ni sur la clé.
[ ]: # et pour corriger
exo_vigenere.correction(vigenere)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
5.11 w5-s4-c1-expressions-generatrices
Expressions génératrices
Vous savez maintenant qu’en Python on favorise la notion d’itérateurs puisqu’ils se manipulent comme des
objets itérables et qu’ils sont en général beaucoup plus compacts en mémoire que l’itérable correspondant.
Comme les compréhensions de listes sont fréquemment utilisées en Python, mais qu’elles sont des itérables
potentiellement gourmands en ressources mémoire, on souhaiterait pouvoir créer un itérateur directement
à partir d’une compréhension de liste. C’est possible et très facile en Python. Il suffit de remplacer les
crochets par des parenthèses, regardons cela.
w5-s4-c1-expressions-generatrices 24
MOOC Python 3 Semaine 5, Séquence 4
print(generator)
Ensuite pour utiliser une expression génératrice, c’est très simple, on l’utilise comme n’importe quel
itérateur.
[3]: True
Avec une expression génératrice on n’est plus limité comme avec les compréhensions par le nombre
d’éléments :
Ou plutôt, c’est comme si elle retournait les premiers entiers lorsqu’on fait une boucle for
w5-s4-c1-expressions-generatrices 25
MOOC Python 3 Semaine 5, Séquence 4
0
1
2
3
mais en réalité le résultat de range exhibe un comportement un peu étrange, en ce sens que :
[7]: # mais en fait la fonction range ne renvoie PAS une liste (depuis Python 3)
range(4)
[7]: range(0, 4)
[8]: False
La raison de fond pour ceci, c’est que le fait de construire une liste est une opération relativement
coûteuse - toutes proportions gardées - car il est nécessaire d’allouer de la mémoire pour stocker tous
les éléments de la liste à un instant donné ; alors qu’en fait dans l’immense majorité des cas, on n’a pas
réellement besoin de cette place mémoire, tout ce dont on a besoin c’est d’itérer sur un certain nombre
de valeurs mais qui peuvent être calculées au fur et à mesure que l’on parcourt la liste.
Compréhension et expression génératrice À la lumière de ce qui vient d’être dit, on peut voir qu’une
compréhension n’est pas toujours le bon choix, car par définition elle construit une liste de résultats - de
la fonction appliquée successivement aux entrées.
Or dans les cas où, comme pour range, on n’a pas réellement besoin de cette liste en tant que telle mais
seulement de cet artefact pour pouvoir itérer sur la liste des résultats, il est préférable d’utiliser une
expression génératrice.
Comme pour range, le résultat de l’expression génératrice ne se laisse pas regarder avec print, mais
comme pour range, on peut itérer sur le résultat :
w5-s4-c1-expressions-generatrices 26
MOOC Python 3 Semaine 5, Séquence 4
Il n’est pas toujours possible de remplacer une compréhension par une expression génératrice, mais
c’est souvent souhaitable, car de cette façon on peut faire de substantielles économies en matière de
performances. On peut le faire dès lors que l’on a seulement besoin d’itérer sur les résultats.
Il faut juste un peu se méfier, car comme on parle ici d’itérateurs, comme toujours si on essaie de faire
plusieurs fois une boucle sur le même itérateur, il ne se passe plus rien, car l’itérateur a été épuisé :
5.12 w5-s4-c2-yield-from
[2]: divs(28)
2
4
7
14
w5-s4-c2-yield-from 27
MOOC Python 3 Semaine 5, Séquence 4
Première idée
Première idée naïve pour faire cela, mais qui ne marche pas :
[5]: try:
for i in divdivs(28):
print(i)
except Exception as e:
print(f"OOPS {e}")
Ce qui se passe ici, c’est que divdivs est perçue comme une fonction normale, lorsqu’on l’appelle elle ne
retourne rien, donc None ; et c’est sur ce None qu’on essaie de faire la boucle for (à l’interieur du try),
qui donc échoue.
Deuxième idée
Si on utilise juste yield, ça ne fait pas du tout ce qu’on veut :
[7]: try:
for i in divdivs(28):
print(i)
except Exception as e:
print(f"OOPS {e}")
En effet, c’est logique, chaque yield dans divdivs() correspond à une itération de la boucle. Bref, il
nous manque quelque chose dans le langage pour arriver à faire ce qu’on veut.
yield from
La construction du langage qui permet de faire ceci s’appelle yield from ;
[9]: try:
for i in divdivs(28):
w5-s4-c2-yield-from 28
MOOC Python 3 Semaine 5, Séquence 4
print(i)
except Exception as e:
print(f"OOPS {e}")
trouvé diviseur 2 de 4
2
trouvé diviseur 2 de 14
2
trouvé diviseur 7 de 14
7
Avec yield from, on peut indiquer que divdivs est une fonction génératrice, et qu’il faut évaluer
divs(..) comme un générateur à utiliser comme tel ; une fonction génératrice attend des yield, on
indique qu’elle doit aussi chercher dans la sous-fonction divs.
5.13 w5-s4-x1-produit-scalaire
Naturellement puisque le sujet de la séquence est les expressions génératrices, on vous demande d’utiliser
ce trait pour résoudre cet exercice.
NOTE remarquez bien qu’on a dit expression génératrice et pas nécessairement fonction génératrice.
w5-s4-x1-produit-scalaire 29
MOOC Python 3 Semaine 5, Séquence 6
# NOTE
# auto-exec-for-latex has skipped execution of this cell
5.14 w5-s6-c1-import-once
Voyons cela sur un exemple simpliste, importons un module pour la première fois :
chargement de multiple_import
Fichier /Users/tparment/git/flotpython-course/modules/multiple_import.py
----------------------------------------
1|"""
2|Ce module est conçu pour illustrer le mécanisme de
3|chargement / rechargement
4|"""
5|
6|print("chargement de", __name__)
Si on le charge une deuxième fois (peu importe où, dans le même module, un autre module, une fonction..),
vous remarquez qu’il ne produit aucune impression
Ce qui confirme que le module a déjà été chargé, donc cette instruction import n’a aucun effet autre
qu’affecter la variable multiple_import de nouveau à l’objet module déjà chargé. En résumé, l’instruc-
tion import fait l’opération d’affectation autant de fois qu’on appelle import, mais elle ne charge le
module qu’une seule fois à la première importation.
Une autre façon d’illustrer ce trait est d’importer plusieurs fois le module this
w5-s6-c1-import-once 30
MOOC Python 3 Semaine 5, Séquence 6
— D’une part, cela permet à deux modules de dépendre l’un de l’autre (ou plus généralement à avoir
des cycles de dépendances), sans avoir à prendre de précaution particulière.
— Marginalement, import est une instruction comme une autre, et vous trouverez occasionnellement
un avantage à l’utiliser à l’intérieur d’une fonction, sans aucun surcoût puisque vous ne payez le
prix de l’import qu’au premier appel et non à chaque appel de la fonction.
def ma_fonction():
import un_module_improbable
....
Cet usage n’est pas recommandé en général, mais de temps en temps peut s’avérer très pratique pour
alléger les dépendances entre modules dans des contextes particuliers, comme du code multi-plateformes.
Pour cette raison, python fournit dans le module importlib une fonction reload, qui permet comme
son nom l’indique de forcer le rechargement d’un module, comme ceci :
chargement de multiple_import
w5-s6-c1-import-once 31
MOOC Python 3 Semaine 5, Séquence 6
Remarquez bien que importlib.reload est une fonction et non une instruction comme import - d’où
la syntaxe avec les parenthèses qui n’est pas celle de import.
Notez également que la fonction importlib.reload a été introduite en python3.4, avant, il fallait utiliser
la fonction imp.reload qui est dépréciée depuis python3.4 mais qui existe toujours. Évidemment, vous
devez maintenant exlusivement utiliser la fonction importlib.reload.
À l’intérieur d’un notebook, vous pouvez faire comme ceci pour recharger le code importé automatique-
ment :
À partir de cet instant, et si le code d’un module importé est modifié par ailleurs (ce qui est difficile
à simuler dans notre environnement), alors le module en question sera effectivement rechargé lors du
prochain import. Voyez le lien ci-dessus pour plus de détails.
sys.modules
L’interpréteur utilise cette variable pour conserver la trace des modules actuellement chargés.
[9]: True
[10]: True
[11]: True
La documentation sur sys.modules indique qu’il est possible de forcer le rechargement d’un module en
l’enlevant de cette variable sys.modules.
w5-s6-c1-import-once 32
MOOC Python 3 Semaine 5, Séquence 6
chargement de multiple_import
sys.builtin_module_names
Signalons enfin la variable sys.builtin_module_names qui contient le nom des modules, comme par
exemple le garbage collector gc, qui sont implémentés en C et font partie intégrante de l’interpréteur.
[13]: True
5.15 w5-s6-c2-modules-et-chemins
Lorsque vous lancez l’interpréteur en mode interactif (sans lui donner de point d’entrée), c’est le répertoire
courant qui sert alors d’emplacement par défaut pour votre code. Le répertoire courant, c’est celui où
vous vous trouvez quand vous lancez la commande python. Si vous n’êtes pas sûr de cet emplacement
vous pouvez le savoir en faisant :
[1]: PosixPath('/Users/tparment/git/flotpython-tools/pdf/work')
Comme expliqué ici, lorsque vous importez le module spam, python cherche dans cet ordre :
w5-s6-c2-modules-et-chemins 33
MOOC Python 3 Semaine 5, Séquence 6
Ainsi sans action particulière de l’utilisateur, Python trouve l’intégralité de la librairie standard, ainsi
que les modules et packages installés dans le même répertoire que le fichier passé à l’interpréteur.
La façon dont cela se présente dans l’interpréteur des notebooks peut vous induire en erreur. Aussi je
vous engage à exécuter plutôt, et sur votre machine, le programme suivant :
#!/usr/bin/env python3
import sys
from pathlib import Path
def show_argv_and_path():
print(f"le répertoire courant est {Path.cwd()}")
print(f"le point d'entrée du programme est {sys.argv[0]}")
print(f"la variable sys.path contient")
for i, path in enumerate(sys.path, 1):
print(f"{i}-ème chemin dans sys.path {path}")
show_argv_and_path()
En admettant que
$ cd /le/repertoire/ou/vous/etes/
/le/repertoire/ou/vous/etes
$ python3 /le/repertoire/du/script/run.py
alors vous devriez observer une sortie sur le terminal comme ceci :
C’est-à-dire que :
(NB que d’après cette documentation sys.argv[0] peut contenir un chemin complet ou un simple nom
de fichier, selon votre OS et comment vous invoquez Python)
En tant que programmeur, vous avez aussi la possibilité d’étendre sys.path avant de faire vos import.
Ici encore, ce n’est pas une pratique très courante, ni très recommandée.
w5-s6-c2-modules-et-chemins 34
MOOC Python 3 Semaine 5, Séquence 7
setuptools permet au programmeur d’écrire - dans un fichier qu’on appelle traditionnellement setup.py
- le contenu de son application ; grâce à quoi on peut ensuite de manière unifiée :
On reviendra en Semaine 6 sur les bonnes pratiques pour organiser l’arborescence des sources de votre
projet, et notamment sur les techniques qui permettent de manière sûre de se passer de tout tripotage
intempestif de PYTHONPATH et/ou sys.path.
5.16 w5-s7-c1-import-as
La clause import as
import monmodule
Pour mémoire, le langage permet de faire aussi des import *, qui est d’un usage déconseillé en dehors
de l’interpréteur interactif, car cela crée évidemment un risque de collisions non contrôlées des espaces
de nommage.
import_module
Comme vous pouvez le voir, avec import on ne peut importer qu’un nom fixe. On ne peut pas calculer
le nom d’un module, et le charger ensuite :
w5-s7-c1-import-as 35
MOOC Python 3 Semaine 5, Séquence 7
import modulename
Sachez que vous pourriez utiliser dans ce cas la fonction import_module du module importlib, qui cette
fois permet d’importer un module dont vous avez calculé le nom :
[3]: module
Nous avons maintenant bien chargé le module math, et on l’a rangé dans la variable loaded
[4]: True
La fonction import_module n’est pas d’un usage très courant, dans la pratique on utilise une des formes
de import que nous allons voir maintenant, mais import_module va me servir à bien illustrer ce que
font, précisément, les différentes formes de import.
Reprenons
Maintenant que nous savons ce que fait import_module, on peut récrire les deux formes d’import de
cette façon :
Et :
w5-s7-c1-import-as 36
MOOC Python 3 Semaine 5, Séquence 7
import as
Tout un module
Dans chacun de ces deux cas, on n’a pas le choix du nom de l’entité importée, et cela pose parfois
problème.
Il peut arriver d’écrire un module sous un nom qui semble bien choisi, mais on se rend compte au bout
d’un moment qu’il entre en conflit avec un autre symbole.
Par exemple, vous écrivez un module dans un fichier globals.py et vous l’importez dans votre code
import globals
Puis un moment après pour débugger vous voulez utiliser la fonction built-in globals. Sauf que, en vertu
de la règle de visibilité des variables (rappelez-vous de la règle “LEGB”, que l’on a vue dans une vidéo
de la Semaine 4), le symbole globals se trouve maintenant désigner votre module, et non la fonction.
À ce stade évidemment vous pouvez (devriez) renommer votre module, mais cela peut prendre du temps
parce qu’il y a de nombreuses dépendances. En attendant vous pouvez tirer profit de la clause import
as dont la forme générale est :
autremodule = import_module('monmodule')
temporaire = import_module('monmodule')
autresymbole = temporaire.monsymbole
del temporaire
Quelques exemples
J’ai écrit des modules jouets :
w5-s7-c1-import-as 37
MOOC Python 3 Semaine 5, Séquence 7
[12]: one()
deux()
three()
5.17 w5-s7-c2-recapitulatif-import
Ce module se contente de définir deux fonctions de noms un et deux. Une fois l’import réalisé de cette
façon, on peut accéder au contenu du module en utilisant un nom de variable complet :
un_deux.un()
<function un at 0x10bb84430>
la fonction un dans le module un_deux
Mais bien sûr on n’a pas de cette façon défini de nouvelle variable un ; la seule nouvelle variable dans la
portée courante est donc un_deux :
w5-s7-c2-recapitulatif-import 38
MOOC Python 3 Semaine 5, Séquence 7
[6]: un()
deux()
[7]: try:
print(un_deux_trois)
except NameError:
print("La variable 'un_deux_trois' n'est pas définie")
Il est important de voir que la variable locale ainsi créée, un peu comme dans le cas d’un appel de fonction,
est une nouvelle variable qui est initialisée avec l’objet du module. Ainsi si on importe le module et une
variable du module comme ceci :
alors nous avons maintenant deux variables différentes qui désignent la fonction un dans le module :
[9]: print(un_deux_trois.un)
print(un)
print("ce sont deux façons d'accéder au même objet", un is un_deux_trois.un)
<function un at 0x10bb851b0>
<function un at 0x10bb851b0>
ce sont deux façons d'accéder au même objet True
w5-s7-c2-recapitulatif-import 39
MOOC Python 3 Semaine 5, Séquence 7
<function un at 0x10bb851b0>
1
Ainsi :
Et :
— from foo import var définit une variable var qui désigne un attribut du module ;
— from foo import var as newvar définit une variable newvar qui désigne ce même attribut.
Ces deux formes sont pratiques pour éviter les conflits de nom.
import *
La dernière forme d’import consiste à importer toutes les variables d’un module comme ceci :
Cette forme, pratique en apparence, va donc créer dans l’espace de nommage courant les variables
[14]: un()
deux()
trois()
quatre()
w5-s7-c2-recapitulatif-import 40
MOOC Python 3 Semaine 5, Séquence 7
À cet égard, citons des variantes de ces deux formes qui permettent d’utiliser des noms plus courts. Vous
trouverez par exemple très souvent
import numpy as np
qui permet d’importer le module numpy mais de l’utiliser sous un nom plus court - car avec numpy on
ne cesse d’utiliser des symboles dans le module.
Avertissement : nous vous recommandons de ne pas utiliser la dernière forme import * - sauf dans
l’interpréteur interactif - car cela peut gravement nuire à la lisibilité de votre code.
python est un langage à liaison statique ; cela signifie que lorsque vous concentrez votre attention sur
un (votre) module, et que vous voyez une référence en lecture à une variable spam disons à la ligne 201,
vous devez forcément trouver dans les deux cents premières lignes quelque chose comme une déclaration
de spam, qui vous indique en gros d’où elle vient.
import * est une construction qui casse cette bonne propriété (pour être tout à fait exhaustif, cette
bonne propriété n’est pas non plus remplie avec les fonctions built-in comme len, mais il faut vivre
avec…)
Mais le point important est ceci : imaginez que dans un module vous faites plusieurs import * comme
par exemple
Peu importe le contenu exact de ces deux modules, il nous suffit de savoir qu’un des deux modules expose
la variable patterns.
Dans ce cas de figure vécu, le module utilise cette variable patterns sans avoir besoin de la déclarer
explicitement, si bien qu’à la lecture on voit une utilisation de la variable patterns, mais on n’a plus
aucune idée de quel module elle provient, sauf à aller lire le code correspondant…
Étant donné la façon dont est conçue l’instruction import, on rencontre une limitation lorsqu’on veut,
par exemple, calculer le nom d’un module avant de l’importer.
Si vous êtes dans ce genre de situation, reportez-vous au module importlib et notamment sa fonction
import_module qui, cette fois, accepte en argument une chaîne.
Voici une illustration dans un cas simple. Nous allons importer le module modtools (qui fait partie de
ce MOOC) de deux façons différentes et montrer que le résultat est le même :
w5-s7-c2-recapitulatif-import 41
MOOC Python 3 Semaine 5, Séquence 7
[15]: True
Imports relatifs
Il existe aussi en python une façon d’importer des modules, non pas directement en cherchant depuis
sys.path, mais en cherchant à partir du module où se trouve la clause import. Nous détaillons ce trait
dans un complément ultérieur.
5.18 w5-s7-c3-packages
La notion de package
Pour ce notebook nous aurons besoin de deux utilitaires pour voir le code correspondant aux modules
et packages que nous manipulons :
[3]: show_module(module_simple)
Fichier /Users/tparment/git/flotpython-course/modules/module_simple.py
----------------------------------------
1|print("Chargement du module", __name__)
2|
3|def spam(n):
4| "Le polynôme (n+1)*(n-3)"
5| return n**2 - 2*n - 3
On a bien compris maintenant que le module joue le rôle d’espace de nom, dans le sens où :
w5-s7-c3-packages 42
MOOC Python 3 Semaine 5, Séquence 7
Pour résumer, un module est donc un objet python qui correspond à la fois à :
La notion de package
Lorsqu’il s’agit d’implémenter une très grosse bibliothèque, il n’est pas concevable de tout concentrer en
un seul fichier. C’est là qu’intervient la notion de package, qui est un peu aux répertoires ce que que le
module est aux fichiers.
Nous allons illustrer ceci en créant un package qui contient un module. Pour cela nous créons une
arborescence de fichiers comme ceci :
package_jouet/
__init__.py
module_jouet.py
[7]: show_module(package_jouet)
Fichier /Users/tparment/git/flotpython-course/modules/package_jouet/__init_�
�_.py
----------------------------------------
1|print("chargement du package", __name__)
2|
3|spam = ['a', 'b', 'c']
4|
5|# on peut forcer l'import de modules
6|import package_jouet.module_jouet
7|
8|# et définir des raccourcis
9|jouet = package_jouet.module_jouet.jouet
[8]: show_module(package_jouet.module_jouet)
Fichier /Users/tparment/git/flotpython-course/modules/package_jouet/module_�
�jouet.py
----------------------------------------
1|print("Chargement du module", __name__, "dans le package 'package_jouet'"�
�)
2|
3|jouet = 'une variable définie dans package_jouet.module_jouet'
w5-s7-c3-packages 43
MOOC Python 3 Semaine 5, Séquence 7
Comme on le voit, le package porte le même nom que le répertoire, c’est-à-dire que, de même que le
module module_simple correspond au fichier module_simple.py, le package python package_jouet
corrrespond au répertoire package_jouet.
Note historique par le passé, pour définir un package, il fallait obligatoirement créer dans le répertoire
(celui, donc, que l’on veut exposer à python), un fichier nommé __init__.py ; ce n’est plus le cas depuis
Python-3.3.
Comme on le voit, importer un package revient essentiellement à charger, lorsqu’il existe, le fichier
__init__.py dans le répertoire correspondant (et sinon, on obtient un package vide).
On a coutume de faire la différence entre package et module, mais en termes d’implémentation les deux
objets sont en fait de même nature, ce sont des modules :
[9]: type(package_jouet)
[9]: module
[10]: type(package_jouet.module_jouet)
[10]: module
Ainsi, le package se présente aussi comme un espace de nom, à présent on a une troisième variable spam
qui est encore différente des deux autres :
[11]: package_jouet.spam
L’espace de noms du package permet de référencer les packages ou modules qu’il contient, comme on l’a
vu ci-dessus, le package référence le module au travers de son attribut module_jouet :
[12]: package_jouet.module_jouet
Cette technique correpond à un usage assez fréquent, où on veut exposer directement dans l’espace de
nom du package des symboles qui sont en réalité définis dans un module.
Avec le code ci-dessus, après avoir importé package_jouet, nous pouvons utiliser
[13]: package_jouet.jouet
w5-s7-c3-packages 44
MOOC Python 3 Semaine 5, Séquence 7
[14]: package_jouet.module_jouet.jouet
Mais cela impose alors à l’utilisateur d’avoir une connaissance sur l’organisation interne de la bibliothèque,
ce qui est considéré comme une mauvaise pratique.
D’abord, cela donne facilement des noms à rallonge et du coup nuit à la lisibilité, ce n’est pas pratique.
Mais surtout, que se passerait-il alors si le développeur du package voulait renommer des modules à
l’intérieur de la bibliothèque ? On ne veut pas que ce genre de décision ait un impact sur les utilisateurs.
De manière générale, __init__.py peut contenir n’importe quel code Python chargé d’initialiser le
package. Notez que depuis Python-3.3, la présence de __init__.py n’est plus strictement nécessaire**.
Lorsqu’il est présent, comme pour les modules usuels, __init__.py n’est chargé qu’une seule fois par
l’interpréteur Python ; s’il rencontre plus tard à nouveau le même import, il l’ignore silencieusement.
5.19 w5-s7-c4-import-advanced
Attributs spéciaux
Les objets de type module possèdent des attributs spéciaux ; on les reconnaît facilement car leur nom est
en __truc__, c’est une convention générale dans tout le langage : on en a déjà vu plusieurs exemples,
comme la méthode __iter__().
Voici pour commencer les attributs spéciaux les plus utilisés ; pour cela nous reprenons le package d’un
notebook précédent :
__name__
Le nom canonique du module :
[3]: package_jouet.__name__
[3]: 'package_jouet'
[4]: package_jouet.module_jouet.__name__
[4]: 'package_jouet.module_jouet'
w5-s7-c4-import-advanced 45
MOOC Python 3 Semaine 5, Séquence 7
__file__
L’emplacement du fichier duquel a été chargé le module ; pour un package ceci dénote un fichier
__init__.py :
[5]: package_jouet.__file__
[5]: '/Users/tparment/git/flotpython-course/modules/package_jouet/__init__.py'
[6]: package_jouet.module_jouet.__file__
[6]: '/Users/tparment/git/flotpython-course/modules/package_jouet/module_jouet.p�
�y'
__all__
Il est possible de redéfinir dans un module la variable __all__, de façon à définir les symboles qui sont
réellement concernés par un import *, comme c’est décrit ici.
Je rappelle toutefois que l’usage de import * est fortement déconseillé dans du code de production.
Import absolu
La mécanique des imports telle qu’on l’a vue jusqu’ici est ce qui s’appelle un import absolu qui est
depuis python-2.5 le mécanisme par défaut : le module importé est systématiquement cherché à partir
de sys.path.
Dans ce mode de fonctionnement, si on trouve dans le même répertoire deux fichiers foo.py et bar.py,
et que dans le premier on fait :
import bar
eh bien, alors qu’il existe ici même un fichier bar.py, l’import ne réussit pas (sauf si le répertoire courant
est dans sys.path ; en général ce n’est pas le cas).
Import relatif
Ce mécanisme d’import absolu a l’avantage d’éviter qu’un module local, par exemple random.py, ne
vienne cacher le module random de la bibliothèque standard. Mais comment peut-on faire alors pour
charger le module random.py local ? C’est à cela que sert l’import relatif.
package_relatif/
__init__.py (vide)
main.py
random.py
Le fichier __init__.py ici est vide, et voici le code des deux autres modules :
w5-s7-c4-import-advanced 46
MOOC Python 3 Semaine 5, Séquence 7
print(
f"""On charge main.py
__name__={__name__}
alea={alea()}""")
Nous avons illustré dans le point d’entrée main.py deux exemples d’import relatif :
Les deux clauses as sont bien sûr optionnelles, on les utilise ici uniquement pour bien identifier les
différents objets en jeu.
Le module local random.py expose une fonction alea qui génére un string aléatoire en se basant sur le
module standard random :
import random
def alea():
return(f"[[{random.randint(0, 10)}]]")
Cet exemple montre comment on peut importer un module local de nom random et le module random
qui provient de la librairie standard :
[11]: print(package_relatif.main.alea())
[[4]]
package_relatif/
__init__.py (vide)
main.py
random.py
w5-s7-c4-import-advanced 47
MOOC Python 3 Semaine 5, Séquence 7
subpackage/
__init__.py (vide)
submodule.py
def alea():
return f"<<{imported()}>>"
On charge package_relatif.subpackage.submodule
[15]: print(package_relatif.subpackage.submodule.alea())
<<[[10]]>>
Sur cet exemple, on montre comment un import relatif permet à un module d’importer un module local
qui a le même nom qu’un module standard.
import package_relatif.random
au lieu de
Mais l’import relatif présente notamment l’avantage d’être insensible aux renommages divers à l’intérieur
d’une bibliothèque.
Dit autrement, lorsque deux modules sont situés dans le même répertoire, il semble naturel que l’import
entre eux se fasse par un import relatif, plutôt que de devoir répéter ad nauseam le nom de la bibliothèque
- ici package_relatif - dans tous les imports.
w5-s7-c4-import-advanced 48
MOOC Python 3 Semaine 5, Séquence 7
l’interpréteur :
De la même manière
devient
Par contre l’attribut __file__ n’est pas utilisé : ce n’est pas parce que deux fichiers python sont dans le
même répertoire que l’import relatif va toujours fonctionner. Avant de voir cela sur un exemple, il nous
faut revenir sur l’attribut __name__.
Digression sur l’attribut __name__ Il faut savoir en effet que le point d’entrée du programme - c’est-à-
dire le fichier qui est passé directement à l’interpréteur python - est considéré comme un module dont
l’attribut __name__ vaut la chaîne "__main__".
python3 tests/montest.py
alors la valeur observée dans l’attribut __name__ n’est pas "tests.montest", mais la constante
"__main__".
C’est pourquoi d’ailleurs (et c’est également expliqué ici) vous trouverez parfois à la fin d’un fichier
source une phrase comme celle-ci :
if __name__ == "__main__":
<faire vraiment quelque chose>
<comme par exemple tester le module>
Cet idiome très répandu permet d’insérer à la fin d’un module du code - souvent un code de test - qui :
w5-s7-c4-import-advanced 49
MOOC Python 3 Semaine 5, Séquence 7
— le point d’entrée - celui qui est donné à python sur la ligne de commande - voit comme valeur pour
__name__ la constante "__main__",
— et le mécanisme d’import relatif se base sur __name__ pour localiser les modules importés.
Du coup, par construction, il n’est quasiment pas possible d’utiliser les imports relatifs à partir du script
de lancement.
Pour pallier à ce type d’inconvénients, il a été introduit ultérieurement (voir PEP 366 ci-dessous) la
possibilité pour un module de définir (écrire) l’attribut __package__, pour contourner cette difficulté.
Ce qu’il faut retenir On voit que tout ceci est rapidement assez scabreux. Cela explique sans doute
l’usage relativement peu répandu des imports relatifs.
— considérer votre ou vos points d’entrée comme des accessoires ; un point d’entrée se contente typi-
quement d’importer une classe d’un module, de créer une instance et de lui envoyer une méthode ;
— toujours placer ces points d’entrée dans un répertoire séparé ;
— notamment si vous utilisez setuptools pour distribuer votre application via pypi.org, vous verrez
que ces points d’entrée sont complètement pris en charge par les outils d’installation.
— la technique qu’on a vue rapidement - de tester si __name__ vaut "__main__" - est extrêmement
basique et limitée. Le mieux est de ne pas l’utiliser en fait, en dehors de micro-maquettes.
— en pratique on écrit les tests dans un répertoire séparé - souvent appelé tests - et en tirant profit
de la librairie unittest.
— du coup les tests sont toujours exécutés avec une phrase comme
et dans ce contexte-là, il est possible par exemple pour les tests de recourir à l’import relatif.
5.20 w5-s7-x1-decode-zen
w5-s7-x1-decode-zen 50
MOOC Python 3 Semaine 5, Séquence 7
Il suit du cours qu’une fois cet import effectué nous avons accès à une variable this, de type module :
[2]: this
But de l’exercice
Constatant que le texte du manifeste doit se trouver quelque part dans le module, le but de l’exercice est
de deviner le contenu du module, et d’écrire une fonction decode_zen, qui retourne le texte du manifeste.
Indices
Cet exercice peut paraître un peu déconcertant ; voici quelques indices optionnels :
[4]: ['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'c',
'd',
'i',
w5-s7-x1-decode-zen 51
MOOC Python 3 Semaine 5, Séquence 7
's']
Vous pouvez ignorer this.c et this.i, les deux autres variables du module sont importantes pour nous.
Ceci devrait vous donner une idée de comment utiliser une des deux variables du module :
[6]: True
[7]: True
[8]: 52
À vous de jouer
Correction
[ ]: exo_decode_zen.correction(decode_zen)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w5-s7-x1-decode-zen 52