Formation Python Cours 3
Formation Python Cours 3
Initiation à la programmation
Xavier Dupré
http ://www.xavierdupre.fr/
Table des matières
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1 Ordinateur et langages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2 Présentation du langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3 Installation du langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.4 Installation d’un éditeur de texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5 Premier programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.6 Installation d’extensions (ou modules externes) . . . . . . . . . . . . . . . . . . . . . . . . 18
1.7 Outils connexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4. Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.1 Présentation des classes : méthodes et attributs . . . . . . . . . . . . . . . . . . . . . . . . 79
4.2 Constructeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.3 Apport du langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.4 Opérateurs, itérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.5 Méthodes, attributs statiques et ajout de méthodes . . . . . . . . . . . . . . . . . . . . . . 92
4.6 Copie d’instances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.7 Attributs figés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
4.8 Héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4.9 Compilation de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
TABLE DES MATIÈRES 3
5. Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
5.1 Principe des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
5.2 Définir ses propres exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
5.3 Exemples d’utilisation des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6. Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
6.1 Modules et fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
6.2 Modules internes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
6.3 Modules externes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
6.4 Python et les autres langages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.5 Ecrire un module en langage C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
6.6 Boost Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
9. Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
9.1 Premier thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
9.2 Synchronisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
9.3 Interface graphique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
9.4 Files de messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Index 446
Première partie
LE LANGAGE PYTHON
Les programmes informatiques sont souvent l’aboutissement d’un raisonnement, d’une construction, d’une
idée, parfois imprécise mais dont le principe général est compris. Et pour valider cette idée, on utilise un
langage de programmation qui ne tolère jamais l’imprécision, qui refuse de comprendre au moindre signe
de ponctuation oublié, qui est d’une syntaxe si rigide.
On reste parfois indécis devant une erreur qui se produit avec un exemple qu’on a pourtant recopié sans
ajout. On compare les deux versions sans faire attention aux petits détails qui ne changent rien pour le
concepteur et beaucoup pour le langage. C’est un espace en plus ou en moins, un symbole : oublié... Il
est fréquent de ne pas vérifier ces petits détails lorsqu’on commence à programmer. Et ils découragent
souvent. On s’habitue peu à peu au fait qu’il ne faut pas confondre parenthèses et crochets, qu’il manque
des guillemets, qu’il y a une lettre en plus, ou en moins.
Lorsque sa syntaxe n’est pas respectée, le langage de programmation ne cherche jamais à comprendre
l’intention du programmeur. Il faut penser à la vérifier en premier lorsqu’une erreur se produit. Les messages
d’erreur, obscurs au premier abord, donnent néanmoins un bon indice pour corriger un programme. Et si
cela ne suffisait pas, il ne faut pas hésiter à recopier ce message dans un moteur de recherche sur Internet
pour y trouver une indication dès les premiers résultats, et le plus souvent en français.
Cette première partie est consacrée à la description du langage Python. Il est parfois utile de reproduire
un des exemples, de le faire fonctionner, de le modifier, d’y introduire des erreurs puis de les corriger.
Plus tard, dans de plus longs programmes, les mêmes erreurs seront plus difficiles à déceler et l’expérience
montre qu’on fait presque toujours les mêmes erreurs.
Chapitre 1
Introduction
Ce chapitre s’intéresse tout d’abord à l’installation du langage Python et à la réalisation d’un premier pro-
gramme avec des instructions dont le sens est intuitif. Les derniers paragraphes présentent de nombreuses
extensions disponibles sur Internet. Elles rendent le langage Python très attractif dans des domaines variés.
Ces extensions témoignent que ce langage emporte l’adhésion de nombreux informaticiens qui en retour
assurent sa pérennité. Il permet de relier facilement différents éléments, différentes applications. C’est une
des raisons de son succès.
1.1.1 L’ordinateur
On peut considérer simplement qu’un ordinateur est composé de trois ensembles : le microprocesseur, la
mémoire, les périphériques. Cette description n’a pas varié en cinquante ans depuis qu’un scientifique du
nom de von Neumann l’a imaginée.
Le microprocesseur est le cœur de l’ordinateur, il suit les instructions qu’on lui donne et ne peut travailler
qu’avec un très petit nombre d’informations. Sa vitesse se mesure en GigaHertz (GHz) qui correspondent
au nombre d’opérations qu’il est capable d’effectuer en une seconde et en nombre de cœurs qui détermine
le nombre d’opérations en parallèle qu’il est capable d’exécuter. On lui adjoint une mémoire avec laquelle
il échange sans arrêt des données. Sa capacité se mesure en octets (kilo-octets, mégaoctets, gigaoctets ou
leurs abréviations Ko, Mo, Go 1 ). Ces échanges entre processeur et mémoire sont rapides.
Les périphériques regroupent tout le reste (écran, clavier, souris, disque dur, imprimante...). Ils sont princi-
palement de deux types : les périphériques de stockages (disque dur, DVD) et ceux qui nous permettent de
dialoguer avec l’ordinateur, que ce soit pour afficher, sonoriser (écran, enceintes) ou pour recevoir (souris,
clavier, webcam, micro...).
Leur écriture est indépendante du langage choisi, qu’il soit écrit en Basic, en Pascal, en C, en Perl, en PHP,
en Python, en français, un algorithme reste le même. Pour les algorithmes simples (un tri par exemple), le
passage d’un langage à l’autre consiste souvent à traduire mot-à-mot, ce qui favorise l’apprentissage d’un
nouveau langage informatique lorsqu’on en connaît déjà un. Les différences entre ces langages résident
dans leur spécialisation, le Visual Basic permet de piloter les applications Microsoft Office, le PHP, le
JavaScript sont dédiés à la programmation Internet. Le langage Python n’est pas spécialisé. Il est souvent
moins efficace que chaque langage appliqué à son domaine de prédilection mais il peut éviter l’apprentissage
d’une syntaxe différente.
Le langage Python n’est pas un langage compilé car un programme Python n’est pas traduit en langage
machine, il est un langage interprété. Entre son écriture et son exécution, il n’y a pas d’étape intermédiaire
telle que la compilation et on peut ainsi tester un programme plus rapidement même si son exécution est
alors plus lente.
La table 3.1 (page 48) regroupe les mots-clés du langage Python. Elle contient peu de mots-clés : son
concepteur s’est attaché à créer un langage objet avec la grammaire la plus simple possible.
Les instructions sont très courtes et tiennent sur une ligne mais si on n’en prend qu’une partie, elle perd
son sens. Ce serait comme considérer une phrase avec un verbe transitif mais sans son complément d’objet
direct.
distribué sous forme de logiciel libre. Python est couvert par sa propre licence 2 . Toutes les versions depuis
la 2.0.1 sont compatibles avec la licence GPL 3 . Selon Wikipedia 4 , Rossum travaillerait maintenant pour
l’entreprise Google qui propose une plate-forme de développement de sites web en Python 5 et qui héberge
de nombreux projets en langage Python via son projet Google Code 6 .
Perl, PHP, ...). Il est souvent possible de transposer les mêmes algorithmes d’un langage à un autre. Le
choix approprié est bien souvent celui qui offre la plus grande simplicité lors de la mise en œuvre d’un
programme même si cette simplicité s’acquiert au détriment de la vitesse d’exécution. Le langage PHP
est par exemple très utilisé pour la conception de sites Internet car il propose beaucoup de fonctions très
utiles dans ce contexte. Dans ce domaine, le langage Python est une solution alternative intéressante.
Comme la plupart des langages, le langage Python est tout d’abord portable puisqu’un même programme
peut être exécuté sur un grand nombre de systèmes d’exploitation comme Linux, Microsoft Windows,
Mac OS X... Python possède également l’avantage d’être entièrement gratuit tout en proposant la possi-
bilité de pouvoir réaliser des applications commerciales à l’aide de ce langage.
Si le langage C reste le langage de prédilection pour l’implémentation d’algorithmes complexes et gour-
mands en temps de calcul ou en capacités de stockage, un langage tel que Python suffit dans la plupart des
cas. De plus, lorsque ce dernier ne convient pas, il offre toujours la possibilité, pour une grande exigence de
rapidité, d’intégrer un code écrit dans un autre langage tel que le C/C++ 8 ou Java, et ce, d’une manière
assez simple. La question du choix du langage pour l’ensemble d’un projet est souvent superflue, il est cou-
rant aujourd’hui d’utiliser plusieurs langages et de les assembler. Le langage C reste incontournable pour
concevoir des applications rapides, en revanche, il est de plus en plus fréquent d’"habiller" un programme
avec une interface graphique programmée dans un langage tel que Python.
En résumé, l’utilisation du langage Python n’est pas restreinte à un domaine : elle comprend le calcul
scientifique, les interfaces graphiques, la programmation Internet. Sa gratuité, la richesse des extensions
disponibles sur Internet le rendent séduisant dans des environnements universitaire et professionnel. Le
langage est vivant et l’intérêt qu’on lui porte ne décroît pas. C’est un critère important lorsqu’on choisit
un outil parmi la myriade de projets open source. De plus, il existe différentes solutions pour améliorer
son principal point faible qui est la vitesse d’exécution.
La documentation (de langue anglaise 10 ) décrit en détail le langage Python, elle inclut également un
tutoriel 11 qui permet de le découvrir. La ligne de commande (voir figure 1.2) permet d’exécuter des
8. Ce point est d’ailleurs abordé au paragraphe 6.6, page 140.
9. Cette version est disponible à l’adresse http://www.python.org/download/. C’est la version utilisée pour développer les
exemples présents dans ce livre.
10. http://www.python.org/doc/
11. La requête python français de nombreux sites de documentation sur Python en français.
1. Introduction 11
instructions en langage Python. Elle est pratique pour effectuer des calculs mais il est nécessaire d’utiliser
un éditeur de texte pour écrire un programme, de le sauvegarder, et de ne l’exécuter qu’une fois terminé
au lieu que chaque ligne de celui-ci ne soit interprétée immédiatement après qu’elle a été écrite comme
c’est le cas pour une ligne de commande.
Figure 1.2 : Ligne de commande, la première ligne affecte la valeur 3 à la variable x, la seconde ligne l’affiche.
>>> x = 3
>>> y = 6
>>> z = x * y
>>> print z
18
>>>
Après l’exécution de ces quelques lignes, les variables 13 x, y, z existent toujours. La ligne de commande
ressemble à une calculatrice améliorée. Pour effacer toutes les variables créées, il suffit de redémarrer
12. voir la page http://www.python.org/download/mac/ ou http://www.python.org/download/linux/
13. Les variables sont définies au chapitre 2. En résumé, on peut considérer une variable comme une lettre, un mot qui
désigne une information. Dans cet exemple, les trois variables désignent des informations numériques qu’on peut manipuler
1. Introduction 12
l’interpréteur par l’intermédiaire du menu Shell–>Restart Shell. Les trois variables précédentes auront
disparu.
Il n’est pas possible de conserver le texte qui a été saisi au clavier, il est seulement possible de rappeler une
instruction déjà exécutée par l’intermédiaire de la combinaison de touches ALT + p, pressée une ou plusieurs
fois. La combinaison ALT + n permet de revenir à l’instruction suivante. Pour écrire un programme et ainsi
conserver toutes les instructions, il faut d’actionner le menu File–>New Window qui ouvre une seconde
fenêtre qui fonctionne comme un éditeur de texte (voir figure 1.4).
Après que le programme a été saisi, le menu Run→Run Module exécute le programme. Il demande au préalable
s’il faut enregistrer le programme et réinitialise l’interpréteur Python pour effacer les traces des exécutions
précédentes. Le résultat apparaît dans la première fenêtre (celle de la figure 1.3). La pression des touches
"Ctrl + C" permet d’arrêter le programme avant qu’il n’arrive à sa fin.
Remarque 1.7 : fenêtre intempestive
Si on veut se débarrasser de cette fenêtre intempestive qui demande confirmation pour enregistrer les
dernières modifications, il suffit d’aller dans le menu Options–>Configure IDLE et de choisir No prompt sur
la quatrième ligne dans la rubrique General (voir figure 1.5). Ce désagrément apparaît sur la plupart des
éditeurs de texte et se résout de la même manière.
Cette description succincte permet néanmoins de réaliser puis d’exécuter des programmes. Les autres
fonctionnalités sont celles d’un éditeur de texte classique, notamment la touche F1 qui débouche sur l’aide
associée au langage Python. Il est possible d’ouvrir autant de fenêtres qu’il y a de fichiers à modifier
simultanément.
comme si elles étaient effectivement des nombres. Les variables supprimées, les informations qu’elles désignaient ne plus
accessibles et sont perdues également.
1. Introduction 13
Néanmoins, il est préférable d’utiliser d’autres éditeurs plus riches comme celui proposé au paragraphe 1.4.1.
Ils proposent des fonctions d’édition plus évoluées que l’éditeur installé avec Python.
Il suffit de préciser le répertoire où se trouve l’interpréteur qui devrait être c : /python26 sur Microsoft
Windows si le répertoire d’installation par défaut du langage Python n’a pas été modifié.
14. Cet éditeur est accessible depuis l’adresse http://www.scintilla.org/SciTE.html et téléchargeable depuis l’adresse http:
//scintilla.sourceforge.net/SciTEDownload.html.
15. Il arrive fréquemment que des universités, des sociétés interdisent à leurs utilisateurs l’installation de nouveaux logiciels
sur leurs machines pour des raisons de sécurité. Il faut des droits spécifiques pour contourner cette interdiction.
1. Introduction 14
if PLAT_WIN
command.go.*.py=c:\Python26\python -u "$(FileNameExt)"
command.go.subsystem.*.py=0
command.go.*.pyw=c:\Python26\pythonw -u "$(FileNameExt)"
command.go.subsystem.*.pyw=1
Une fois ce fichier enregistré, il ne reste plus qu’à écrire un programme Python. Une fois terminé, la touche
F5 lance son exécution. L’exemple de la figure 1.7 montre un cas où l’exécution a été interrompue par une
erreur.
Le site Internet de SciTe propose quelques trucs et astuces pour configurer l’éditeur à votre convenance.
Comme à peu près tous les logiciels, il dispose d’une rubrique FAQ 16 (Frequently Asked Questions) qui
recense les questions et les réponses les plus couramment posées. Cette rubrique existe pour la plupart des
éditeurs et plus généralement pour la plupart des applications disponibles sur Internet.
afin de vérifier chaque ligne de son exécution. Lorsqu’un programme produit de mauvais résultats, la
première solution consiste à afficher de nombreux résultats intermédiaires mais cela nécessite d’ajouter
des instructions un peu partout dans le programme. La seconde méthode est l’utilisation du débugger.
Sans modifier le code informatique, il est possible d’arrêter temporairement l’exécution à une ligne donnée
pour inspecter le contenu des variables à cet instant de l’exécution. Ce logiciel fonctionne sans autre
manipulation supplémentaire autre que son installation.
Cet éditeur, contrairement à SciTe, ne fonctionne qu’avec Python et ne peut être utilisé avec d’autres
langages. Son apparence est adaptée au développement. Il permet également de voir la liste des variables,
fonctions, classes, présentes dans un programme.
1.4.6 Tabulations
De nombreux éditeurs utilisent les tabulations pour mettre en forme un programme et tenir compte des
décalages (indentation) d’un bloc par rapport à un autre. Il est conseillé de remplacer ces tabulations par
des espaces, cela évite les confusions et les mauvaises interprétations lorsque tabulations et espaces sont
mélangés. La taille d’une tabulation peut varier d’un éditeur à l’autre contrairement aux espaces.
Un programme ne barbouille pas l’écran de texte indésirable, il n’affiche que ce que le programmeur lui
demande d’afficher et cette intention se matérialise grâce à l’instruction print. On peut ensuite écrire
quelques lignes qu’on pourrait également taper sur une calculatrice :
Ces deux lignes affichent 78.5398 qui est la surface d’un cercle de 5 mètres de côté. Le symbole ∗∗ correspond
à l’opération puissance. Il existe une constante PI ou π mais qui ne fait pas partie du langage lui-même : elle
est définie dans une extension interne 28 du langage qui contient bon nombre de fonctions mathématiques
telles que les cosinus, sinus...
Dans ce cas, il faut ajouter une ligne placée en première position qui précise que des accents pourront être
utilisés. L’exécution du programme qui suit ne soulève aucune erreur.
# coding: latin-1
print "accentué"
Cette ligne précise en fait que l’interpréteur Python doit utiliser un jeu de caractères spécial. Ces jeux de
caractères ont parfois leur importance, les navigateurs n’aiment pas trop les accents au sein des adresses
Internet : il est parfois préférable de ne pas utiliser d’accents sur les sites de réservations d’hôtels, de trains
ou d’avions.
Par défaut, l’interpréteur Python utilise le jeu de caractères le plus réduit, il faut lui préciser qu’il doit
en utiliser un plus étendu incluant les accents. On trouve également sous Microsoft Windows la syntaxe
équivalente suivante :
# coding: cp1252
print "accentué"
Enfin, s’il fallait utiliser des caractères autres que latins, il faudrait alors choisir le jeu de caractères utf − 8
qui s’appliquent aussi aux langues orientales.
# coding: utf-8
print "accentué"
28. Cette extension est installée automatiquement avec Python contrairement à une extension externe.
1. Introduction 18
A cause des accents, la plupart des exemples cités dans ce livre ne fonctionnent pas sans cette première
ligne qui a parfois été enlevée lors de l’impression pour des questions de lisibilité. Il faut penser à l’ajouter
pour reproduire les exemples. Le paragraphe 7.8 page 168 est plus complet à ce sujet.
Remarque 1.9 : #!/usr/local/bin/python
Sur Internet, on trouve de nombreux exemples commençant par la ligne suivante :
#!/usr/local/bin/python
On rencontre souvent cette ligne pour un programme écrit sur une machine Linux, elle indique l’emplace-
ment de l’interpréteur Python à utiliser. Cette ligne s’appelle un shebang.
1.6.1 Graphes
matplotlib 32 : ce module reprend la même logique que le logiciel MatLab ou Octave. Pour ceux
que la finance intéresse, il comporte également des fonctions qui permettent de récupérer des historiques
quotidiens de cours d’actions depuis le site Yahoo ! Finance 33 .
biggles 34 : ce module permet de dessiner des courbes (2D, 3D) et de convertir des graphiques sous
forme d’images de tout format. Ce module nécessite l’installation de fichiers supplémentaires (libpng, zlib,
29. L’instruction help() saisie sur la ligne de commande Python retourne entre autres le numéro de version.
30. http://pypi.python.org/pypi
31. On peut citer http://wikipython.flibuste.net/ ou http://www.afpy.org/, deux sites français qui regroupent des informations
intéressantes sur de nouveaux modules ou des exemples d’utilisation de modules courants.
32. http://matplotlib.sourceforge.net/
33. Avec la fonction fetch_historical_yahoo.
34. http://biggles.sourceforge.net/
1. Introduction 19
plotutils) 35 .
chartdirector 36 : beaucoup plus rapide que MatPlotLib, ce module est gratuit à condition d’accepter un
bandeau jaune en bas de chaque graphique.
pydot 37 : ce module propose le dessin de graphes dans une image (arbre généalogique, réseau de distribu-
tion, ...). Le module pydot permet de dessiner tout type de graphe. Ce module nécessite un autre module
pyparsing 38 et une application Graphviz 39 . Avec cet outil, on peut réaliser des graphes comme celui de
la figure 1.9. Ce module est utilisé lors de l’exercice 10.5.1 page 230.
pyx 40 : ce module est aussi une librairie qui permet de tracer des graphes avec la possibilité de convertir
directement ceux-ci en fichiers PDF ou EPS.
Sans utiliser nécessairement une extension, il existe des outils comme GNUPlot 41 venu du monde Linux
qui s’exécutent facilement en ligne de commande. Cet outil propose son propre langage de programmation
dédié à la création de graphiques. Une fois ce langage maîtrisé, il est facile de construire un programme
qui permet de l’exécuter en ligne de commande pour récupérer l’image du graphique.
Remarque 1.10 : sortie texte et graphique
Un des moyens simples de construire un rapport mélangeant résultats numériques et graphiques est de
construire un fichier au format HTML 42 puis de l’afficher grâce à un navigateur Internet. Il est même
possible de construire un fichier au format PDF en utilisant l’outil Latex 43 .
Il propose de nombreux algorithmes de classifications, d’analyse des données qui ont trait au machine
learning.
Rpy 47 : le langage R 48 est un langage dédié aux calculs statistiques. Il est optimisé pour la manipulation
de grandes matrices et des calculs statistiques et matriciels rapides. Il dispose également de nombreux
graphiques. Il faut au préalable installer Python et R puis le module Rpy fera la liaison entre les deux.
On pourrait également citer le module mdp 49 qui offre quelques fonctionnalités relatives à l’analyse des
données tels que l’analyse en composante principale (ACP) ou encore le module cvxopt 50 qui propose
des algorithmes d’optimisation quadratique sous contraintes.
1.6.3 Images
PythonImageLibrary(PIL) 51 : ce module permet d’utiliser les images, quelque soit leur format. Elle propose
également quelques traitements d’images simples, rotation, zoom, histogrammes.
Lorsqu’on souhaite réaliser un jeu, il est nécessaire d’avoir une interface graphique plus conciliante avec la
gestion des images, des dessins, des sons voire des vidéos. C’est le cas du module qui suit.
pygame 58 : ce module est prévu pour concevoir des jeux en Python. Il permet entre autres d’écouter les
morceaux enregistrés sur un CD, d’afficher un film et également d’afficher des images, de récupérer les
touches pressées au clavier, les mouvements de la souris, ceux de la manette de jeux. Il suffit d’une heure
ou deux pour écrire ses premiers programmes avec pygame. Il n’est pas prévu pour réaliser des jeux en 3D
mais suffit pour bon nombre de jeux de plateau, jeux de réflexion.
Pour la réalisation d’images et d’animations 3D, on peut utiliser le module pyOpenGL 59 qui s’interface avec
pygame, wxPython ou pyQt. C’est un module dont la maîtrise ne se compte plus en heures mais en dizaine
d’heures tout comme le module directpython 60 qui est l’équivalent de pyOpenGL mais uniquement sous
Microsoft Windows.
import psyco
psyco.full ()
Dans les faits, le programme s’exécute plus vite mais pas uniformément plus vite : les calculs sont le plus
favorisés. Toutefois, même avec ce module, le langage Python reste très lent par rapport à un langage
compilé comme le C++. L’outil Pyrex 64 propose une solution en permettant l’insertion de code C++ au
sein d’un programme Python. Pyrex n’est pas un module, c’est un interpréteur Python modifié pour
prendre en compte ces insertions C++. Il est essentiel de connaître la syntaxe du langage C++. Pyrex
propose des temps d’exécution tout à fait acceptable par rapport à ceux du C++ et il accroît fortement
la vitesse d’écriture des programmes. Son installation n’est pas toujours évidente car il faut le relier au
compilateur C++ GCC 65 . Une approche semblable est développée par l’extension Weave 66 du module
scipy 67 . Pyrex et Weave offre des performances bien supérieures à Python seul, proches de celle du
langage C. Weave conviendra sans doute mieux à ceux qui ont déjà programmé en C.
58. http://www.pygame.org/
59. http://pyopengl.sourceforge.net/
60. http://sourceforge.net/projects/directpython/
61. http://python.net/crew/mhammond/win32/Downloads.html
62. comme http://wikipython.flibuste.net/CodeWindows#ExempleavecExcel
63. http://www.reportlab.com/software/opensource/rl-toolkit/
64. http://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/
65. http://gcc.gnu.org/, ce compilateur présent sur toute machine Linux est porté également sur Microsoft Windows (voir
le site http://www.mingw.org/).
66. http://www.scipy.org/Weave
67. http://www.scipy.org/
1. Introduction 22
L’accélération de l’exécution d’un programme Python suscite l’intérêt de nombreux informaticiens, c’est
aussi l’objectif de l’outil PyPy 68 qui réécrit automatiquement certaines partie du code en langage C et
l’intègre au programme existant.
CherryPy 91 : il permet de réaliser des applications Internet. Il est suffisamment robuste pour être utilisé
par de nombreux sites Internet. Il peut paraître fastidieux de s’y plonger mais ce genre d’outil offre un
réel gain de temps lors de la conception de sites qui incluent de nombreux échanges avec les utilisateurs.
TurboGears 92 : cet outil en regroupe plusieurs dont CherryPy, c’est un environement complet pour créer
des sites Internet.
Zope 93 : c’est encore un outil pour concevoir des sites Internet, il est plus vieux et beaucoup plus riche. Il
est également plus complexe à appréhender. Il est plutôt destiné au développement de sites conséquents.
91. http://www.cherrypy.org/
92. http://turbogears.org/
93. http://www.zope.org/
Chapitre 2
Types et variables du langage Python
2.1 Variables
Il est impossible d’écrire un programme sans utiliser de variable. Ce terme désigne le fait d’attribuer un
nom ou identificateur à des informations : en les nommant, on peut manipuler ces informations beaucoup
plus facilement. L’autre avantage est de pouvoir écrire des programmes valables pour des valeurs qui
varient : on peut changer la valeur des variables, le programme s’exécutera toujours de la même manière
et fera les mêmes types de calculs quelles que soient les valeurs manipulées. Les variables jouent un rôle
semblable aux inconnues dans une équation mathématique.
L’ordinateur ne sait pas faire l’addition de plus de deux nombres mais cela suffit à calculer la somme de
n premiers nombres entiers. Pour cela, il est nécessaire de créer une variable intermédiaire qu’on appellera
par exemple somme de manière à conserver le résultat des sommes intermédiaires.
somme = 0 # initialisation : la somme est nulle
for i in range(1,n) : # pour tous les indices de 1 à n exclu
somme = somme + i # on ajoute le i ème élément à somme
Comme le typage est dynamique en Python, le type n’est pas précisé explicitement, il est implicitement
liée à l’information manipulée. Par exemple, en écrivant, x = 3.4, on ne précise pas le type de la variable
x mais il est implicite car x reçoit une valeur réelle : x est de type réel ou float en Python. Pour leur
première initialisation, une variable reçoit dans la plupart des cas une constante :
Le langage Python possède un certain nombre de types de variables déjà définis ou types fondamentaux à
partir desquels il sera possible de définir ses propres types (voir chapitre 4). Au contraire de langages tels
que le C, il n’est pas besoin de déclarer une variable pour signifier qu’elle existe, il suffit de lui affecter
une valeur. Le type de la variable sera défini par le type de la constante qui lui est affectée. Le type d’une
variable peut changer, il correspond toujours au type de la dernière affectation.
x = 3.5 # création d’une variable nombre réel appelée x initialisée à 3.5
# 3.5 est un réel, la variable est de type "float"
sc = "chaîne" # création d’une variable chaîne de caractères appelée str
# initialisée à "chaîne", sc est de type "str"
2. Types et variables du langage Python 26
Avec ce symbole, les longues instructions peuvent être écrites sur plusieurs lignes de manière plus lisibles,
de sorte qu’elles apparaissent en entier à l’écran. Si le dernier caractère est une virgule, il est implicite.
Les paragraphes suivant énumèrent les types incontournables en Python. Ils sont classés le plus souvent en
deux catégories : types immuables ou modifiables. Tous les types du langage Python sont également des
objets, c’est pourquoi on retrouve dans ce chapitre certaines formes d’écriture similaires à celles présentées
plus tard dans le chapitre concernant les classes (chapitre 4).
Autrement dit, la simple instruction x+=3 qui consiste à ajouter à la variable x la valeur 3 crée une seconde
variable dont la valeur est celle de x augmentée de 3 puis à en recopier le contenu dans celui de la variable
x. Les nombres sont des types immuables tout comme les chaînes de caractères et les tuple qui sont des
tableaux d’objets. Il n’est pas possible de modifier une variable de ce type, il faut en recréer une autre du
même type qui intègrera la modification.
Certaines fonctions 1 utilisent cette convention lorsqu’il leur est impossible de retourner un résultat. Ce
n’est pas la seule option pour gérer cette impossibilité : il est possible de générer une exception 2 , de
retourner une valeur par défaut ou encore de retourner None. Il n’y a pas de choix meilleur, il suffit juste
de préciser la convention choisie.
1. Les fonctions sont définies au paragraphe 3.4, plus simplement, ce sont des mini-programmes : elles permettent de
découper un programme long en tâches plus petites. On les distingue des variables car leur nom est suivi d’une liste de
constantes ou variables comprises entre parenthèses et séparées par une virgule.
2. voir chapitre 5
2. Types et variables du langage Python 27
x = 3
y = 3.0
print "x =", x, type(x) x = 3 <type ’int’>
print "y =", y, type(y) y = 3.0 <type ’float’>
La liste des opérateurs qui s’appliquent aux nombres réels et entiers suit ce paragraphe. Les trois premiers
résultats s’expliquent en utilisant la représentation en base deux. 8 « 1 s’écrit en base deux 100 « 1 =
1000, ce qui vaut 16 en base décimale : les bits sont décalés vers la droite ce qui équivaut à multiplier par
deux. De même, 7&2 s’écrit 1011&10 = 10, qui vaut 2 en base décimale. Les opérateurs <<, >>, |, & sont
des opérateurs bit à bit, ils se comprennent à partir de la représentation binaire des nombres entiers.
Les fonctions int et float permettent de convertir un nombre quelconque ou une chaîne de caractères
respectivement en un entier (arrondi) et en un nombre réel.
x = int (3.5)
y = float (3)
z = int ("3")
print "x:", type(x), " y:", type(y), " z:", type(z)
# affiche x: <type ’int’> y: <type ’float’> z: <type ’int’>
Les opérateurs listés par le tableau ci-dessus ont des priorités différentes, triés par ordre croissant 3 . Tou-
tefois, il est conseillé d’avoir recours aux parenthèses pour enlever les doutes : 3 ∗ 2 ∗ ∗4 = 3 ∗ (2 ∗ ∗4).
Remarque 2.7 : division entière
Il existe un cas pour lequel cet opérateur cache un sens implicite : lorsque la division opère sur deux
nombres entiers ainsi que le montre l’exemple suivant.
3. La page http://docs.python.org/reference/expressions.html#operator-summary est plus complète à ce sujet.
2. Types et variables du langage Python 28
x = 11
y = 2
z = x / y # le résultat est 5 et non 5.5 car la division est entière
Pour obtenir le résultat juste incluant la partie décimale, il faut convertir les entiers en réels.
x = float (11)
y = float (2)
z = x / y # le résultat est 5.5 car c’est une division entre deux réels
Pour éviter d’écrire le type float, on peut également écrire 11.0/2 de façon à spécifier explicitement que la
valeur 11.0 est réelle et non entière. L’opérateur // permet d’effectuer une division entière lorsque les deux
nombres à diviser sont réels, le résultat est un entier mais la variable est de type réel si l’un des nombres
est de type réel.
2.2.3 Booléen
Les booléens sont le résultat d’opérations logiques et ont deux valeurs possibles : True ou False. Voici la
liste des opérateurs qui s’appliquent aux booléens.
x = 4 < 5
print x # affiche True
print not x # affiche False
Voici la liste des opérateurs de comparaisons qui retournent des booléens. Ceux-ci s’applique à tout type,
aux entiers, réels, chaînes de caractères, t-uples... Une comparaison entre un entier et une chaîne de
caractères est syntaxiquement correcte même si le résultat a peu d’intérêt.
A l’instar des nombres réels, il est préférable d’utiliser les parenthèses pour éviter les problèmes de priorités
d’opérateurs dans des expressions comme : 3 < x and x < 7. Toutefois, pour cet exemple, Python accepte
l’écriture résumée qui enchaîne des comparaisons : 3 < x and x < 7 est équivalent à 3 < x < 7. Il existe deux
autres mots-clés qui retournent un résultat de type booléen :
opérateur signification
is test d’identification
in test d’appartenance
Ces deux opérateurs seront utilisés ultérieurement, in avec les listes, les dictionnaires, les boucles (para-
graphe 3.3.2), is lors de l’étude des listes (paragraphe 2.3.2) et des classes (chapitre 4). Bien souvent, les
booléens sont utilisés de manière implicite lors de tests (paragraphe 3.2.1) ce qui n’empêche pas de les
déclarer explicitement.
x = True
y = False
2. Types et variables du langage Python 29
Ce texte est compris entre deux guillemets ou deux apostrophes, ces deux symboles sont interchangeables.
L’exemple suivant montre comment créer une chaîne de caractères. Il ne faut pas confondre la partie entre
guillemets ou apostrophes, qui est une constante, de la variable qui la contient.
t = "string = texte"
print type (t), t
t = ’string = texte, initialisation avec apostrophes’
print type (t), t
t = "morceau 1" \
"morceau 2" # second morceau ajouté au premier par l’ajout du symbole \,
# il ne doit rien y avoir après le symbole \,
# pas d’espace ni de commentaire
print t
t = """première ligne
seconde ligne""" # chaîne de caractères qui s’étend sur deux lignes
print t
La troisième chaîne de caractères créée lors de ce programme s’étend sur deux lignes. Il est parfois plus com-
mode d’écrire du texte sur deux lignes plutôt que de le laisser caché par les limites de fenêtres d’affichage.
Python offre la possibilité de couper le texte en deux chaînes de caractères recollées à l’aide du symbole \
à condition que ce symbole soit le dernier de la ligne sur laquelle il apparaît. De même, lorsque le texte
contient plusieurs lignes, il suffit de les encadrer entre deux symboles """ ou ”’ pour que l’interpréteur
Python considère l’ensemble comme une chaîne de caractères et non comme une série d’instructions.
Par défaut, le Python ne permet pas l’insertion de caractères tels que les accents dans les chaînes de
caractères, le paragraphe 1.5.2 (page 17) explique comment résoudre ce problème. De même, pour insérer
un guillemet dans une chaîne de caractères encadrée elle-même par des guillemets, il faut le faire précéder
du symbole \. La séquence \” est appelée un extra-caractère (voir table 2.1).
Remarque 2.9 : préfixe "r", chaîne de caractères
Il peut être fastidieux d’avoir à doubler tous les symboles \ d’un nom de fichier. Il est plus simple dans ce
cas de préfixer la chaîne de caractères par r de façon à éviter que l’utilisation du symbole \ ne désigne un
caractère spécial. Les deux lignes suivantes sont équivalentes :
s = "C:\\Users\\Dupre\\exemple.txt"
s = r"C:\Users\Dupre\exemple.txt"
Sans la lettre "r", tous les \ doivent être doublés, dans le cas contraire, Python peut avoir des effets
indésirables selon le caractère qui suit ce symbole.
2. Types et variables du langage Python 30
\” guillemet
\0 apostrophe
\n passage à la ligne
\\ insertion du symbole \
\% pourcentage, ce symbole est aussi un caractère spécial
\t tabulation
\r
retour à la ligne, peu usité, il a surtout son importance lorsqu’on passe d’un système Windows à
Linux car Windows l’ajoute automatiquement à tous ses fichiers textes
Table 2.1 : Liste des extra-caractères les plus couramment utilisés à l’intérieur d’une chaîne de caractères (voir
page http:// docs.python.org/ reference/ lexical_analysis.html#strings).
Une chaîne de caractères est semblable à un tableau et certains opérateurs qui s’appliquent aux tableaux
s’appliquent également aux chaînes de caractères. Ceux-ci sont regroupés dans la table 2.2. La fonction
str permet de convertir un nombre, un tableau, un objet (voir chapitre 4) en chaîne de caractères afin de
pouvoir l’afficher. La fonction len retourne la longueur de la chaîne de caractères.
x = 5.567
s = str (x)
print type(s), s # <type ’str’> 5.567
print len(s) # affiche 5
Où s est une chaîne de caractères, fonction est le nom de l’opération que l’on veut appliquer à s,
res est le résultat de cette manipulation.
La table 2.3 (page 31) présente une liste non exhaustive des fonctions disponibles dont un exemple d’uti-
lisation suit. Cette syntaxe variable.fonction(arguments) est celle des classes 4 .
4. voir chapitre 4
2. Types et variables du langage Python 31
st = "langage python"
st = st.upper () # mise en lettres majuscules
i = st.find ("PYTHON") # on cherche "PYTHON" dans st
print i # affiche 8
print st.count ("PYTHON") # affiche 1
print st.count ("PYTHON", 9) # affiche 0
L’exemple suivant permet de retourner une chaîne de caractères contenant plusieurs éléments séparés par
”; ”. La chaîne ”un; deux; trois” doit devenir ”trois; deux; un”. On utilise pour cela les fonctionnalités split
et join (voir table 2.3). L’exemple utilise également la fonctionnalité reverse des listes qui seront décrites
plus loin dans ce chapitre. Il faut simplement retenir qu’une liste est un tableau. reverse retourne le
tableau.
s = "un;deux;trois"
mots = s.split (";") # mots est égal à [’un’, ’deux’, ’trois’]
mots.reverse () # retourne la liste, mots devient égal à
# [’trois’, ’deux’, ’un’]
s2 = ";".join (mots) # concaténation des éléments de mots séparés par ";"
print s2 # affiche trois;deux;un
Python offre une manière plus concise de former une chaîne de caractères à l’aide de plusieurs types
d’informations en évitant la conversion explicite de ces informations (fonction str) et leur concaténation.
Il est particulièrement intéressant pour les nombres réels qu’il est possible d’écrire en imposant un nombre
2. Types et variables du langage Python 32
c1 est un code choisi parmi ceux de la table 2.4 (page 33). Il indique le format dans lequel la variable
v1 devra être transcrite. Il en est de même pour le code c2 associé à la variable v2. Les codes insérés
dans la chaîne de caractères seront remplacés par les variables citées entre parenthèses après le
symbole % suivant la fin de la chaîne de caractères. Il doit y avoir autant de codes que de variables,
qui peuvent aussi être des constantes.
La seconde affectation de la variable res propose une solution équivalente à la première en utilisant
l’opérateur de concaténation +. Les deux solutions sont équivalentes, tout dépend des préférences de celui
qui écrit le programme.
un nombre réel 5.500000 et un entier 7, une chaîne de caractères,
un réel d’abord converti en chaîne de caractères 9.5
un nombre réel 5.5 et un entier 7, une chaîne de caractères,
un réel d’abord converti en chaîne de caractères 9.5
La première option permet néanmoins un formatage plus précis des nombres réels en imposant par exemple
un nombre défini de décimal. Le format est le suivant :
où n est le nombre de chiffres total et d est le nombre de décimales, f désigne un format réel indiqué
par la présence du symbole %
x = 0.123456789
print x # affiche 0.123456789
print "%1.2f" % x # affiche 0.12
print "%06.2f" % x # affiche 000.12
Il existe d’autres formats regroupés dans la table 2.4. L’aide reste encore le meilleur réflexe car le langage
Python est susceptible d’évoluer et d’ajouter de nouveaux formats.
2.2.5 T-uple
Un T-uple apparaît comme une liste d’objets comprise entre parenthèses et séparés par des virgules. Leur
création reprend le même format :
2. Types et variables du langage Python 33
d entier relatif
e nombre réel au format exponentiel
f nombre réel au format décimal
g nombre réel, format décimal ou exponentiel si la puissance est trop grande ou trop petite
s chaîne de caractères
Table 2.4 : Liste non exhaustive des codes utilisés pour formater des informations dans une chaîne de caractères
(voir page http:// docs.python.org/ library/ stdtypes.html ).
Ces objets sont des vecteurs d’objets. Il est possible d’effectuer les opérations regroupées dans la table 2.5.
Etant donné que les chaînes de caractères sont également des tableaux, ces opérations reprennent en partie
celles de la table 2.2.
x in s vrai si x est un des éléments de s
x not in s réciproque de la ligne précédente
s+t concaténation de s et t
s∗n concatène n copies de s les unes à la suite des autres
s[i] retourne le ième élément de s
s[i : j] retourne un T-uple contenant une copie des éléments de s d’indices i à j exclu.
retourne un T-uple contenant une copie des éléments de s dont les indices sont compris
s[i : j : k]
entre i et j exclu, ces indices sont espacés de k : i, i + k, i + 2k, i + 3k, ...
len(s) nombre d’éléments de s
plus petit élément de s, résultat difficile à prévoir lorsque les types des éléments sont diffé-
min(s)
rents
max(s) plus grand élément de s
sum(s) retourne la somme de tous les éléments
Table 2.5 : Opérations disponibles sur les T-uples, on suppose que s et t sont des T-uples, x est quant à lui
quelconque.
a = (4,5)
a [0] = 3 # déclenche une erreur d’exécution
Pour changer cet élément, il est possible de s’y prendre de la manière suivante :
a = (4,5)
a = (3,) + a[1:2] # crée un T-uple d’un élément concaténé
# avec la partie inchangée de a
2. Types et variables du langage Python 34
Le langage Python offre la possibilité de créer ses propres types immuables 5 mais ils seront définis à partir
des types immuables présentés jusqu’ici.
2.3.1 Liste
2.3.1.1 Définition et fonctions
Une liste apparaît comme une succession d’objets compris entre crochets et séparés par des virgules. Leur
création reprend le même format :
Ces objets sont des listes chaînées d’autres objets de type quelconque (immuable ou modifiable). Il est
possible d’effectuer les opérations regroupées dans la table 2.6. Ces opérations reprennent celles des T-uples
(voir table 2.5) et incluent d’autres fonctionnalités puisque les listes sont modifiables (voir table 2.6). Il
est donc possible d’insérer, de supprimer des éléments, de les trier. La syntaxe des opérations sur les listes
est similaire à celle des opérations qui s’appliquent sur les chaînes de caractères, elles sont présentées par
la table 2.7.
5. voir le paragraphe 4.7
2. Types et variables du langage Python 35
2.3.1.2 Exemples
Pour classer les objets contenus par la liste mais selon un ordre différent, il faut définir une fonction qui
détermine un ordre entre deux éléments de la liste. C’est la fonction compare de l’exemple suivant.
def compare (x,y): # crée une fonction
if x > y : return -1 # qui retourne -1 si x<y,
elif x == y : return 0 # 0 si x == y
else : return 1 # 1 si x < y
L’exemple suivant illustre un exemple dans lequel on essaye d’accéder à l’indice d’un élément qui n’existe
pas dans la liste :
x = [9,0,3,5,0]
print x.index (1) # cherche la position de l’élément 1
Comme cet élément n’existe pas, on déclenche ce qu’on appelle une exception qui se traduit par l’affichage
d’un message d’erreur. Le message indique le nom de l’exception générée (ValueError) ainsi qu’un message
d’information permettant en règle générale de connaître l’événement qui en est la cause.
Traceback (most recent call last):
File "c:/temp/temp", line 2, in -toplevel-
print x.index(1)
ValueError: list.index(x): x not in list
x = [9,0,3,5,0]
try: print x.index(1)
except ValueError: print "1 n’est pas présent dans la liste x"
else: print "trouvé"
Les listes sont souvent utilisées dans des boucles ou notamment par l’intermédiaire de la fonction range.
Cette fonction retourne une liste d’entiers.
Retourne une liste incluant tous les entiers compris entre debut et fin exclu. Si le paramètre facultatif
marche est renseigné, la liste contient tous les entiers n compris debut et fin exclu et tels que n − debut
soit un multiple de marche.
Exemple :
print range (0,10,2) # affiche [0, 2, 4, 6, 8]
La fonction xrange est d’un usage équivalent à range. Elle permet de parcourir une liste d’entiers sans la
créer vraiment. Elle est plus rapide.
print xrange (0,10,2) # affiche xrange(0,10,2)
Cette fonction est souvent utilisée lors de boucles 6 pour parcourir tous les éléments d’un T-uple, d’une
liste, d’un dictionnaire... Le programme suivant permet par exemple de calculer la somme de tous les
entiers impairs compris entre 1 et 20 exclu.
s = 0
for n in range (1,20,2) : # ce programme est équivalent à
s += n # s = sum (range(1,20,2))
Il est possible aussi de ne pas se servir des indices comme intermédiaires pour accéder aux éléments d’une
liste quand il s’agit d’effectuer un même traitement pour tous les éléments de la liste x.
x = ["un", 1, "deux", 2]
for el in x :
print "la liste inclut : ", el
L’instruction for el in x : se traduit littéralement par : pour tous les éléments de la liste, faire... Ce
programme a pour résultat :
la liste inclut : un
la liste inclut : 1
la liste inclut : deux
la liste inclut : 2
Il existe également des notations abrégées lorsqu’on cherche à construire une liste à partir d’une autre.
Le programme suivant construit la liste des entiers de 1 à 5 à partir du résultat retourné par la fonction
range.
y = list ()
for i in range(0,5) : y.append (i+1)
print y # affiche [1,2,3,4,5]
Le langage Python offre la possibilité de résumer cette écriture en une seule ligne. Cette syntaxe sera
reprise au paragraphe 3.3.2.2.
y = [ i+1 for i in range (0,5)]
print y # affiche [1,2,3,4,5]
Cette définition de liste peut également inclure des tests ou des boucles imbriquées.
y = [ i for i in range(0,5) if i % 2 == 0] # sélection les éléments pairs
print y # affiche [0,2,4]
z = [ i+j for i in range(0,5) \
for j in range(0,5)] # construit tous les nombres i+j possibles
print z # affiche [0, 1, 2, 3, 4, 1, 2, 3, 4, 5, 2,
# 3, 4, 5, 6, 3, 4, 5, 6, 7, 4, 5, 6, 7, 8]
On suppose qu’on dispose de n séquences d’éléments (T-uple, liste), toutes de longueur l. La fonction zip
permet de construire une liste de T-uples qui est la juxtaposition de toutes ces séquences. Le iième T-uple
de la liste résultante contiendra tous les iième éléments des séquences juxtaposées. Si les longueurs des
séquences sont différentes, la liste résultante aura même taille que la plus courte des séquences.
a = (1,0,7,0,0,0)
b = [2,2,3,5,5,5]
c = [ "un", "deux", "trois", "quatre" ]
d = zip (a,b,c)
print d # affiche [(1, 2, ’un’), (0, 2, ’deux’),
# (7, 3, ’trois’), (0, 5, ’quatre’)]
Il arrive fréquemment de constuire une chaîne de caractères petits bouts par petits bouts comme le montre
le premier exemple ci-dessous. Cette construction peut s’avérer très lente lorsque le résultat est long. Dans
ce cas, il est nettement plus rapide d’ajouter chaque morceau dans une liste puis de les concaténer en une
seule fois grâce à la méthode join.
s = "" s = []
while <condition> : s += ... while <condition> : s.append ( ... )
s = "".join (s)
2.3.2 Copie
A l’inverse des objets de type immuable, une affectation ne signifie pas une copie. Afin d’éviter certaines
opérations superflues et parfois coûteuses en temps de traitement, on doit distinguer la variable de son
contenu. Une variable désigne une liste avec un mot (ou identificateur), une affection permet de créer un
second mot pour désigner la même liste. Il est alors équivalent de faire des opérations avec le premier mot
ou le second comme le montre l’exemple suivant avec les listes l et l2.
2. Types et variables du langage Python 39
l = [4,5,6]
l2 = l
print l # affiche [4,5,6]
print l2 # affiche [4,5,6]
l2 [1] = "modif"
print l # affiche [4, ’modif’, 6]
print l2 # affiche [4, ’modif’, 6]
Dans cet exemple, il n’est pas utile de créer une seconde variable, dans le suivant, cela permet quelques
raccourcis.
l = [[0,1], [2,3]]
l1 = l [0]
l1 [0] = "modif" # ligne équivalente à : l [0][0] = "modif"
Par conséquent, lorsqu’on affecte une liste à une variable, celle-ci n’est pas recopiée, la liste reçoit seulement
un nom de variable. L’affectation est en fait l’association d’un nom avec un objet (voir paragraphe 4.6).
Pour copier une liste, il faut utiliser la fonction copy du module copy 7 . Ce point sera rappelé au para-
graphe 4.6.3 (page 100).
import copy
l = [4,5,6]
l2 = copy.copy(l)
print l # affiche [4,5,6]
print l2 # affiche [4,5,6]
l2 [1] = "modif"
print l # affiche [4,5,6]
print l2 # affiche [4, ’modif’, 6]
L’opérateur == permet de savoir si deux listes sont égales même si l’une est une copie de l’autre. Le
mot-clé is permet de vérifier si deux variables font référence à la même liste ou si l’une est une copie de
l’autre comme le montre l’exemple suivant :
import copy
l = [1,2,3]
l2 = copy.copy (l)
l3 = l
7. Le module copy est une extension interne. C’est une librairie de fonctions dont la fonction copy. Cette syntaxe sera
vue au chapitre 6.
2. Types et variables du langage Python 40
La fonction deepcopy est plus lente à exécuter car elle prend en compte les références récursives comme
celles de l’exemple suivant où deux listes se contiennent l’une l’autre.
l = [1,"a"]
ll = [l,3] # ll contient l
l [0] = ll # l contient ll
print l # affiche [[[...], 3], ’a’]
print ll # affiche [[[...], ’a’], 3]
import copy
z = copy.deepcopy (l)
print z # affiche [[[...], 3], ’a’]
2.3.3 Dictionnaire
Les dictionnaires sont des tableaux plus souples que les listes. Une liste référence les éléments en leur
donnant une position : la liste associe à chaque élément une position entière comprise entre 0 et n − 1
si n est la longueur de la liste. Un dictionnaire permet d’associer à un élément autre chose qu’une position
entière : ce peut être un entier, un réel, une chaîne de caractères, un T-uple contenant des objets immuables.
D’une manière générale, un dictionnaire associe à une valeur ce qu’on appelle une clé de type immuable.
Cette clé permettra de retrouver la valeur associée.
L’avantage principal des dictionnaires est la recherche optimisée des clés. Par exemple, on recense les
noms des employés d’une entreprise dans une liste. On souhaite ensuite savoir si une personne ayant un
nom précisé à l’avance appartient à cette liste. Il faudra alors parcourir la liste jusqu’à trouver ce nom ou
parcourir toute la liste si jamais celui-ci ne s’y trouve pas 8 . Dans le cas d’un dictionnaire, cette recherche
du nom sera beaucoup plus rapide à écrire et à exécuter.
Un dictionnaire apparaît comme une succession de couples d’objets comprise entre accolades et séparés
par des virgules. La clé et sa valeur sont séparées par le symbole :. Leur création reprend le même format :
x = { "cle1":"valeur1", "cle2":"valeur2" }
y = { } # crée un dictionnaire vide
z = dict () # crée aussi un dictionnaire vide
Les indices ne sont plus entiers mais des chaînes de caractères pour cet exemple. Pour associer la valeur à
la clé "cle1", il suffit d’écrire :
print x ["cle1"]
La plupart des fonctions disponibles pour les listes sont interdites pour les dictionnaires comme la conca-
ténation ou l’opération de multiplication (∗). Il n’existe plus non plus d’indices entiers pour repérer les
éléments, le seul repère est leur clé. La table 2.8 dresse la liste des opérations simples sur les dictionnaires
tandis que la table 2.9 dresse la liste des méthodes plus complexes.
Contrairement à une liste, un dictionnaire ne peut être trié car sa structure interne est optimisée pour
effectuer des recherches rapides parmi les éléments. On peut aussi se demander quel est l’intérêt de la
méthode popitem qui retourne un élément puis le supprime alors qu’il existe le mot-clé del. Cette méthode
est simplement plus rapide car elle choisit à chaque fois l’élément le moins coûteux à supprimer, surtout
lorsque le dictionnaire est volumineux.
Les itérateurs sont des objets qui permettent de parcourir rapidement un dictionnaire, ils seront décrits en
détail au chapitre 4 sur les classes. Un exemple de leur utilisation est présenté dans le paragraphe suivant.
2.3.3.2 Exemples
Il n’est pas possible de trier un dictionnaire. L’exemple suivant permet néanmoins d’afficher tous les
éléments d’un dictionnaire selon un ordre croissant des clés. Ces exemples font appel aux paragraphes sur
les boucles (voir chapitre 3).
key = d.keys ()
key.sort ()
for k in key:
print k,d [k]
L’exemple suivant montre un exemple d’utilisation des itérateurs. Il s’agit de construire un dictionnaire
inversé pour lequel les valeurs seront les clés et réciproquement.
d = { "un":1, "zero":0, "deux":2, "trois":3, "quatre":4, "cinq":5, \
"six":6, "sept":1, "huit":8, "neuf":9, "dix":10 }
Pour être plus efficace, on peut remplacer la ligne for key, value in d.items() : par : for key, value in d.iteritems() :.
De cette manière, on parcourt les éléments du dictionnaire sans créer de liste intermédiaire. Il est équi-
valent d’utiliser l’une ou l’autre au sein d’une boucle même si le programme suivant montre une différence.
Il a pour résultat :
[(’trois’, 3), (’sept’, 1), (’neuf’, 9), (’six’, 6), (’zero’, 0),
(’un’, 1), (’dix’, 10), (’deux’, 2), (’huit’, 8), (’quatre’, 4),
(’cinq’, 5)]
2.3.3.3 Copie
A l’instar des listes (voir paragraphe 2.3.2), les dictionnaires sont des objets et une affectation n’est pas
équivalente à une copie comme le montre le programme suivant.
d = {4:4,5:5,6:6}
d2 = d
print d # affiche {4: 4, 5: 5, 6: 6}
print d2 # affiche {4: 4, 5: 5, 6: 6}
d2 [5] = "modif"
print d # affiche {4: 4, 5: ’modif’, 6: 6}
print d2 # affiche {4: 4, 5: ’modif’, 6: 6}
2. Types et variables du langage Python 43
Lorsqu’on affecte une liste à une variable, celle-ci n’est pas recopiée, la liste reçoit seulement un nom de
variable. L’affectation est en fait l’association d’un nom avec un objet (voir paragraphe 4.6). Pour copier
une liste, il faut utiliser la fonction copy du module copy.
d = {4:4,5:5,6:6}
import copy
d2 = copy.copy(l)
print d # affiche {4: 4, 5: 5, 6: 6}
print d2 # affiche {4: 4, 5: 5, 6: 6}
d2 [5] = "modif"
print d # affiche {4: 4, 5: 5, 6: 6}
print d2 # affiche {4: 4, 5: ’modif’, 6: 6}
Le mot-clé is a la même signification pour les dictionnaires que pour les listes, l’exemple du para-
graphe 2.3.2 (page 38) est aussi valable pour les dictionnaires. Il en est de même pour la remarque
concernant la fonction deepcopy. Cette fonction recopie les listes et les dictionnaires.
Ce paragraphe concerne davantage des utilisateurs avertis qui souhaitent malgré tout utiliser des clés de
type modifiable. Dans l’exemple qui suit, la clé d’un dictionnaire est également un dictionnaire et cela
provoque une erreur. Il en serait de même si la variable k utilisée comme clé était une liste.
k = { 1:1}
Traceback (most recent call last):
d = { }
File "cledict.py", line 3, in <module>
d [k] = 0
d [k] = 0
TypeError: dict objects are unhashable
Cela ne veut pas dire qu’il faille renoncer à utiliser un dictionnaire ou une liste comme clé. La fonction id
permet d’obtenir un entier qui identifie de manière unique tout objet. Le code suivant est parfaitement
correct.
k = { 1:1}
d = { }
d [id (k)] = 0
Toutefois, ce n’est pas parce que deux dictionnaires auront des contenus identiques que leurs identifiants
retournés par la fonction id seront égaux. C’est ce qui explique l’erreur que provoque la dernière ligne du
programme suivant.
k = {1:1}
d = { }
d [id (k)] = 0
b = k
print d [id(b)] # affiche bien zéro
c = {1:1}
print d [id(c)] # provoque une erreur car même si k et c ont des contenus égaux,
# ils sont distincts, la clé id(c) n’existe pas dans d
Il existe un cas où on peut se passer de la fonction id mais il inclut la notion de classe définie au chapitre 4.
L’exemple suivant utilise directement l’instance k comme clé. En affichant le dictionnaire d, on vérifie que
la clé est liée au résultat de l’instruction id(k) même si ce n’est pas la clé.
class A : pass
k = A ()
d = { }
d [k] = 0
2. Types et variables du langage Python 44
La fonction hex convertit un entier en notation hexadécimale. Les nombres affichés changent à chaque
exécution. Pour conclure, ce dernier exemple montre comment se passer de la fonction id dans le cas d’une
clé de type dictionnaire.
class A (dict):
def __hash__(self):
return id(self)
k = A ()
k ["t"]= 4
d = { }
d [k] = 0
print d # affiche {{’t’: 4}: 0}
2.4 Extensions
2.4.1 Mot-clé print, repr, conversion en chaîne de caractères
Le mot-clé print est déjà apparu dans les exemples présentés ci-dessus, il permet d’afficher une ou plusieurs
variables préalablement définies, séparées par des virgules. Les paragraphes qui suivent donnent quelques
exemples d’utilisation. La fonction print permet d’afficher n’importe quelle variable ou objet à l’écran,
cet affichage suppose la conversion de cette variable ou objet en une chaîne de caractères. Deux fonctions
permettent d’effectuer cette étape sans toutefois afficher le résultat à l’écran.
La fonction str (voir paragraphe 2.2.4.2) permet de convertir toute variable en chaîne de caractères. Il
existe cependant une autre fonction repr, qui effectue cette conversion. Dans ce cas, le résultat peut être
interprété par la fonction eval (voir paragraphe 2.4.2) qui se charge de la conversion inverse. Pour les types
simples comme ceux présentés dans ce chapitre, ces deux fonctions retournent des résultats identiques.
Pour l’exemple, x désigne n’importe quelle variable.
x == eval (repr(x)) # est toujours vrai (True)
x == eval (str (x)) # n’est pas toujours vrai
32 // 9 = 3
32 & 9 = 0
32 | 9 = 41
32 and 9 = 9
32 or 9 = 32
32 << 9 = 16384
32 >> 9 = 0
Le programme va créer une chaîne de caractères pour chacune des opérations et celle-ci sera évaluée
grâce à la fonction eval comme si c’était une expression numérique. Il faut bien sûr que les variables que
l’expression mentionne existent durant son évaluation.
Certaines variables - des chaînes des caractères - existent déjà avant même la première instruction. Elles
contiennent différentes informations concernant l’environnement dans lequel est exécuté le programme
Python :
Ce module contient tous les éléments présents dès le début d’un programme Python,
__builtins__ il contient entre autres les types présentés dans ce chapitre et des fonctions simples
comme range.
C’est une chaîne commentant le fichier, c’est une chaîne de caractères insérée aux
__doc__
premières lignes du fichiers et souvent entourée des symboles ””” (voir chapitre 6).
__file__ Contient le nom du fichier où est écrit ce programme.
__name__ Contient le nom du module.
La fonction dir est également pratique pour afficher toutes les fonctions d’un module. L’instruction
dir(sys) affiche la liste des fonctions du module sys (voir chapitre 6).
Remarque 2.21 : fonction dir
La fonction dir appelée sans argument donne la liste des fonctions et variables définies à cet endroit du
programme. Ce résultat peut varier selon qu’on se trouver dans une fonction, une méthode de classe ou à
l’extérieur du programme. L’instruction dir([]) donne la liste des méthodes qui s’appliquent à une liste.
De la même manière, la fonction type retourne une information concernant le type d’une variable.
x = 3
print x, type(x) # affiche 3 <type ’int’>
x = 3.5
print x, type(x) # affiche 3.5 <type ’float’>
x = 5 # affecte 5 à x
y = 6 # affecte 6 à y
x,y = 5,6 # affecte en une seule instruction 5 à x et 6 à y
Cette particularité reviendra lorsque les fonctions seront décrites puisqu’il est possible qu’une fonction
retourne plusieurs résultats comme la fonction divmod illustrée par le programme suivant.
x,y = divmod (17,5)
print x,y # affiche 3 2
print "17 / 5 = 5 * ", x, " + ",y # affiche 17 / 5 = 5 * 3 + 2
Le langage Python offre la possibilité d’effectuer plusieurs affectations sur la même ligne. Dans l’exemple
qui suit, le couple (5, 5) est affecté à la variable point, puis le couple x, y reçoit les deux valeurs du T-uple
point.
2.4.6 Ensemble
Le langage Python définit également ce qu’on appelle un ensemble. Il est défini par les classes set de type
modifiable et la classe frozenset de type immuable. Ils n’acceptent que des types identiques et offrent la
plupart des opérations liées aux ensembles comme l’intersection, l’union. D’un usage moins fréquent, ils
ne seront pas plus détaillés 10 .
print set ( (1,2,3) ) & set ( (2,3,5) )
# construit l’intersection qui est set([2, 3])
Ce chapitre a présenté les différents types de variables définis par le langage Python pour manipuler des
données ainsi que les opérations possibles avec ces types de données. Le chapitre suivant va présenter les
tests, les boucles et les fonctions qui permettent de réaliser la plupart des programmes informatiques.
Avec les variables, les boucles et les fonctions, on connaît suffisamment d’éléments pour écrire des pro-
grammes. Le plus difficile n’est pas forcément de les comprendre mais plutôt d’arriver à découper un
algorithme complexe en utilisant ces briques élémentaires. C’est l’objectif des chapitres centrés autour des
exercices. Toutefois, même si ce chapitre présente les composants élémentaires du langage, l’aisance qu’on
peut acquérir en programmation vient à la fois de la connaissance du langage mais aussi de la connaissance
d’algorithmes standards comme celui du tri ou d’une recherche dichotomique. C’est cette connaissance tout
autant que la maîtrise d’un langage de programmation qui constitue l’expérience en programmation.
enchaînement des instructions les unes à la suite des autres : passage d’une instruction à
la séquence
la suivante
passage d’une instruction à une autre qui n’est pas forcément la suivante (c’est une rupture
le saut
de séquence)
le test choix entre deux instructions
Le saut n’apparaît plus de manière explicite dans les langages évolués car il est une source fréquente
d’erreurs. Il intervient dorénavant de manière implicite au travers des boucles qui combinent un saut et
un test comme le montre l’exemple suivant :
Version avec boucles :
initialisation de la variable moy à 0
faire pour i allant de 1 à N
moy reçoit moy + ni
moy reçoit moy / N
Tout programme peut se résumer à ces trois concepts. Chaque langage les met en place avec sa propre
syntaxe et parfois quelques nuances mais il est souvent facile de les reconnaître même dans des langages
inconnus. Le calcul d’une somme décrit plus haut et écrit en Python correspond à l’exemple suivant :
3. Syntaxe du langage Python (boucles, tests, fonctions) 48
moy = 0
for i in range(1,N+1): # de 1 à N+1 exclu --> de 1 à N inclus
moy += n [i]
moy /= N
Le premier élément de cette syntaxe est constitué de ses mots-clés (for et in) (voir également table 3.1)
et des symboles (=, +=, /=, [, ], (, ), :) (voir table 3.2) La fonction iskeyword du module keyword 1 permet
de savoir si un mot-clé donné fait partie du langage Python.
import keyword
print keyword.iskeyword("for") # affiche True
print keyword.iskeyword("until") # affiche False
3.2 Tests
3.2.1 Définition et syntaxe
La clause else est facultative. Lorsque la condition condition1 est fausse et qu’il n’y a aucune
instruction à exécuter dans ce cas, la clause else est inutile. La syntaxe du test devient :
if condition1 :
instruction1
instruction2
...
S’il est nécessaire d’enchaîner plusieurs tests d’affilée, il est possible de condenser l’écriture avec le
mot-clé elif :
if condition1 :
instruction1
instruction2
...
elif condition2 :
instruction3
instruction4
...
elif condition3 :
instruction5
instruction6
...
else :
instruction7
instruction8
...
Une ligne est mal indentée (print "le nombre est négatif"). Elle ne devrait être exécutée que si la
condition x > 0 n’est pas vérifiée. Le fait qu’elle soit alignée avec les premières instructions du programme
fait que son exécution n’a plus rien à voir avec cette condition. La programme répond de manière erronée :
Dans certains cas, l’interpréteur Python ne sait pas à quel bloc attacher une instruction, c’est le cas de
l’exemple suivant, la même ligne a été décalée de deux espaces, ce qui est différent de la ligne qui précède
et de la ligne qui suit.
x = 1
if x > 0 :
signe = 1
print "le nombre est positif"
else :
signe = -1
print "le nombre est négatif" # ligne mal indentée
print "signe = ", signe
Le second cas d’écriture condensée concerne les comparaisons enchaînées. Le test if 3 < x and x < 5 : instruction
peut être condensé par if 3 < x < 5 : instruction. Il est ainsi possible de juxtaposer autant de comparai-
sons que nécessaire : if 3 < x < y < 5 : instruction.
Le mot-clé in permet également de condenser certains tests lorsque la variable à tester est entière.
if x == 1 or x == 6 or x == 50 : peut être résumé simplement par if x in (1, 6, 50) :.
3.2.5 Exemple
L’exemple suivant associe à la variable signe le signe de la variable x.
x = -5 x = -5
if x < 0 : if x < 0 : signe = -1
signe = -1 Son écriture condensée elif x == 0 : signe = 0
elif x == 0 : lorsqu’il n’y a qu’une else : signe = 1
signe = 0 instruction à exécuter :
else :
signe = 1
Le programme suivant saisit une ligne au clavier et dit si c’est "oui" ou "non" qui a été saisi.
s = raw_input ("dites oui : ") # voir remarque suivante
if s == "oui" or s [0:1] == "o" or s [0:1] == "O" or s == "1" :
print "oui"
else : print "non"
Figure 3.1 : Fenêtre apparaissant lors de l’exécution du programme page 51 proposant une version graphique de la
fonction raw_input.
Dans ce cas précis, si l’instruction pass est oubliée, l’interpréteur Python génère l’erreur suivante :
File "nopass.py", line 6
else :
^
IndentationError: expected an indented block
3.3 Boucles
Le langage Python propose deux types de boucles. La boucle while suit scrupuleusement la définition 3.8.
La boucle for est une boucle while déguisée (voir paragraphe 3.3.2), elle propose une écriture simplifiée
pour répéter la même séquence d’instructions pour tous les éléments d’un ensemble.
3. Syntaxe du langage Python (boucles, tests, fonctions) 53
Où cond est une condition qui détermine la poursuite de la répétition des instructions incluses dans
la boucle. Tant que celle-ci est vraie, les instructions 1 à n sont exécutées.
Tout comme les tests, la remarque 3.4 concernant l’indentation reste vraie. Le décalage des lignes d’un
cran vers la droite par rapport à l’instruction while permet de les inclure dans la boucle comme le montre
l’exemple suivant.
n = 0 à l’intérieur 0
while n < 3: à l’intérieur 1
print "à l’intérieur ", n Résultat : à l’intérieur 2
n += 1 à l’extérieur 3
print "à l’extérieur ", n
3.3.1.2 Condition
Les conditions suivent la même syntaxe que celles définies lors des tests (voir paragraphes 3.2.2 et 3.2.3).
A moins d’inclure l’instruction break 2 qui permet de sortir prématurément d’une boucle, la condition
qui régit cette boucle doit nécessairement être modifiée à l’intérieur de celle-ci. Dans le cas contraire, on
appelle une telle boucle une boucle infinie puisqu’il est impossible d’en sortir. L’exemple suivant contient
une boucle infinie car le symbole = est manquant dans la dernière instruction. La variable n n’est jamais
modifiée et la condition n < 3 toujours vraie.
n = 0
while n < 3 :
print n
n + 1 # n n’est jamais modifié, l’instruction correcte serait n += 1
Où x est un élément de l’ensemble set. Les instructions 1 à n sont exécutées pour chaque élément x de
l’ensemble set. Cet ensemble peut être une chaîne de caractères, un T-uple, une liste, un dictionnaire
ou tout autre type incluant des itérateurs qui sont présentés au chapitre 4 concernant les classes
(page 90).
Tout comme les tests, la remarque 3.4 concernant l’indentation reste vraie. L’exemple suivant affiche tous
les éléments d’un T-uple à l’aide d’une boucle for.
t = (1,2,3,4)
for x in t: # affiche les nombres 1,2,3,4
print x # chacun sur une ligne différente
Lors de l’affichage d’un dictionnaire, les éléments n’apparaissent pas triés ni dans l’ordre dans lequel ils
y ont été insérés. L’exemple suivant montre comment afficher les clés et valeurs d’un dictionnaire dans
l’ordre croissant des clés.
d = { 1:2, 3:4, 5:6, 7:-1, 8:-2 }
print d # affiche le dictionnaire {8: -2, 1: 2, 3: 4, 5: 6, 7: -1}
k = d.keys ()
print k # affiche les clés [8, 1, 3, 5, 7]
k.sort ()
print k # affiche les clés triées [1, 3, 5, 7, 8]
for x in k: # affiche les éléments du dictionnaire
print x,":",d [x] # triés par clés croissantes
Le langage Python propose néanmoins la fonction sorted qui réduit l’exemple suivant en trois lignes :
d = { 1:2, 3:4, 5:6, 7:-1, 8:-2 }
for x in sorted(d): # pour les clés dans l’ordre croissant
print x,":",d [x]
La boucle la plus répandue est celle qui parcourt des indices entiers compris entre 0 et n − 1. On utilise
pour cela la boucle for et la fonction range ou xrange comme dans l’exemple qui suit. La différence entre
ces deux fonctions a déjà été présentée au paragraphe 2.3.1.3 (page 37).
sum = 0
N = 10
for n in range(0,N): # ou for n in xrange(0,N): (plus rapide)
sum += n # additionne tous les entiers compris entre 0 et N-1
Le paragraphe 2.3.1.4 a montré comment le mot-clé for peut être utilisé pour simplifier la création d’une
liste à partir d’une autre. La syntaxe suit le schéma suivant :
Où expression est une expression numérique incluant ou non x, la variable de la boucle, ensemble
est un ensemble d’éléments, T-uple, liste, dictionnaire.
Cette syntaxe permet de résumer en une ligne la création de la séquence y du programme suivant.
y = list ()
for i in range(0,5) :
y.append (i+1)
print y # affiche [1,2,3,4,5]
Un autre exemple de cette syntaxe réduite a été présenté au paragraphe 2.3.1.4 page 37. Cette écriture
condensée est bien souvent plus lisible même si tout dépend des préférences de celui qui programme.
3.3.2.3 Itérateurs
Toute boucle for peut s’appliquer sur un objet muni d’un itérateur tels que les chaînes de caractères,
T-uples, les listes, les dictionnaires.
d = ["un", "deux", "trois"]
for x in d:
print x # affichage de tous les éléments de d
Cette syntaxe réduite a déjà été introduite pour les listes et les dictionnaires au chapitre précédent. Il existe
une version équivalente avec la boucle while utilisant de façon explicite les itérateurs. Il peut être utile de
lire le chapitre suivant sur les classes et le chapitre 5 sur les exceptions avant de revenir sur la suite de cette
section qui n’est de toutes façons pas essentielle. L’exemple précédent est convertible en une boucle while
en faisant apparaître explicitement les itérateurs (voir paragraphe 4.4.2). Un itérateur est un objet qui
permet de parcourir aisément un ensemble. La fonction it = iter(e) permet d’obtenir un itérateur it sur
l’ensemble e. L’appel à l’instruction it.next() parcourt du premier élément jusqu’au dernier en retournant
la valeur de chacun d’entre eux. Lorsqu’il n’existe plus d’élément, l’exception StopIteration est déclenchée
(voir paragraphe 5). Il suffit de l’intercepter pour mettre fin au parcours.
Jusqu’à présent, la boucle for n’a été utilisée qu’avec une seule variable de boucle, comme dans l’exemple
suivant où on parcourt une liste de T-uple pour les afficher.
3. Syntaxe du langage Python (boucles, tests, fonctions) 56
Lorsque les éléments d’un ensemble sont des T-uples, des listes ou des dictionnaires composés de taille
fixe, il est possible d’utiliser une notation qui rappelle les affectations multiples (voir paragraphe 2.4.4).
L’exemple précédent devient dans ce cas :
d = [ (1,0,0), (0,1,0), (0,0,1) ]
for x,y,z in d: print x,y,z
Cette écriture n’est valable que parce que chaque élément de la liste d est un T-uple composé de trois
nombres. Lorsqu’un des éléments est de taille différente à celle des autres, comme dans l’exemple suivant,
une erreur survient.
d = [ (1,0,0), (0,1,0,6), (0,0,1) ] # un élément de taille quatre
for x,y,z in d: print x,y,z
Cette syntaxe est très pratique associée à la fonction zip (voir paragraphe 2.3.1.5). Il est alors possible de
parcourir plusieurs séquences (T-uple, liste, dictionnaire) simultanément.
a = range(0,5) 0 est le carré de 0
b = [x**2 for x in a] 1 est le carré de 1
for x,y in zip (a,b): 4 est le carré de 2
print y, " est le carré de ", x 9 est le carré de 3
# affichage à droite 16 est le carré de 4
Il existe peu de cas où la boucle while s’écrit sur une ligne car elle inclut nécessairement une instruction
permettant de modifier la condition d’arrêt.
d = ["un", "deux", "trois"]
i = 0
while d [i] != "trois" : i += 1
print "trois a pour position ", i
Pour certains éléments d’une boucle, lorsqu’il n’est pas nécessaire d’exécuter toutes les instructions, il est
possible de passer directement à l’élément suivant ou l’itération suivante. Le programme suivant utilise la
crible d’Eratosthène 3 pour dénicher tous les nombres premiers compris entre 1 et 99.
3. Le crible d’Eratosthène est un algorithme permettant de déterminer les nombres premiers. Pour un nombre premier p,
il paraît plus simple de considérer tous les entiers de p − 1 à 1 pour savoir si l’un d’eux divise p. C’est ce qu’on fait lorsqu’on
3. Syntaxe du langage Python (boucles, tests, fonctions) 57
d = dict ()
for i in range(1,100): # d [i] est vrai si i est un nombre premier
d [i] = True # au début, comme on ne sait pas, on suppose
# que tous les nombres sont premiers
for i in range(2,100):
# si d [i] est faux,
if not d [i]: continue # les multiples de i ont déjà été cochés
# et peut passer à l’entier suivant
for j in range (2,100):
if i*j < 100:
d [i*j] = False # d [i*j] est faux pour tous les multiples de i
# inférieurs à 100
print "liste des nombres premiers"
for i in d:
if d [i]: print i
for i in range(2,100):
if d [i]:
for j in range (2,100):
if i*j < 100 :
d [i*j] = False
Le mot-clé continue évite de trop nombreuses indentations et rend les programmes plus lisibles.
Lors de l’écriture d’une boucle while, il n’est pas toujours adéquat de résumer en une seule condition
toutes les raisons pour lesquelles il est nécessaire d’arrêter l’exécution de cette boucle. De même, pour une
boucle for, il n’est pas toujours utile de visiter tous les éléments de l’ensemble à parcourir. C’est le cas
par exemple lorsqu’on recherche un élément, une fois qu’il a été trouvé, il n’est pas nécessaire d’aller plus
loin. L’instruction break permet de quitter l’exécution d’une boucle.
l = [6,7,5,4,3]
n = 0
c = 5
for x in l:
if x == c: break # l’élément a été trouvé, on sort de la boucle
n += 1 # si l’élément a été trouvé, cette instruction
# n’est pas exécutée
print "l’élément ",c, " est en position ",
print n # affiche l’élément 5 est en position 2
Si deux boucles sont imbriquées, l’instruction break ne sort que de la boucle dans laquelle elle est insérée.
L’exemple suivant vérifie si un entier est la somme des carrés de deux entiers compris entre 1 et 20.
set = range (1,21)
n = 53
doit vérifier le caractère premier d’un seul nombre. Pour plusieurs nombres à la fois, le crible d’Eratosthène est plus efficace :
au lieu de s’intéresser aux diviseurs, on s’intéresse aux multiples d’un nombre. Pour un nombre i, on sait que 2i, 3i, ... ne
sont pas premiers. On les raye de la liste. On continue avec i + 1, 2(i + 1), 3(i + 1)...
3. Syntaxe du langage Python (boucles, tests, fonctions) 58
for x in set:
for y in set:
c = x*x + y*y
if c == n: break
if c == n: break # cette seconde instruction break est nécessaire
# pour sortir de la seconde boucle
# lorsque la solution a été trouvée
if c == n:
# le symbole \ permet de passer à la ligne sans changer d’instruction
print n, " est la somme des carrés de deux entiers :", \
x, "*", x, "+", y, "*", y, "=", n
else:
print n, " n’est pas la somme des carrés de deux entiers"
Le programme affiche :
53 est la somme des carrés de deux entiers : 2 * 2 + 7 * 7 = 53
Le mot-clé else existe aussi pour les boucles et s’utilise en association avec le mot-clé break. L’instruction
else est placée à la fin d’une boucle, indentée au même niveau que for ou while. Les lignes qui suivent
le mot-clé else ne sont exécutées que si aucune instruction break n’a été rencontrée dans le corps de la
boucle. On reprend l’exemple du paragraphe précédent. On recherche cette fois-ci la valeur 1 qui ne se
trouve pas dans la liste L. Les lignes suivant le test if x == c ne seront jamais exécutées au contraire de
la dernière.
L = [6,7,5,4,3]
n = 0
c = 1
for x in L :
if x == c :
print "l’élément ", c, " est en position ", n
break
n += 1
else:
print "aucun élément ", c, " trouvé" # affiche aucun élément 1 trouvé
Les lignes dépendant de la clause else seront exécutées dans tous les cas où l’exécution de la boucle n’est
pas interrompue par une instruction break ou une instruction return 4 ou par la levée d’une exception 5 .
En parcourant la liste en se servant des indices, il est possible de supprimer une partie de cette liste. Il
faut néanmoins faire attention à ce que le code ne produise pas d’erreur comme c’est le cas pour le suivant.
La boucle for parcourt la liste range(0, len(li)) qui n’est pas modifiée en même temps que l’instruction
del li[i : i + 2].
li = range (0,10)
print li # affiche [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for i in range (0, len (li)):
if i == 5 :
del li [i:i+2]
print li [i] # affiche successivement 0, 1, 2, 3, 4, 7, 8, 9 et
# produit une erreur
print li
Le programme suivant marche parfaitement puisque cette fois-ci la boucle parcourt la liste li. En revanche,
pour la suppression d’une partie de celle-ci, il est nécessaire de conserver en mémoire l’indice de l’élément
visité. C’est le rôle de la variable i.
li = range (0,10)
print li # affiche [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
i = 0
for t in li :
if i == 5 : del li [i:i+2]
i = i+1
print t # affiche successivement 0, 1, 2, 3, 4, 5, 8, 9
print li # affiche [0, 1, 2, 3, 4, 7, 8, 9]
Le langage Python offre la possibilité de supprimer des éléments d’une liste alors même qu’on est en train
de la parcourir. Le programme qui suit ne marche pas puisque l’instruction del i ne supprime pas un
élément de la liste mais l’identificateur i qui prendra une nouvelle valeur lors du passage suivant dans la
boucle.
li = range (0,10)
print li # affiche [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for i in li :
if i == 5 : del i
print li # affiche [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
On pourrait construire des exemples similaires dans le cadre de l’ajout d’un élément à la liste. Il est en
règle générale déconseillé de modifier une liste, un dictionnaire pendant qu’on le parcourt. Malgré tout, si
cela s’avérait indispensable, il convient de faire plus attention dans ce genre de situations.
3.4 Fonctions
Les fonctions sont des petits programmes qui effectuent des tâches plus précises que le programme entier.
On peut effectivement écrire un programme sans fonction mais ils sont en général illisibles. Utiliser des
fonctions implique de découper un algorithme en tâches élémentaires. Le programme final est ainsi plus
facile à comprendre. Un autre avantage est de pouvoir plus facilement isoler une erreur s’il s’en produit
une : il suffit de tester une à une les fonctions pour déterminer laquelle retourne un mauvais résultat.
L’avantage le plus important intervient lorsqu’on doit effectuer la même chose à deux endroits différentes
d’un programme : une seule fonction suffit et elle sera appelée à ces deux endroits 6 .
Lorsqu’on écrit ses premiers programme, on écrit souvent des fonctions plutôt longues avant de s’apercevoir
que certains parties sont identiques ailleurs. On extrait donc la partie répétée pour en faire une fonction.
6. Pour les utilisateurs experts : en langage Python, les fonctions sont également des variables, elles ont un identificateur
et une valeur qui est dans ce cas un morceau de code. Cette précision explique certaines syntaxes du chapitre 8 sur les
interfaces graphiques ou celle introduite en fin de chapitre au paragraphe 3.7.10.
3. Syntaxe du langage Python (boucles, tests, fonctions) 60
Avec l’habitude, on finit par écrire des fonctions plus petites et réutilisables.
fonction_nom est le nom de la fonction, il suit les mêmes règles que le nom des variables. par_1 à
par_n sont les noms des paramètres et res_1 à res_n sont les résultats retournés par la fonction.
Les instructions associées à une fonction doivent être indentées par rapport au mot-clé def.
S’il n’y a aucun résultat, l’instruction return est facultative ou peut être utilisée seule sans être suivie par
une valeur ou une variable. Cette instruction peut apparaître plusieurs fois dans le code de la fonction
mais une seule d’entre elles sera exécutée. A partir de ce moment, toute autre instruction de la fonction
sera ignorée. Pour exécuter une fonction ainsi définie, il suffit de suivre la syntaxe suivante :
Où fonction_nom est le nom de la fonction, valeur_1 à valeur_n sont les noms des paramètres,
x_1 à x_n reçoivent les résultats retournés par la fonction. Cette affectation est facultative. Si on
ne souhaite pas conserver les résultats, on peut donc appeler la fonction comme suit :
fonction_nom (valeur_1, valeur_2, ..., valeur_n)
Lorsqu’on commence à programmer, il arrive parfois qu’on confonde le rôle des mots-clés print et return.
Dans ce cas, il faut se reporter à la remarque 10.3 page 225.
3.4.2 Exemple
Le programme suivant utilise deux fonctions. La première convertit des coordonnées cartésiennes en coor-
données polaires. Elle prend deux réels en paramètres et retourne deux autres réels. La seconde fonction
affiche les résultats de la première pour tout couple de valeurs (x, y). Elle ne retourne aucun résultat.
import math
def coordonnees_polaires (x,y):
rho = math.sqrt(x*x+y*y) # calcul la racine carrée de x*x+y*y
theta = math.atan2 (y,x) # calcule l’arc tangente de y/x en tenant
# compte des signes de x et y
return rho, theta
affichage (1,1)
affichage (0.5,1)
affichage (-0.5,1)
affichage (-0.5,-1)
affichage (0.5,-1)
Où fonction_nom est le nom de la fonction. param_1 à param_n sont les noms des paramètres,
valeur_2 à valeur_n sont les valeurs par défaut des paramètres param_2 à param_n. La seule
contrainte lors de cette définition est que si une valeur par défaut est spécifiée pour un paramètre,
alors tous ceux qui suivent devront eux aussi avoir une valeur par défaut.
Exemple :
def commander_carte_orange (nom, prenom, paiement = "carte", nombre = 1, zone = 2):
print "nom : ", nom
print "prénom : ", prenom
print "paiement : ", paiement
print "nombre : ", nombre
print "zone :", zone
Il est impossible qu’un paramètre sans valeur par défaut associée se situe après un paramètre dont une
valeur par défaut est précisée. Le programme suivant ne pourra être exécuté.
def commander_carte_orange (nom, prenom, paiement = "carte", nombre = 1, zone):
print "nom : ", nom
# ...
L’explication provient du fait que la valeur par défaut est une liste qui n’est pas recréée à chaque appel :
c’est la même liste à chaque fois que la fonction est appelée sans paramètre. Pour remédier à cela, il
faudrait écrire :
3. Syntaxe du langage Python (boucles, tests, fonctions) 62
import copy
def fonction (l = [0,0]) :
l = copy.copy (l)
l [0] += 1
return l
L’exercice 12.4.7 (page 323) propose un exemple plus complet, voire retors.
Où fonction_nom est le nom de la fonction, param_1 à param_n sont les noms des paramètres,
valeur_1 à valeur_n sont les valeurs que reçoivent ces paramètres. Avec cette syntaxe, l’ordre
d’écriture n’importe pas. La valeur valeur_i sera toujours attribuée à param_i. Les variables x_1 à
x_n reçoivent les résultats retournés par la fonction. L’ordre des résultats ne peut pas être changé.
S’il y a plusieurs résultats retournés, il est impossible de choisir lesquels conserver : soit tous, soit
aucun.
Exemple :
def identite (nom, prenom):
print "nom : ", nom, " prénom : ", prenom
Cette possibilité est intéressante surtout lorsqu’il y a de nombreux paramètres par défaut et que seule la
valeur d’un des derniers paramètres doit être changée.
def commander_carte_orange (paiement = "carte", nombre = 1, zone = 2):
print "paiement : ", paiement
print "nombre : ", nombre
print "zone :", zone
Le petit programme précédent est syntaxiquement correct mais son exécution génère une erreur parce que
la seconde définition de la fonction fonction efface la première.
Traceback (most recent call last):
File "cours4.py", line 7, in ?
print fonction (5,6)
TypeError: fonction() takes exactly 3 arguments (2 given)
3.4.6 Commentaires
Le langage Python propose une fonction help qui retourne pour chaque fonction un commentaire ou mode
d’emploi qui indique comment se servir de cette fonction. L’exemple suivant affiche le commentaire associé
à la fonction round.
>>> help (round)
round(...)
round(number[, ndigits]) -> floating point number
Lorsqu’on utilise cette fonction help sur la fonction coordonnees_polaires définie dans l’exemple du
paragraphe précédent 3.4.2, le message affiché n’est pas des plus explicites.
>>> help (coordonnees_polaires)
coordonnees_polaires(x, y)
Pour changer ce message, il suffit d’ajouter en première ligne du code de la fonction une chaîne de carac-
tères.
import math
def coordonnees_polaires (x,y):
"""convertit des coordonnées cartésiennes en coordonnées polaires
(x,y) --> (pho,theta)"""
rho = math.sqrt(x*x+y*y)
theta = math.atan2 (y,x)
return rho, theta
help (coordonnees_polaires)
coordonnees_polaires(x, y)
convertit des coordonnées cartésiennes en coordonnées polaires
(x,y) --> (pho,theta)
Il est conseillé d’écrire ce commentaire pour toute nouvelle fonction avant même que son corps ne soit
écrit. L’expérience montre qu’on oublie souvent de l’écrire après.
3. Syntaxe du langage Python (boucles, tests, fonctions) 64
def somme_n_premier_terme(n,liste):
"""calcul la somme des n premiers termes d’une liste"""
somme = 0
for i in liste:
somme += i
n -= 1 # modification de n (type immuable)
if n <= 0: break
liste[0] = 0 # modification de liste (type modifiable)
return somme
l = [1,2,3,4]
nb = 3
print "avant la fonction ",nb,l # affiche avant la fonction 3 [1, 2, 3, 4]
s = somme_n_premier_terme (nb,l)
print "après la fonction ",nb,l # affiche après la fonction 3 [0, 2, 3, 4]
print "somme : ", s # affiche somme : 6
liste = [0,1,2]
print liste # affiche [0,1,2]
fonction (liste)
print liste # affiche [0,1,2]
Il faut considérer dans ce programme que la fonction fonction reçoit un paramètre appelé liste mais
utilise tout de suite cet identificateur pour l’associer à un contenu différent. L’identificateur liste est en
quelque sorte passé du statut de paramètre à celui de variable locale. La fonction associe une valeur à
liste - ici, une liste vide - sans toucher à la valeur que cet identificateur désignait précédemment.
Le programme qui suit est différent du précédent mais produit les mêmes effets. Ceci s’explique par le fait
que le mot-clé del ne supprime pas le contenu d’une variable mais seulement son identificateur. Le langage
3. Syntaxe du langage Python (boucles, tests, fonctions) 65
Python détecte ensuite qu’un objet n’est plus désigné par aucun identificateur pour le supprimer. Cette
remarque est à rapprocher de celles du paragraphe 4.6.
def fonction (liste):
del liste
liste = [0,1,2]
print liste # affiche [0,1,2]
fonction (liste)
print liste # affiche [0,1,2]
Le programme qui suit permet cette fois-ci de vider la liste liste passée en paramètre à la fonction
fonction. La seule instruction de cette fonction modifie vraiment le contenu désigné par l’identificateur
liste et cela se vérifie après l’exécution de cette fonction.
def fonction (liste):
del liste[0:len(liste)] # on peut aussi écrire : liste[:] = []
liste = [0,1,2]
print liste # affiche [0,1,2]
fonction (liste)
print liste # affiche []
La fonction récursive la plus fréquemment citée en exemple est la fonction factorielle. Celle-ci met en
évidence les deux composantes d’une fonction récursive, la récursion proprement dite et la condition
d’arrêt.
def factorielle(n):
if n == 0 : return 1
else : return n * factorielle(n-1)
La dernière ligne de la fonction factorielle est la récursion tandis que la précédente est la condition
d’arrêt, sans laquelle la fonction ne cesserait de s’appeler, empêchant le programme de terminer son
exécution. Si celle-ci est mal spécifiée ou absente, l’interpréteur Python affiche une suite ininterrompue de
messages. Python n’autorise pas plus de 1000 appels récursifs : factorielle(999) provoque nécessairement
une erreur d’exécution même si la condition d’arrêt est bien spécifiée.
Traceback (most recent call last):
File "fact.py", line 5, in <module>
factorielle(999)
File "fact.py", line 3, in factorielle
else : return n * factorielle(n-1)
File "fact.py", line 3, in factorielle
else : return n * factorielle(n-1)
...
La liste des messages d’erreurs est aussi longue qu’il y a eu d’appels à la fonction récursive. Dans ce cas,
il faut transformer cette fonction en une fonction non récursive équivalente, ce qui est toujours possible.
def factorielle_non_recursive (n) :
r = 1
for i in range (2, n+1) :
r *= i
return r
3. Syntaxe du langage Python (boucles, tests, fonctions) 66
3.4.9 Portée
3.4.9.1 Portée des variables, des paramètres
Lorsqu’on définit une variable, elle n’est pas utilisable partout dans le programme. Par exemple, elle n’est
pas utilisable avant d’avoir été déclarée au moyen d’une affectation. Le court programme suivant déclenche
une erreur.
print x # déclenche une erreur
Il est également impossible d’utiliser une variable à l’extérieur d’une fonction où elle a été déclarée.
Plusieurs fonctions peuvent ainsi utiliser le même nom de variable sans qu’à aucun moment, il n’y ait
confusion. Le programme suivant déclenche une erreur identique à celle reproduite ci-dessus.
def portee_variable(x):
var = x
print var
portee_variable(3)
print var # déclenche une erreur car var est déclarée dans
# la fonction portee_variable
Une variable n’a donc d’existence que dans la fonction dans laquelle elle est déclarée. On appelle ce type
de variable une variable locale. Par défaut, toute variable utilisée dans une fonction est une variable locale.
Par opposition aux variables locales, on définit les variables globales qui sont déclarées à l’extérieur de
toute fonction.
L’exemple suivant mélange variable locale et variable globale. L’identificateur n est utilisé à la fois pour
désigner une variable globale égale à 1 et une variable locale égale à 1. A l’intérieur de la fonction, n désigne
la variable locale égale à 2. A l’extérieur de la fonction, n désigne la variable globale égale à 1.
n = 1 # déclaration d’une variable globale
def locale_globale():
n = 2 # déclaration d’une variable locale
print n # affiche le contenu de la variable locale
print n # affiche 1
locale_globale() # affiche 2
print n # affiche 1
3. Syntaxe du langage Python (boucles, tests, fonctions) 67
Il est possible de faire référence aux variables globales dans une fonction par l’intermédiaire du mot-clé
global. Celui-ci indique à la fonction que l’identificateur n n’est plus une variable locale mais désigne une
variable globale déjà déclarée.
n = 1 # déclaration d’une variable globale
def locale_globale():
global n # cette ligne indique que n désigne la variable globale
n = 2 # change le contenu de la variable globale
print n # affiche le contenu de la variable globale
print n # affiche 1
locale_globale() # affiche 2
print n # affiche 2
Cette possibilité est à éviter le plus possible car on peut considérer que locale_globale est en fait une
fonction avec un paramètre caché. La fonction locale_globale n’est plus indépendante des autres fonctions
puisqu’elle modifie une des données du programme.
Le langage Python considère les fonctions également comme des variables d’un type particulier. La portée
des fonctions obéit aux mêmes règles que celles des variables. Une fonction ne peut être appelée que si elle
a été définie avant son appel.
print type(factorielle) # affiche <type ’function’>
Comme il est possible de déclarer des variables locales, il est également possible de définir des fonctions
locales ou fonctions imbriquées. Une fonction locale n’est appelable qu’à l’intérieur de la fonction dans
laquelle elle est définie. Dans l’exemple suivant, la fonction affiche_pair inclut une fonction locale qui
n’est appelable que par cette fonction affiche_pair.
def affiche_pair():
def fonction_locale(i): # fonction locale ou imbriquée
if i % 2 == 0 : return True
else : return False
for i in range(0,10):
if fonction_locale(i):
print i
affiche_pair()
fonction_locale(5) # l’appel à cette fonction locale
# déclenche une erreur d’exécution
A l’intérieur d’une fonction locale, le mot-clé global désigne toujours les variables globales du programme
et non les variables de la fonction dans laquelle cette sous-fonction est définie.
Où fonction est un nom de fonction, param_1 à param_n sont des paramètres de la fonction, liste est
le nom de la liste qui doit recevoir la liste des valeurs seules envoyées à la fonction et qui suivent les
paramètres (plus précisément, c’est un tuple), dictionnaire reçoit la liste des couples (identificateur,
valeur).
Où fonction est un nom de fonction, valeur_1 à valeur_n sont les valeurs associées aux para-
mètres param_1 à param_n, liste_valeur_1 à liste_valeur_p formeront la liste liste, les couples
nom_1 : v_1 à nom_q : v_q formeront le dictionnaire dictionnaire.
Exemple :
def fonction(p,*l,**d):
print "p = ",p
print "liste (tuple) l :", l
print "dictionnaire d :", d
Ce programme affiche :
p = 1
liste l : (2, 3)
dictionnaire d : {’a’: 5, ’b’: 6}
A l’instar des paramètres par défaut, la seule contrainte de cette écriture est la nécessité de respecter
l’ordre dans lequel les informations doivent apparaître. Lors de l’appel, les valeurs sans précision de nom
de paramètre seront placées dans une liste (ici le tuple l). Les valeurs associées à un nom de paramètre
seront placées dans un dictionnaire (ici d). Les valeurs par défaut sont obligatoirement placées après les
paramètres non nommés explicitement.
Une fonction qui accepte des paramètres en nombre variable peut à son tour appeler une autre fonction
acceptant des paramètres en nombre variable. Il faut pour cela se servir du symbole ∗ afin de transmettre
à fonction les valeurs reçues par fonction2.
def fonction(p,*l,**d):
print "p = ",p
print "liste l :", l
print "dictionnaire d :", d
fonction2 (1,2,3,a=5,b=6)
3. Syntaxe du langage Python (boucles, tests, fonctions) 69
Le programme affiche :
p = 1
liste l : (2, 3, 4)
dictionnaire d : {’a’: 5, ’c’: 5, ’b’: 6}
nom_fonction est le nom de la fonction, param_1 à param_n sont les paramètres de cette fonction
(ils peuvent également recevoir des valeurs par défaut), expression est l’expression retournée par la
fonction.
L’exemple suivant utilise cette écriture pour définir la fonction min retournant le plus petit entre deux
nombres positifs.
min = lambda x,y : (abs (x+y) - abs (x-y))/2
for a in fonction_yield(2):
print a # affiche tous les éléments que retourne la
# fonction fonction_yield, elle simule la liste
3. Syntaxe du langage Python (boucles, tests, fonctions) 70
# [0,1]
print "-----------------------------------------------"
for a in fonction_yield(3):
print a # nouvel appel, l’exécution reprend
# au début de la fonction,
# affiche tous les éléments que retourne la
# fonction fonction_yield, elle simule la liste
# [0,1,2]
Le programme affiche tous les entiers compris entre 0 et 4 inclus ainsi que le texte ”yield 1” ou ”yield 2”
selon l’instruction yield qui a retourné le résultat. Lorsque la fonction a finalement terminé son exécution,
le prochain appel agit comme si c’était la première fois qu’on l’appelait.
yield 1
0
yield 2
1
-----------------------------------------------
yield 1
0
yield 1
1
yield 2
2
x = 5
def y () :
return None
print callable (x) # affiche False car x est une variable
print callable (y) # affiche True car y est une fonction
Cette fonction a déjà été abordée lors des paragraphes 2.4.1 (paragraphe 2.4.2, page 44). Un exemple a
déjà été présenté page 44. Elle évalue toute chaîne de caractères contenant une expression écrite avec la
syntaxe du langage Python. Cette expression peut utiliser toute variable ou toute fonction accessible au
moment où est appelée la fonction eval.
x = 3
y = 4
print eval ("x*x+y*y+2*x*y") # affiche 49
print (x+y)**2 # affiche 49
Si l’expression envoyée à la fonction eval inclut une variable non définie, l’interpréteur Python génère une
erreur comme le montre l’exemple suivant.
x = 3
y = 4
print eval ("x*x+y*y+2*x*y+z")
L’erreur se produit dans une chaîne de caractères traduite en programme informatique, c’est pourquoi
l’interpréteur ne peut pas situer l’erreur dans un fichier. L’erreur ne se produit dans aucun fichier, cette
chaîne de caractères pourrait être définie dans un autre.
Plus complète que la fonction eval, la fonction compile permet d’ajouter une ou plusieurs fonctions au
programme, celle-ci étant définie par une chaîne de caractères. Le code est d’abord compilé (fonction
compile) puis incorporé au programme (fonction exec) comme le montre l’exemple suivant.
import math
str = """def coordonnees_polaires (x,y):
rho = math.sqrt(x*x+y*y)
theta = math.atan2 (y,x)
return rho, theta""" # fonction définie par une chaîne de caractères
La fonction compile prend en fait trois arguments. Le premier est la chaîne de caractères contenant le code
à compiler. Le second paramètre ("" dans l’exemple) contient un nom de fichier dans lequel seront placées
les erreurs de compilation. Le troisième paramètre est une chaîne de caractères à choisir parmi "exec" ou
"eval". Selon ce choix, ce sera la fonction exec ou eval qui devra être utilisée pour agréger le résultat de la
fonction compile au programme. L’exemple suivant donne un exemple d’utilisation de la fonction compile
avec la fonction eval.
import math
str = """math.sqrt(x*x+y*y)""" # expression définie par une chaîne de caractères
3.5 Indentation
L’indentation est synonyme de décalage. Pour toute boucle, test, fonction, et plus tard, toute définition
de classe, le fait d’indenter ou décaler les lignes permet de définir une dépendance d’un bloc de lignes par
rapport à un autre. Les lignes indentées par rapport à une boucle for dépendent de celle-ci puisqu’elle
seront exécutées à chaque passage dans la boucle. Les lignes indentées par rapport au mot-clé def sont
considérées comme faisant partie du corps de la fonction. La remarque 3.4 page 49 précise l’erreur que
l’interpréteur Python retourne en cas de mauvaise indentation.
Contrairement à d’autres langages comme le C ou PERL, Python n’utilise pas de délimiteurs pour regrou-
per les lignes. L’indentation, souvent présentée comme un moyen de rendre les programmes plus lisibles,
est ici intégrée à la syntaxe du langage. Il n’y a pas non plus de délimiteurs entre deux instructions autre
qu’un passage à la ligne. Le caractère \ placé à la fin d’une ligne permet de continuer l’écriture d’une
instruction à la ligne suivante.
3. Syntaxe du langage Python (boucles, tests, fonctions) 72
Elle peut aider à simplifier l’écriture lorsque plusieurs listes sont impliquées. Ici encore, les deux dernières
lignes sont équivalentes.
def addition (x,y) : return x + y
l = [0,3,4,4,5,6]
m = [1,3,4,5,6,8]
print [ addition (l [i], m [i]) for i in range (0, len (l)) ]
print map (addition, l, m) # affiche [1, 6, 8, 9, 11, 14]
Il est possible de substituer None à la fonction f pour obtenir l’équivalent de la fonction zip.
print map (None, l,m) # affiche [(0, 1), (3, 3), (4, 4), (4, 5), (5, 6), (6, 8)]
print zip (l,m) # affiche [(0, 1), (3, 3), (4, 4), (4, 5), (5, 6), (6, 8)]
Comme pour les dictionnaires, la fonction sorted permet de parcourir les éléments d’une liste de façon
ordonnée. Les deux exemples qui suivent sont presque équivalents. Dans le second, la liste l demeure
inchangée alors qu’elle est triée dans le premier programme.
l = [ 4, 5, 3, -6, 7, 9] l = [ 4, 5, 3, -6, 7, 9]
l.sort ()
for n in l : for n in sorted (l) :
print n print n
La fonction enumerate permet d’éviter l’emploi de la fonction range ou xrange lorsqu’on souhaite parcourir
une liste alors que l’indice et l’élément sont nécessaires.
l = [ 4, 5, 3, -6, 7, 9] l = [ 4, 5, 3, -6, 7, 9]
for i in xrange (0, len (l)) : for i,v in enumerate (l) :
print i, l [i] print i, v
3. Syntaxe du langage Python (boucles, tests, fonctions) 73
Néanmoins, la fonction recherche peut être adaptée pour rechercher la première valeur trouvée parmi deux
possibles, ce qu’il n’est pas possible de faire avec la méthode index.
def recherche (li, c,d) :
for i,v in enumerate (li) :
if v in [c,d] : return i
return -1
li = [ 45, 32, 43, 56 ]
print recherche (li, 43, 32) # affiche 1
Ce court exemple montre qu’il est utile de connaître quelques algorithmes simples. Ils ne sont pas forcément
les plus efficaces mais ils aident à construire des programmes plus rapidement et à obtenir un résultat. S’il
est positif alors il sera toujours temps de se pencher sur l’optimisation du programme pour qu’il soit plus
efficace.
Pour éviter la boucle, on peut utiliser l’astuce décrite au paragraphe 3.7.7 qui consiste à former une liste
de couples avec la position initiale :
k = [ (v,i) for i,v in enumerate (li) ]
m = max (k) [1]
Il arrive fréquemment qu’on ne doive pas chercher le maximum d’une liste mais le maximum au sein
d’un sous-ensemble de cette liste. Dans l’exemple qui suit, on cherche le tuple dont la première valeur est
maximale et dont la seconde valeur est égale à 1.
li = [ (0,0), (434,0), (43,1), (6436,1), (5,0) ]
m = -1 # -1 car le premier élément peut ne pas faire partie du sous-ensemble
for i in range (0, len (li)) :
if li [i][1] == 0 and (m == -1 or li [m][0] < li [i][0]) : m = i
3. Syntaxe du langage Python (boucles, tests, fonctions) 75
li = range (0,100,2) 10
print recherche_dichotomique (li, 48) # affiche 24
print recherche_dichotomique (li, 49) # affiche -1
L’opération inverse :
3.7.5 Somme
Le calcul d’une somme fait toujours intervenir une boucle car le langage Python ne peut faire des additions
qu’avec deux nombres. Le schéma est toujours le même : initialisation et boucle.
li = [ 0, 434, 43, 6456 ]
s = 0 # initialisation
for l in li : # boucle
s += l # addition
Ce code est équivalent à l’instruction sum(li). Dans ce cas où la somme intègre le résultat d’une fonction
et non les éléments d’une liste, il faudrait écrire :
def fonction (x) : return x*x
s = 0
for l in li : s += fonction (l)
Et ces deux lignes pourraient être résu- s = sum ( [fonction (l) for l in li] )
mées en une seule grâce à l’une de ces
deux instructions. s = sum ( map (fonction, li) )
3. Syntaxe du langage Python (boucles, tests, fonctions) 76
L’avantage de la solution avec la boucle est qu’elle évite la création d’une liste intermédiaire, c’est un point
à prendre en compte si la liste sur laquelle opère la somme est volumineuse. Il peut être intéressant de
regarder l’exercice 12.1.9 page 295.
3.7.6 Tri
Le tri est une opération fréquente. On n’a pas toujours le temps de programmer le tri le plus efficace comme
un tri quicksort et un tri plus simple suffit la plupart du temps. Le tri suivant consiste à recherche le plus
petit élément puis à échanger sa place avec le premier élément du tableau du tableau. On recommence la
même procédure à partir de la seconde position, puis la troisième et ainsi de suite jusqu’à la fin du tableau.
La méthode sort tri également une liste mais selon un algorithme plus efficace que celui-ci dont la logique
est présentée par l’exercice 10.5.1, page 230. Ceci explique pourquoi on hésite toujours à programmer un
tri quitte à avoir recours à une astuce telle que celle présentées au paragraphe suivant.
La liste [p[1] for p in pos] correspond à l’ensemble des positions initiales des éléments de la liste tab. Les
deux lignes qui suivent permettent d’en déduire la position de chaque élément dans l’ensemble trié. ordre[i]
est le rang de l’élément d’indice i avant le tri.
ordre = range (0, len (pos))
for i in xrange (0, len (pos)) : ordre [pos [i][1]] = i
3.7.8 Comptage
On souhaite ici compter le nombre d’occurrences de chaque élément d’un tableau. Par exemple, on pourrait
connaître par ce moyen la popularité d’un mot dans un discours politique ou l’étendue du vocabulaire
utilisé. L’exemple suivant compte les mots d’une liste de mots.
li = ["un", "deux", "un", "trois"]
d = { }
for l in li :
if l not in d : d [l] = 1
else : d [l] += 1
print d # affiche {’un’: 2, ’trois’: 1, ’deux’: 1}
3. Syntaxe du langage Python (boucles, tests, fonctions) 77
La structure la plus appropriée ici est un dictionnaire puisqu’on cherche à associer une valeur à un élément
d’une liste qui peut être de tout type. Si la liste contient des éléments de type modifiable comme une
liste, il faudrait convertir ceux-ci en un type immuable comme une chaîne de caractères. L’exemple suivant
illustre ce cas en comptant les occurrences des lignes d’une matrice.
mat = [ [1,1,1], [2,2,2], [1,1,1]]
d = { }
for l in mat :
k = str (l) # k = tuple (l) lorsque cela est possible
if k not in d : d [k] = 1
else : d [k] += 1
print d # affiche {’[1, 1, 1]’: 2, ’[2, 2, 2]’: 1}
On peut également vouloir non pas compter le nombre d’occurrence mais mémoriser les positions des
éléments tous identiques. On doit utiliser un dictionnaire de listes :
li = ["un", "deux", "un", "trois"]
d = { }
for i,v in enumerate (li) :
if v not in d : d [v] = [ i ]
else : d [v].append (i)
print d # affiche {’un’: [0, 2], ’trois’: [3], ’deux’: [1]}
Le programme suivant fait l’inverse. Il faut faire attention à la position des crochets et l’ordre des boucles
for.
nc = len (mat [0])
mat = [ [ lin [i * nc + j] for j in range (0, len (mat [i])) ] \
for i in range (0, len (mat)) ]
l = [0,1,2,3]
print l # affiche [0, 1, 2, 3]
Imaginons qu’une banque détienne un fichier contenant des informations sur ses clients et qu’il soit impos-
sible pour un client d’avoir accès directement à ces informations. Toutefois, il lui est en théorie possible
de demander à son banquier quelles sont les informations le concernant détenues par sa banque. Il est en
théorie également possible de les rectifier s’il estime qu’elles sont incorrectes. 1 .
On peut comparer cette banque à un objet qui possède des informations et des moyens permettant de lire
ces informations et de les modifier. Vu de l’extérieur, cette banque cache son fonctionnement interne et les
informations dont elle dispose, mais propose des services à ses utilisateurs.
On peut considérer la banque comme un objet au sens informatique. Ce terme désigne une entité possédant
des données et des méthodes permettant de les manipuler. Plus concrètement, une classe est un assemblage
de variables appelées attributs et de fonctions appelées méthodes. L’ensemble des propriétés associées aux
classes est regroupé sous la désignation de programmation objet.
Le corps d’une classe peut être vide, inclure des variables ou attributs, des fonctions ou méthodes.
Il est en tout cas indenté de façon à indiquer à l’interpréteur Python les lignes qui forment le corps
de la classe.
Les classes sont l’unique moyen en langage Python de définir de nouveaux types propres à celui qui
programme. Il n’existe pas de type "matrice" ou de type "graphe" en langage Python qui soit prédéfini 2 .
Il est néanmoins possible de les définir au moyen des classes. Une matrice est par exemple un objet qui
inclut les attributs suivant : le nombre de lignes, le nombre de colonnes, les coefficients de la matrice.
Cette matrice inclut aussi des méthodes comme des opérations entre deux matrices telles que l’addition,
la soustraction, la multiplication ou des opérations sur elle-même comme l’inversion, la transposition, la
diagonalisation.
Cette liste n’est pas exhaustive, elle illustre ce que peut être une classe "matrice" - représentation infor-
matique d’un objet "matrice" -, un type complexe incluant des informations de types variés (entier pour
les dimensions, réels pour les coefficients), et des méthodes propres à cet objet, capables de manipuler ces
1. Tout fichier contenant des données à caractère personnel doit être recensé auprès de la Commission nationale de
l’informatique et des libertés (CNIL). Cet organisme facilite l’accès à ces informations.
2. Les matrices sont définies dans une extension externe numpy (voir le chapitre 6).
4. Classes 80
informations.
Il est tout-à-fait possible de se passer des classes pour rédiger un programme informatique. Leur utilisation
améliore néanmoins sa présentation et la compréhension qu’on peut en avoir. Bien souvent, ceux qui passent
d’un langage uniquement fonctionnel à un langage objet ne font pas marche arrière.
La création d’une variable de type objet est identique à celle des types standards du langage Python :
elle passe par une simple affectation. On appelle aussi cl une instance de la classe nom_classe.
Cette syntaxe est identique à la syntaxe d’appel d’une fonction. La création d’une instance peut également
faire intervenir des paramètres (voir paragraphe 4.4). Le terme instance va de paire avec le terme classe :
L’exemple suivant permet de définir une classe vide. Le mot-clé pass permet de préciser que le corps de la
classe ne contient rien.
class classe_vide:
pass
Il est tout de même possible de définir une instance de la classe classe_vide simplement par l’instruction
suivante :
class classe_vide:
pass
cl = classe_vide ()
Pour savoir si une variable est une instance d’une classe donnée, il faut utiliser la fonction isinstance :
isinstance (cl,classe_vide) # affiche True
4.1.2 Méthodes
Ces données ou attributs sont définis plus loin. Les méthodes sont en fait des fonctions pour lesquelles
la liste des paramètres contient obligatoirement un paramètre explicite qui est l’instance de la classe à
4. Classes 81
laquelle cette méthode est associée. Ce paramètre est le moyen d’accéder aux données de la classe.
A part le premier paramètre qui doit de préférence s’appeler self, la syntaxe de définition d’une
méthode ressemble en tout point à celle d’une fonction. Le corps de la méthode est indenté par
rapport à la déclaration de la méthode, elle-même indentée par rapport à la déclaration de la classe.
L’appel d’une méthode nécessite tout d’abord la création d’une variable. Une fois cette variable créée,
il suffit d’ajouter le symbole "." pour exécuter la méthode. Le paramètre self est ici implicitement
remplacé par cl lors de l’appel.
L’exemple suivant simule le tirage de nombres aléatoires à partir d’une suite définie par récurrence un+1 =
(un ∗A) mod B où A et B sont des entiers très grands. Cette suite n’est pas aléatoire mais son comportement
imite celui d’une suite aléatoire. Le terme un est dans cet exemple contenu dans la variable globale rnd.
rnd = 42
class exemple_classe:
def methode1(self,n):
"""simule la génération d’un nombre aléatoire
compris entre 0 et n-1 inclus"""
global rnd
rnd = 397204094 * rnd % 2147483647
return int (rnd % n)
nb = exemple_classe ()
l = [ nb.methode1(100) for i in range(0,10) ]
print l # affiche [19, 46, 26, 88, 44, 56, 56, 26, 0, 8]
nb2 = exemple_classe ()
l2 = [ nb2.methode1(100) for i in range(0,10) ]
print l2 # affiche [46, 42, 89, 66, 48, 12, 61, 84, 71, 41]
Deux instances nb et nb2 de la classe exemple_classe sont créées, chacune d’elles est utilisée pour générer
aléatoirement dix nombres entiers compris entre 0 et 99 inclus. Les deux listes sont différentes puisque
l’instance nb2 utilise la variable globale rnd précédemment modifiée par l’appel nb.methode1(100).
Remarque 4.9 : méthodes et fonctions
Les méthodes sont des fonctions insérées à l’intérieur d’une classe. La syntaxe de la déclaration d’une
méthode est identique à celle d’une fonction en tenant compte du premier paramètre qui doit impérati-
vement être self. Les paramètres par défaut, l’ordre des paramètres, les nombres variables de paramètres
présentés au paragraphe 3.4 sont des extensions tout autant applicables aux méthodes qu’aux fonctions.
4. Classes 82
4.1.3 Attributs
Une classe permet en quelque sorte de regrouper ensemble des informations liées. Elles n’ont de sens
qu’ensemble et les méthodes manipulent ces données liées. C’est le cas pour un segment qui est toujours
défini par ces deux extrémités qui ne vont pas l’une sans l’autre.
Le paramètre self n’est pas un mot-clé même si le premier paramètre est le plus souvent appelé
self. Il désigne l’instance de la classe sur laquelle va s’appliquer la méthode. La déclaration d’une
méthode inclut toujours un paramètre self de sorte que self.nom_attribut désigne un attribut de
la classe. nom_attribut seul désignerait une variable locale sans aucun rapport avec un attribut
portant le même nom. Les attributs peuvent être déclarés à l’intérieur de n’importe quelle méthode,
voire à l’extérieur de la classe elle-même.
L’endroit où est déclaré un attribut a peu d’importance pourvu qu’il le soit avant sa première utilisation.
Dans l’exemple qui suit, la méthode methode1 utilise l’attribut rnd sans qu’il ait été créé.
class exemple_classe:
def methode1(self,n):
"""simule la génération d’un nombre aléatoire
compris entre 0 et n-1 inclus"""
self.rnd = 397204094 * self.rnd % 2147483647
return int (self.rnd % n)
nb = exemple_classe ()
l = [ nb.methode1(100) for i in range(0,10) ]
print l
Cet exemple déclenche donc une erreur (ou exception) signifiant que l’attribut rnd n’a pas été créé.
Pour remédier à ce problème, il existe plusieurs endroits où il est possible de créer l’attribut rnd. Il est
possible de créer l’attribut à l’intérieur de la méthode methode1. Mais le programme n’a plus le même
sens puisqu’à chaque appel de la méthode methode1, l’attribut rnd reçoit la valeur 42. La liste de nombres
aléatoires contient dix fois la même valeur.
class exemple_classe:
def methode1(self,n):
"""simule la génération d’un nombre aléatoire
compris entre 0 et n-1 inclus"""
self.rnd = 42 # déclaration à l’intérieur de la méthode,
# doit être précédé du mot-clé self
self.rnd = 397204094 * self.rnd % 2147483647
return int (self.rnd % n)
4. Classes 83
nb = exemple_classe ()
l = [ nb.methode1(100) for i in range(0,10) ]
print l # affiche [19, 19, 19, 19, 19, 19, 19, 19, 19, 19]
Il est possible de créer l’attribut rnd à l’extérieur de la classe. Cette écriture devrait toutefois être évitée
puisque la méthode methode1 ne peut pas être appelée sans que l’attribut rnd ait été ajouté.
class exemple_classe:
def methode1(self,n):
"""simule la génération d’un nombre aléatoire
compris entre 0 et n-1 inclus"""
self.rnd = 397204094 * self.rnd % 2147483647
return int (self.rnd % n)
nb = exemple_classe ()
nb.rnd = 42 # déclaration à l’extérieur de la classe,
# indispensable pour utiliser la méthode methode1
l = [ nb.methode1(100) for i in range(0,10) ]
print l # affiche [19, 46, 26, 88, 44, 56, 56, 26, 0, 8]
4.2 Constructeur
L’endroit le plus approprié pour déclarer un attribut est à l’intérieur d’une méthode appelée le constructeur.
S’il est défini, il est implicitement exécuté lors de la création de chaque instance. Le constructeur d’une
classe se présente comme une méthode et suit la même syntaxe à ceci près que son nom est imposé :
__init__. Hormis le premier paramètre, invariablement self, il n’existe pas de contrainte concernant la
liste des paramètres excepté que le constructeur ne doit pas retourner de résultat.
nom_classe est une classe, __init__ est son constructeur, sa syntaxe est la même que celle d’une
méthode sauf que le constructeur ne peut employer l’instruction return.
nom_classe est une classe, valeur_1 à valeur_n sont les valeurs associées aux paramètres param_1
à param_n du constructeur.
L’exemple suivant montre deux classes pour lesquelles un constructeur a été défini. La première n’ajoute
aucun paramètre, la création d’une instance ne nécessite pas de paramètre supplémentaire. La seconde
classe ajoute deux paramètres a et b. Lors de la création d’une instance de la classe classe2, il faut ajouter
deux valeurs.
class classe1:
def __init__(self):
# pas de paramètre supplémentaire
print "constructeur de la classe classe1"
self.n = 1 # ajout de l’attribut n
4. Classes 84
class classe2:
def __init__(self,a,b):
# deux paramètres supplémentaires
print "constructeur de la classe classe2"
self.n = (a+b)/2 # ajout de l’attribut n
nb = exemple_classe ()
l = [ nb.methode1(100) for i in range(0,10) ]
print l # affiche [19, 46, 26, 88, 44, 56, 56, 26, 0, 8]
nb2 = exemple_classe ()
l2 = [ nb2.methode1(100) for i in range(0,10) ]
print l2 # affiche [19, 46, 26, 88, 44, 56, 56, 26, 0, 8]
De la même manière qu’il existe un constructeur exécuté à chaque création d’instance, il existe un des-
tructeur exécuté à chaque destruction d’instance. Il suffit pour cela de redéfinir la méthode __del__. A
l’inverse d’autres langages comme le C++, cet opérateur est peu utilisé car le Python nettoie automati-
quement les objets qui ne sont plus utilisés ou plus référencés par une variable. Un exemple est donné
page 98.
nb = exemple_classe ()
print nb.__dict__ # affiche {’rnd’: 42}
4. Classes 85
Ce dictionnaire offre aussi la possibilité de tester si un attribut existe ou non. Dans un des exemples du
paragraphe précédent, l’attribut rnd était créé dans la méthode methode1, sa valeur était alors initialisée
à chaque appel et la fonction retournait sans cesse la même valeur. En testant l’existence de l’attribut
rnd, il est possible de le créer dans la méthode methode1 au premier appel sans que les appels suivants ne
réinitialisent sa valeur à 42.
class exemple_classe:
def methode1(self,n):
if "rnd" not in self.__dict__ : # l’attribut existe-t-il ?
self.rnd = 42 # création de l’attribut
self.__dict__ ["rnd"] = 42 # autre écriture possible
self.rnd = 397204094 * self.rnd % 2147483647
return int (self.rnd % n)
nb = exemple_classe ()
l = [ nb.methode1(100) for i in range(0,10) ]
print l # affiche [19, 46, 26, 88, 44, 56, 56, 26, 0, 8]
__module__ Contient le nom du module dans lequel est incluse la classe (voir chapitre 6).
Contient le nom de la classe de l’instance. Ce nom est précédé du nom du module suivi
__class__
d’un point.
__dict__ Contient la liste des attributs de l’instance (voir paragraphe 4.3.1).
__doc__ Contient un commentaire associé à la classe (voir paragraphe 4.3.3).
class classe_vide:
pass
cl = classe_vide ()
print cl.__module__ # affiche __main__
print cl.__class__ # affiche __main__.classe_vide ()
print cl.__dict__ # affiche {}
print cl.__doc__ # affiche None (voir paragraphe suivant)
print cl.__class__.__doc__ # affiche None
print cl.__class__.__dict__ # affiche {’__module__’: ’__main__’,
# ’__doc__’: None}
print cl.__class__.__name__ # affiche classe_vide
print cl.__class__.__bases__ # affiche ()
ainsi que chacun des commentaires qui leur sont associés. Ce commentaire est affecté à l’attribut implicite
__doc__. L’appel à la fonction help rassemble le commentaire de toutes les méthodes, le résultat suit le
programme ci-dessous.
class exemple_classe:
"""simule une suite de nombres aléatoires"""
def __init__ (self) :
"""constructeur : initialisation de la première valeur"""
self.rnd = 42
def methode1(self,n):
"""simule la génération d’un nombre aléatoire
compris entre 0 et n-1 inclus"""
self.rnd = 397204094 * self.rnd % 2147483647
return int (self.rnd % n)
nb = exemple_classe ()
help (exemple_classe) # appelle l’aide associée à la classe
class exemple_classe
| simule une suite de nombres aléatoires
|
| Methods defined here:
|
| __init__(self)
| constructeur : initialisation de la première valeur
|
| methode1(self, n)
| simule la génération d’un nombre aléatoire
| compris entre 0 et n-1 inclus
Pour obtenir seulement le commentaire associé à la classe, il suffit d’écrire l’une des trois lignes suivantes :
La fonction help permet d’accéder à l’aide associée à une fonction, une classe. Il existe des outils qui
permettent de collecter tous ces commentaires pour construire une documentation au format HTML à
l’aide d’outils comme pydoc 3 ou Doxygen 4 . Ces outils sont souvent assez simples d’utilisation.
La fonction dir permet aussi d’obtenir des informations sur la classe. Cette fonction appliquée à la classe
ou à une instance retourne l’ensemble de la liste des attributs et des méthodes. L’exemple suivant utilise la
fonction dir avant et après l’appel de la méthode meth. Etant donné que cette méthode ajoute un attribut,
la fonction dir retourne une liste plus longue après l’appel.
class essai_class:
def meth(self):
x = 6
self.y = 7
a = essai_class()
print dir (a) # affiche [’__doc__’, ’__module__’, ’meth’]
a.meth ()
print dir (a) # affiche [’__doc__’, ’__module__’, ’meth’, ’y’]
print dir (essai_class) # affiche [’__doc__’, ’__module__’, ’meth’]
La fonction dir appliquée à la classe elle-même retourne une liste qui inclut les méthodes et les attributs
déjà déclarés. Elle n’inclut pas ceux qui sont déclarés dans une méthode jamais exécutée jusqu’à présent.
3. voir http://pydoc.org/ ou http://docs.python.org/library/pydoc.html
4. http://www.stack.nl/~dimitri/doxygen/ associé à doxypy (http://code.foosel.org/doxypy).
4. Classes 87
class ensemble_element :
class element :
def __init__ (self) :
self.x, self.y, self.z = 0,0,0
f = ensemble_element ()
f.all [0].x, f.all [0].y, f.all [0].z = 4.5,1.5,1.5
b = f.barycentre ()
print b.x,b.y,b.z # affiche 1.5 0.5 0.5
Pour créer une instance de la classe element, il faut faire précéder son nom de la classe où elle est déclarée :
b = ensemble_element.element() comme c’est le cas dans la méthode barycentre par exemple.
L’addition n’est pas le seul symbole concerné, le langage Python permet de donner un sens à tous les
opérateurs numériques et d’autres reliés à des fonctions du langage comme len ou max.
4.4.1 Opérateurs
Le programme suivant contient une classe définissant un nombre complexe. La méthode ajoute définit ce
qu’est une addition entre nombres complexes.
class nombre_complexe:
def __init__ (self, a = 0, b= 0) : self.a, self.b = a,b
def get_module (self) : return math.sqrt (self.a * self.a + self.b * self.b)
4. Classes 88
c1 = nombre_complexe (0,1)
c2 = nombre_complexe (1,0)
c = c1.ajoute (c2) # c = c1 + c2
print c.a, c.b
Toutefois, on aimerait bien écrire simplement c = c1 + c2 au lieu de c = c1.ajoute(c2) car cette syntaxe
est plus facile à lire et surtout plus intuitive. Le langage Python offre cette possibilité. Il existe en effet
des méthodes clés dont l’implémentation définit ce qui doit être fait dans le cas d’une addition, d’une
comparaison, d’un affichage, ... A l’instar du constructeur, toutes ces méthodes clés, qu’on appelle des
opérateurs, sont encadrées par deux blancs soulignés, leur déclaration suit invariablement le même schéma.
Voici celui de l’opérateur __add__ qui décrit ce qu’il faut faire pour une addition.
nom_classe est une classe. L’opérateur __add__ définit l’addition entre l’instance self et l’instance
autre et retourne une instance de la classe nom_classe.
Le programme suivant reprend le précédent de manière à ce que l’addition de deux nombres complexes
soit dorénavant une syntaxe correcte.
class nombre_complexe:
def __init__ (self, a = 0, b= 0) : self.a, self.b = a,b
def get_module (self) : return math.sqrt (self.a * self.a + self.b * self.b)
c1 = nombre_complexe (0,1)
c2 = nombre_complexe (1,0)
c = c1 + c2 # cette expression est maintenant syntaxiquement correcte
c = c1.__add__ (c2) # même ligne que la précédente mais écrite explicitement
print c.a, c.b
L’avant dernière ligne appelant la méthode __add__ transcrit de façon explicite ce que le langage Py-
thon fait lorsqu’il rencontre un opérateur + qui s’applique à des classes. Plus précisément, c1 et c2 pour-
raient être de classes différentes, l’expression serait encore valide du moment que la classe dont dépend
c1 a redéfini la méthode __add__. Chaque opérateur possède sa méthode-clé associée. L’opérateur +=,
différent de + est associé à la méthode-clé __iadd__.
nom_classe est une classe. L’opérateur __iadd__ définit l’addition entre l’instance self et l’instance
autre. L’instance self est modifiée pour recevoir le résultat. L’opérateur retourne invariablement
l’instance modifiée self.
On étoffe la classe nombre_complexe à l’aide de l’opérateur __iadd__.
class nombre_complexe:
def __init__ (self, a = 0, b= 0) : self.a, self.b = a,b
4. Classes 89
def __iadd__(self, c) :
self.a += c.a
self.b += c.b
return self
c1 = nombre_complexe (0,1)
c2 = nombre_complexe (1,0)
c1 += c2 # utilisation de l’opérateur +=
c1.__iadd__ (c2) # c’est la transcription explicite de la ligne précédente
print c1.a, c1.b
Un autre opérateur souvent utilisé est __str__ qui permet de redéfinir l’affichage d’un objet lors d’un
appel à l’instruction print.
nom_classe est une classe. L’opérateur __str__ construit une chaîne de caractères qu’il retourne
comme résultat de façon à être affiché.
L’exemple suivant reprend la classe nombre_complexe pour que l’instruction print affiche un nombre com-
plexe sous la forme a + ib.
class nombre_complexe:
def __init__ (self, a = 0, b= 0) : self.a, self.b = a,b
def __add__(self, c) : return nombre_complexe (self.a + c.a, self.b + c.b)
c1 = nombre_complexe (0,1)
c2 = nombre_complexe (1,0)
c3 = c1 + c2
print c3 # affiche 1.000000 + 1.000000 i
Il existe de nombreux opérateurs qu’il est possible de définir. La table 4.1 (page 117) présente les plus
utilisés. Parmi ceux-là, on peut s’attarder sur les opérateurs __getitem__ et __setitem__, ils redéfi-
nissent l’opérateur [] permettant d’accéder à un élément d’une liste ou d’un dictionnaire. Le premier,
__getitem__ est utilisé lors d’un calcul, un affichage. Le second, __setitem__, est utilisé pour affecter
une valeur.
L’exemple suivant définit un point de l’espace avec trois coordonnées. Il redéfinit ou surcharge les opérateurs
__getitem__ et __setitem__ de manière à pouvoir accéder aux coordonnées de la classe point_espace
qui définit un point dans l’espace. En règle générale, lorsque les indices ne sont pas corrects, ces deux
opérateurs lèvent l’exception IndexError (voir le chapitre 5).
class point_espace:
def __init__ (self, x,y,z): self._x, self._y, self._z = x,y,z
def __getitem__(self,i):
if i == 0 : return self._x
if i == 1 : return self._y
if i == 2 : return self._z
# pour tous les autres cas --> erreur
4. Classes 90
def __setitem__(self,i,x):
if i == 0 : self._x = x
elif i == 1 : self._y = y
elif i == 2 : self._z = z
# pour tous les autres cas --> erreur
raise IndexError ("indice impossible, 0,1,2 autorisés")
def __str__(self):
return "(%f,%f,%f)" % (self._x, self._y, self._z)
a = point_espace (1,-2,3)
Par le biais de l’exception IndexError, les expressions a[i] avec i 6= 0, 1, 2 sont impossibles et arrêtent le
programme par un message comme celui qui suit obtenu après l’interprétation de print a [4] :
Traceback (most recent call last):
File "point_espace.py", line 31, in ?
print a [4]
File "point_espace.py", line 13, in __getitem__
raise IndexError, "indice impossible, 0,1,2 autorisés"
IndexError: indice impossible, 0,1,2 autorisés
4.4.2 Itérateurs
L’opérateur __iter__ permet de définir ce qu’on appelle un itérateur. C’est un objet qui permet d’en
explorer un autre, comme une liste ou un dictionnaire. Un itérateur est un objet qui désigne un élément
d’un ensemble à parcourir et qui connaît l’élément suivant à visiter. Il doit pour cela contenir une référence
à l’objet qu’il doit explorer et inclure une méthode next qui retourne l’élément suivant ou lève une exception
si l’élément actuel est le dernier.
Par exemple, on cherche à explorer tous les éléments d’un objet de type point_espace défini au paragraphe
précédent. Cette exploration doit s’effectuer au moyen d’une boucle for.
a = point_espace (1,-2,3)
for x in a:
print x # affiche successivement 1,-2,3
Cette boucle cache en fait l’utilisation d’un itérateur qui apparaît explicitement dans l’exemple suivant
équivalent au précédent (voir paragraphe 3.3.2.3, page 55).
a = point_espace (1,-2,3)
it = iter (a)
while True:
try : print it.next ()
except StopIteration : break
Afin que cet extrait de programme fonctionne, il faut définir un itérateur pour la classe point_espace.
Cet itérateur doit inclure la méthode next. La classe point_espace doit quant à elle définir l’opérateur
__iter__ pour retourner l’itérateur qui permettra de l’explorer.
class point_espace:
def __init__ (self, x,y,z):
4. Classes 91
class class_iter:
"""cette classe définit un itérateur pour point_espace"""
def __init__ (self,ins):
"""initialisation, self._ins permet de savoir quelle
instance de point_espace on explore,
self._n mémorise l’indice de l’élément exploré"""
self._n = 0
self._ins = ins
def __iter__(self):
"""opérateur de la classe point_espace, retourne un itérateur
permettant de l’explorer"""
return point_espace.class_iter (self)
a = point_espace (1,-2,3)
for x in a:
print x # affiche successivement 1,-2,3
Cette syntaxe peut paraître fastidieuse mais elle montre de manière explicite le fonctionnement des ité-
rateurs. Cette construction est plus proche de ce que d’autres langages objets proposent. Python offre
néanmoins une syntaxe plus courte avec le mot-clé yield qui permet d’éviter la création de la classe
class_iter. Le code de la méthode __iter__ change mais les dernières lignes du programme précédent
qui affichent successivement les éléments de point_espace sont toujours valides.
class point_espace:
def __init__ (self, x,y,z):
self._x, self._y, self._z = x,y,z
def __str__(self):
return "(%f,%f,%f)" % (self._x, self._y, self._z)
def __getitem__(self,i):
if i == 0 : return self._x
if i == 1 : return self._y
if i == 2 : return self._z
# pour tous les autres cas --> erreur
raise IndexError ("indice impossible, 0,1,2 autorisés")
def __iter__(self):
"""itérateur avec yield (ou générateur)"""
_n = 0
while _n <= 2 :
yield self.__getitem__ (_n)
4. Classes 92
_n += 1
a = point_espace (1,-2,3)
for x in a:
print x # affiche successivement 1,-2,3
L’exemple suivant définit une classe avec une seule méthode. Comme toutes les méthodes présentées jusqu’à
présent, elle inclut le paramètre self qui correspond à l’instance pour laquelle elle est appelée.
class essai_class:
def methode (self):
print "méthode non statique"
x = essai_class ()
x.methode ()
Une méthode statique ne nécessite pas qu’une instance soit créée pour être appelée. C’est donc une méthode
n’ayant pas besoin du paramètre self.
nom_classe est une classe, nom_methode est une méthode statique. Il faut pourtant ajouter la
ligne suivante pour indiquer à la classe que cette méthode est bien statique à l’aide du mot-clé
staticmethod.
Le programme précédent est modifié pour inclure une méthode statique. La méthode methode ne nécessite
aucune création d’instance pour être appelée.
class essai_class:
def methode ():
print "méthode statique"
methode = staticmethod(methode)
essai_class.methode ()
class essai_class:
pass
essai_class.methode = staticmethod(methode)
essai_class.methode ()
Toutefois, il est conseillé de placer l’instruction qui contient staticmethod à l’intérieur de la classe. Elle
n’y sera exécutée qu’une seule fois comme le montre l’exemple suivant :
class essai_class:
print "création d’une instance de la classe essai_class"
methode = staticmethod(methode)
cl = classe_vide () # affiche création d’une instance de la classe essai_class
ck = classe_vide () # n’affiche rien
Les méthodes statiques sont souvent employées pour créer des instances spécifiques d’une classe.
class Couleur :
def __init__ (self, r, v, b) : self.r, self.v, self.b = r, v, b
def __str__ (self) : return str ( (self.r,self.v,self.b))
def blanc () : return Couleur (255,255,255)
def noir () : return Couleur (0,0,0)
blanc = staticmethod (blanc)
noir = staticmethod (noir)
c = Couleur.blanc ()
print c # affiche (255, 255, 255)
c = Couleur.noir ()
print c # affiche (0, 0, 0)
nom_classe est une classe, nom_methode est une méthode non statique, nom_methode_st est une mé-
thode statique. Les trois paramètres attribut_statique, attribut_statique2, attribut_statique3
sont statiques, soit parce qu’ils sont déclarés en dehors d’une méthode, soit parce que leur déclaration
fait intervenir le nom de la classe.
Pour le programme suivant, la méthode meth n’utilise pas self.x mais essai_class.x. L’attribut x est alors
un attribut statique, partagé par toutes les instances. C’est pourquoi dans l’exemple qui suit l’instruction
z.meth() affiche la valeur 6 puisque l’appel y.meth() a incrémenté la variable statique x.
class essai_class:
x = 5
4. Classes 94
def meth(self):
print essai_class.x
essai_class.x += 1
y = essai_class ()
z = essai_class ()
y.meth() # affiche 5
z.meth() # affiche 6
cl = exemple_classe()
Dans ce cas, ce sont en fait deux attributs qui sont créés. Le premier est un attribut statique créé avec
la seconde ligne de l’exemple rnd = 42. Le second attribut n’est pas statique et apparaît dès la première
exécution de l’instruction self.rnd+=1 comme le montre son apparition dans l’attribut __dict__ qui ne
recense pas les attributs statiques.
class nom_classe :
# code de la classe
nom_methode = classmethod (nom_methode) # syntaxe 1
nom_classe est une classe, nom_methode est une méthode, nom_methode est une fonction qui est par
la suite considérée comme une méthode de la classe nom_methode grâce à l’une ou l’autre des deux
instructions incluant le mot-clé classmethod.
Dans l’exemple qui suit, cette syntaxe est utilisée pour inclure trois méthodes à la classe essai_class selon
que la méthode est déclarée et affectée à cette classe à l’intérieur ou à l’extérieur du corps de essai_class.
class essai_classe:
x = 5
def meth(self): print "ok meth", self.x
def meth2(cls): print "ok meth2", cls.x
x = essai_classe ()
x.meth () # affiche ok meth 5
x.meth2 () # affiche ok meth2 5
x.meth3 () # affiche ok meth3 5
4.5.4 Propriétés
Cette fonctionnalité est également peu utilisée, elle permet des raccourcis d’écriture. Les propriétés per-
mettent de faire croire à l’utilisateur d’une instance de classe qu’il utilise une variable alors qu’il utilise
en réalité une ou plusieurs méthodes. A chaque fois que le programmeur utilise ce faux attribut, il appelle
une méthode qui calcule sa valeur. A chaque fois que le programmeur cherche à modifier la valeur de ce
faux attribut, il appelle une autre méthode qui modifie l’instance.
Au sein de ces trois lignes, nom_classe est une classe, nom_propriete est le nom de la propriété,
fget est la méthode qui doit retourner la valeur du pseudo-attribut nom_propriete, fset est la
méthode qui doit modifier la valeur du pseudo-attribut nom_propriete, fdel est la méthode qui doit
détruire le pseudo-attribut nom_propriete, doc est un commentaire qui apparaîtra lors de l’appel de
la fonction help(nom_class) ou help(nom_class.nom_propriete).
Pour illustrer l’utilisation des propriétés, on part d’une classe nombre_complexe qui ne contient que les
parties réelle et imaginaire. Lorsqu’on cherche à obtenir le module 5 , on fait appel à une méthode qui calcule
ce module. Lorsqu’on cherche à modifier ce module, on fait appel à une autre méthode qui multiplie les
parties réelle et imaginaire par un nombre réel positif de manière à ce que le nombre complexe ait le
module demandé. On procède de même pour la propriété arg.
La propriété conj retourne quant à elle le conjugué du nombre complexe mais la réciproque n’est pas
prévue. On ne peut affecter une valeur à conj.
import math
c = nombre_complexe ()
c.module = 1
c.arg = math.pi * 2 / 3
print "c = ", c # affiche c = -0.500000 + 0.866025 i
print "module = ", c.module # affiche module = 1.0
print "argument = ", c.arg # affiche argument = 2.09439510239
print "conjugué = ", c.conj # affiche conjugué = -0.500000 - 0.866025 i
La propriété conj ne possède pas de fonction qui permet de la modifier. Par conséquent, l’instruction
c.conj = nombre_complexe(0, 0) produit l’erreur suivante :
Etant donné qu’une propriété porte déjà le nom de conj, aucun attribut du même nom ne peut être ajouté
à la classe nombre_complexe.
Remarque 4.26 : propriété et héritage
Afin que la propriété fonctionne correctement, il est nécessaire que la classe hérite de la classe object ou
une de ses descendantes (voir également paragraphe 4.8).
4. Classes 97
class exemple_classe:
def __init__ (self) : self.rnd = 42
def methode1(self,n):
self.rnd = 397204094 * self.rnd % 2147483647
return int (self.rnd % n)
nb = exemple_classe ()
nb2 = nb
print nb.rnd # affiche 42
print nb2.rnd # affiche 42
nb2.rnd = 0
Pour créer une copie de l’instance nb, il faut le dire explicitement en utilisant la fonction copy du module
copy (voir le chapitre 6).
nom_instance est une instance à copier, nom_copy est le nom désignant la copie.
L’exemple suivant applique cette copie sur la classe exemple_classe générant des nombres aléatoires.
class exemple_classe:
def __init__ (self) : self.rnd = 42
def methode1(self,n):
self.rnd = 397204094 * self.rnd % 2147483647
return int (self.rnd % n)
nb = exemple_classe ()
nb2.rnd = 0
m = [ 0, 1 ]
m2 = m
del m2 # supprime l’identificateur mais pas la liste
print m # affiche [0, 1]
La suppression d’un objet n’est effective que s’il ne reste aucune variable le référençant. L’exemple suivant
le montre.
class CreationDestruction (object) :
print "a"
m = CreationDestruction ()
print "b" 15
m2 = m
print "c"
del m
print "d"
del m2 20
print "e"
Le destructeur est appelé autant de fois que le constructeur. Il est appelé lorsque plus aucun identificateur
n’est relié à l’objet. Cette configuration survient lors de l’exemple précédent car le mot-clé del a détruit
tous les identificateurs m et m2 qui étaient reliés au même objet.
class exemple_classe:
def __init__ (self) :
self.inclus = classe_incluse ()
self.rnd = 42
4. Classes 99
nb = exemple_classe ()
nb2.inclus.attr = 0
Pour effectivement copier les attributs dont le type est une classe, la première option - la plus simple -
est de remplacer la fonction copy par la fonction deepcopy. Le comportement de cette fonction dans le cas
des classes est le même que dans le cas des listes comme l’explique la remarque 2.18 page 39. La seconde
solution, rarement utilisée, est d’utiliser l’opérateur __copy__ et ainsi écrire le code associé à la copie
des attributs de la classe.
syntaxe 4.29 : classe, opérateur __copy__
class nom_classe :
def __copy__ () :
copie = nom_classe (...)
# ...
return copie
nom_classe est le nom d’une classe. La méthode __copy__ doit retourner une instance de la classe
nom_classe, dans cet exemple, cette instance a pour nom copie.
L’exemple suivant montre un exemple d’implémentation de la classe __copy__. Cette méthode crée
d’abord une autre instance copie de la classe exemple_classe puis initialise un par un ses membres.
L’attribut rnd est recopié grâce à une affectation car c’est un nombre. L’attribut inclus est recopié grâce
à la fonction copy du module copy car c’est une instance de classe. Après la copie, on vérifie bien que
modifier l’attribut inclus.attr de l’instance nb ne modifie pas l’attribut inclus.attr de l’instance nb2.
import copy
class classe_incluse:
def __init__ (self) : self.attr = 3
class exemple_classe:
def __init__ (self) :
self.inclus = classe_incluse ()
self.rnd = 42
def __copy__ (self):
copie = exemple_classe ()
copie.rnd = self.rnd
copie.inclus = copy.copy (self.inclus)
return copie
nb = exemple_classe ()
nb.inclus.attr = 0
nb.rnd = 1
Lorsqu’une fonction retourne un résultat mais que celui-ci n’est pas attribué à un nom de variable. Le
langage Python détecte automatiquement que ce résultat n’est plus lié à aucune variable. Il est détruit
automatiquement.
def fonction_liste ():
return range (4,7)
fonction_liste () # la liste [4,5,6] n’est pas recopiée,
# elle n’est pas non plus attribuée à une variable,
# elle est alors détruite automatiquement par le langage Python
La fonction copy ne suffit pourtant pas lorsque l’objet à copier est par exemple une liste incluant d’autres
objets. Elle copiera la liste et ne fera pas de copie des objets eux-mêmes.
import copy
l = [ [i] for i in range(0,3)]
4. Classes 101
ll = copy.copy (l)
print l, " - ", ll # affiche [[0], [1], [2]] - [[0], [1], [2]]
ll [0][0] = 6
print l, " - ", ll # affiche [[6], [1], [2]] - [[6], [1], [2]]
Il n’est pas possible de modifier la méthode __copy__ d’un objet de type liste. Il existe néanmoins la
fonction deepcopy qui permet de faire une copie à la fois de la liste et des objets qu’elle contient.
import copy
l = [ [i] for i in range(0,3)]
ll = copy.deepcopy (l)
print l, " - ", ll # affiche [[0], [1], [2]] - [[0], [1], [2]]
ll [0][0] = 6
print l, " - ", ll # affiche [[0], [1], [2]] - [[6], [1], [2]]
La fonction copy effectue une copie d’un objet, la fonction deepcopy effectue une copie d’un objet et de
ceux qu’il contient. La fonction copy est associée à la méthode __copy__ tandis que la fonction deepcopy
est associée à la méthode __deepcopy__. Il est rare que l’une de ces deux méthodes doivent être redéfinies.
L’intérêt de ce paragraphe est plus de montrer le mécanisme que cache la fonction deepcopy qui est la
raison pour laquelle il existe deux fonctions de copie et non une seule.
nom_instance est une instance à copier, nom_copy est le nom désignant la copie. memo est un paramètre
facultatif : s’il est envoyé à la fonction deepcopy, il contiendra alors la liste de toutes les copies d’objet
effectuées lors de cet appel.
nom_classe est le nom d’une classe. La méthode __deepcopy__ doit retourner une instance de la
classe nom_classe, dans cet exemple, cette instance a pour nom copie. Le paramètre memo permet
de conserver la liste des copies effectuées à condition d’appeler deepcopy avec un dictionnaire en
paramètre.
Le programme suivant reprend le second programme du paragraphe 4.6.2 et modifie la classe classe_incluse
pour distinguer copie et copie profonde. Il peut être utile de lire le paragraphe 2.3.3.4 (page 43) pour com-
prendre pourquoi un dictionnaire utilisant comme clé une instance de classe est possible.
import copy
class classe_incluse:
def __init__ (self) : self.attr = 3
class exemple_classe:
def __init__ (self) :
self.inclus = classe_incluse ()
4. Classes 102
self.rnd = 42
def __copy__ (self):
copie = exemple_classe ()
copie.rnd = self.rnd
return copie
def __deepcopy__ (self,memo):
if self in memo : return memo [self]
copie = copy.copy (self)
memo [self] = copie # mémorise la copie de self qui est copie
copie.inclus = copy.deepcopy (self.inclus,memo)
return copie
nb = exemple_classe ()
nb.inclus.attr = 0
nb.rnd = 1
On peut se demander quel est l’intérêt de la méthode __deepcopy__ et surtout du paramètre memo modifié
par la ligne memo[self] = copie. Ce détail est important lorsqu’un objet inclut un attribut égal à lui-même
ou inclut un objet qui fait référence à l’objet de départ comme dans l’exemple qui suit.
import copy
class Objet1 :
def __init__ (self, i) : self.i = i
def __str__ (self) :
return "o1 " + str (self.i) + " : " + str (self.o2.i)
class Objet2 :
def __init__ (self, i, o) :
self.i = i
self.o1 = o
o.o2 = self
def __str__ (self) :
return "o2 " + str (self.i) + " : " + str (self.o1.i)
o1 = Objet1 (1)
o2 = Objet2 (2, o1)
print o1 # affiche o1 1 : 2
print o2 # affiche o2 2 : 1
o3 = copy.deepcopy (o2)
o3.i = 4
print o1 # affiche o1 1 : 4 --> on voudrait 2
print o2 # affiche o2 2 : 1
print o3 # affiche o2 4 : 1
4. Classes 103
On modifie le programme comme suit pour obtenir une recopie d’instances de classes qui pointent les unes
sur vers les autres. Le paramètre memo sert à savoir si la copie de l’objet a déjà été effectuée ou non. Si
non, on fait une copie, si oui, on retourne la copie précédemment effectuée et conservée dans memo.
import copy
class Objet1 :
def __init__ (self, i) : self.i = i
def __str__ (self) :
return "o1 " + str (self.i) + " : " + str (self.o2.i)
def __deepcopy__ (self,memo={}) :
if self in memo : return memo [self]
r = Objet1 (self.i)
memo [self] = r
r.o2 = copy.deepcopy (self.o2, memo)
return r
class Objet2 :
def __init__ (self, i, o) :
self.i = i
self.o1 = o
o.o2 = self
def __str__ (self) :
return "o2 " + str (self.i) + " : " + str (self.o1.i)
o1 = Objet1 (1)
o2 = Objet2 (2, o1)
print o1 # affiche o1 1 : 2
print o2 # affiche o2 2 : 1
o3 = copy.deepcopy (o2)
o3.i = 4
print o1 # affiche o1 1 : 2 --> on a 2 cette fois-ci
print o2 # affiche o2 2 : 1
print o3 # affiche o2 4 : 1
Ces problématiques se rencontrent souvent lorsqu’on aborde le problème de la sérialisation d’un objet qui
consiste à enregistrer tout objet dans un fichier, même si cet objet inclut des références à des objets qui
font référence à lui-même. C’est ce qu’on appelle des références circulaires. L’enregistrement d’un tel objet
avec des références circulaires et sa relecture depuis un fichier se résolvent avec les mêmes artifices que ceux
proposés ici pour la copie. L’utilisation des opérateurs __copy__ et __deepcopy__ est peu fréquente.
Les fonctions copy et deepcopy du module copy suffisent dans la plupart des cas.
nom_classe est le nom de la classe, elle doit hériter de object ou d’une classe qui en hérite
elle-même (voir paragraphe 4.8). Il faut ensuite ajouter au début du corps de la classe la ligne
__slots__ = ”attribut_1”, ..., ”attribut_n” où attribut_1 à attribut_n sont les noms des attri-
buts de la classe. Aucun autre ne sera accepté.
L’exemple suivant utilise cette syntaxe pour définir un point avec seulement trois attributs _x, _y, _z.
class point_espace(object):
__slots__ = "_x", "_y", "_z"
a = point_espace (1,-2,3)
print a
Etant donné que la liste des attributs est figée, l’instruction a.j = 6 qui ajoute un attribut j à l’instance
a déclenche une exception (voir paragraphe 5). La même erreur se déclenche si on cherche à ajouter cet
attribut depuis une méthode (self.j = 6).
L’attribut __dict__ n’existe pas non plus, par conséquent, l’expression a.__dict__ génère la même
exception. La présence de l’instruction __slots__ = ... n’a aucun incidence sur les attributs statiques.
4.8 Héritage
L’héritage est un des grands avantages de la programmation objet. Il permet de créer une classe à partir
d’une autre en ajoutant des attributs, en modifiant ou en ajoutant des méthodes. En quelque sorte, on
peut modifier des méthodes d’une classe tout en conservant la possibilité d’utiliser les anciennes versions.
def cent_tirages () :
s = 0
for i in range (0,100) : s += random.randint (0,1)
return s
4. Classes 105
print cent_tirages ()
On désire maintenant réaliser cette même expérience pour une pièce truquée pour laquelle la face pile
sort avec une probabilité de 0, 7. Une solution consiste à réécrire la fonction cent_tirages pour la pièce
truquée.
def cent_tirages () :
s = 0
for i in range (0,100) :
t = random.randint (0,10)
if t >= 3 : s += 1
return s
print cent_tirages ()
Toutefois cette solution n’est pas satisfaisante car il faudrait réécrire cette fonction pour chaque pièce
différente pour laquelle on voudrait réaliser cette expérience. Une autre solution consiste donc à passer en
paramètre de la fonction cent_tirages une fonction qui reproduit le comportement d’une pièce, qu’elle
soit normale ou truquée.
def piece_normale () :
return random.randint (0,1)
def piece_truquee () :
t = random.randint (0,10)
if t >= 3 : return 1
else : return 0
Mais cette solution possède toujours un inconvénient car les fonctions associées à chaque pièce n’acceptent
aucun paramètre. Il n’est pas possible de définir une pièce qui est normale si la face pile vient de sortir et
qui devient truquée si la face face vient de sortir 6 . On choisit alors de représenter une pièce normale par
une classe.
class piece_normale :
def tirage (self) :
return random.randint (0,1)
p = piece_normale ()
print p.cent_tirages ()
class piece_truquee :
def tirage (self) :
t = random.randint (0,10)
if t >= 3 : return 1
else : return 0
p = piece_normale ()
print p.cent_tirages ()
p2 = piece_truquee ()
print p2.cent_tirages ()
Toutefois, pour les deux classes piece_normale et piece_truquee, la méthode cent_tirage est exactement
la même. Il serait préférable de ne pas répéter ce code puisque si nous devions modifier la première - un
nombre de tirages différent par exemple -, il faudrait également modifier la seconde. La solution passe
par l’héritage. On va définir la classe piece_truquee à partir de la classe piece_normale en remplaçant
seulement la méthode tirage puisqu’elle est la seule à changer.
On indique à la classe piece_truquee qu’elle hérite - ou dérive - de la classe piece_normale en mettant
piece_normale entre parenthèses sur la ligne de la déclaration de la classe piece_truquee. Comme la
méthode cent_tirages ne change pas, elle n’a pas besoin d’apparaître dans la définition de la nouvelle
classe même si cette méthode est aussi applicable à une instance de la classe piece_truquee.
class piece_normale :
def tirage (self) :
return random.randint (0,1)
p = piece_normale ()
print p.cent_tirages ()
p2 = piece_truquee ()
print p2.cent_tirages ()
Enfin, on peut définir une pièce très truquée qui devient truquée si face vient de sortir et qui redevient
normale si pile vient de sortir. Cette pièce très truquée sera implémentée par la classe piece_tres_truquee.
Elle doit contenir un attribut avant qui conserve la valeur du précédent tirage. Elle doit redéfinir la méthode
tirage pour être une pièce normale ou truquée selon la valeur de l’attribut avant. Pour éviter de réécrire
4. Classes 107
des méthodes déjà écrites, la méthode tirage de la classe piece_tres_truquee doit appeler la méthode
tirage de la classe piece_truquee ou celle de la classe piece_normale selon la valeur de l’attribut avant.
class piece_normale :
def tirage (self) :
return random.randint (0,1)
p = piece_normale ()
print "normale ", p.cent_tirages ()
p2 = piece_truquee ()
print "truquee ", p2.cent_tirages ()
p3 = piece_tres_truquee ()
print "tres truquee ", p3.cent_tirages ()
L’héritage propose donc une manière élégante d’organiser un programme. Il rend possible la modification
des classes d’un programme sans pour autant les altérer.
La surcharge est un autre concept qui va de pair avec l’héritage. Elle consiste à redéfinir des méthodes
déjà définies chez l’ancêtre. Cela permet de modifier le comportement de méthodes bien que celles-ci soient
utilisées par d’autres méthodes dont la définition reste inchangée.
4.8.2 Syntaxe
nom_classe désigne le nom d’une classe qui hérite ou dérive d’une autre classe nom_ancetre. Celle-ci
nom_ancetre doit être une classe déjà définie.
L’utilisation de la fonction help permet de connaître tous les ancêtres d’une classe. On applique cette
fonction à la classe piece_tres_truquee définie au paragraphe précédent.
help (piece_tres_truquee)
class piece_tres_truquee(piece_truquee)
| Method resolution order:
| piece_tres_truquee
| piece_truquee
| piece_normale
|
| Methods defined here:
|
| __init__(self)
|
| tirage(self)
|
| ----------------------------------------------------------------------
| Methods inherited from piece_normale:
|
| cent_tirages(self)
Dans les exemples précédents, piece_normale ne dérive d’aucune autre classe. Toutefois, le langage Py-
thon propose une classe d’objets dont héritent toutes les autres classes définies par le langage : c’est la classe
4. Classes 109
object. Les paragraphes précédents ont montré qu’elle offrait certains avantages (voir paragraphe 4.5.4
sur les propriétés ou encore paragraphe 4.7 sur les attributs non liés).
Le paragraphe précédent a montré qu’il était parfois utile d’appeler dans une méthode une autre méthode
appartenant explicitement à l’ancêtre direct de cette classe ou à un de ses ancêtres. La syntaxe est la
suivante.
nom_classe désigne le nom d’une classe, nom_ancetre est le nom de la classe dont nom_classe hérite
ou dérive. nom_methode est une méthode surchargée qui appelle la méthode portant le même nom
mais définie dans la classe nom_ancetre ou un de ses ancêtres. nom_autre_methode est une autre
méthode. La méthode nom_methode de la classe nom_classe peut faire explicitement appel à une
méthode définie chez l’ancêtre nom_ancetre même si elle est également surchargée ou faire appel à
la méthode surchargée.
Ces appels sont très fréquents en ce qui concerne les constructeurs qui appellent le constructeur de l’ancêtre.
Il est même conseillé de le faire à chaque fois.
class A :
def __init__ (self) :
self.x = 0
class B (A) :
def __init__ (self) :
A.__init__ (self)
self.y = 0
x = ancetre ()
print x # affiche a = 5
y = fille ()
print y # affiche a = 6
r = rectangle (3,4)
print r # affiche rectangle 3 x 4
c = carre (5)
print c # affiche rectangle 5 x 5
Toutefois, on peut aussi considérer que la classe carre contient une information redondante puisqu’elle
possède deux attributs qui seront toujours égaux. On peut se demander s’il n’est pas préférable que la
classe rectangle hérite de la classe carre.
class carre :
def __init__( self, a) :
self.a = a
def __str__ (self) :
return "carre " + str (self.a)
r = rectangle (3,4)
print r # affiche rectangle 3 x 4
c = carre (5)
print c # affiche carre 5
Cette seconde version minimise l’information à mémoriser puisque la classe carre ne contient qu’un seul at-
tribut et non deux comme dans l’exemple précédent. Néanmoins, il a fallu surcharger l’opérateur __str__
afin d’afficher la nouvelle information.
Il n’y a pas de meilleur choix parmi ces deux solutions proposées. La première solution va dans le sens des
propriétés croissantes, les méthodes implémentées pour les classes de bases restent vraies pour les suivantes.
La seconde solution va dans le sens des attributs croissants, des méthodes implémentées pour les classes
de bases doivent souvent être adaptées pour les héritiers. En contrepartie, il n’y a pas d’information
redondante.
Ce problème d’héritage ne se pose pas à chaque fois. Dans l’exemple du paragraphe 4.8.1 autour des pièces
truquées, il y a moins d’ambiguïté sur le sens de l’héritage. Celui-ci est guidé par le problème à résoudre
qui s’avère plus simple à concevoir dans le sens d’un héritage d’une pièce normale vers une pièce truquée.
4. Classes 111
Dans le cas des classes carre et rectangle, il n’est pas possible de déterminer la meilleure solution tant
que leur usage ultérieur n’est pas connu. Ce problème revient également lorsqu’on définit des matrices et
des vecteurs. Un vecteur est une matrice d’une seul colonne, il ne possède qu’une seule dimension au lieu
de deux pour une matrice. L’exercice 12.1.10 page 296 revient sur ce dilemme.
class B :
def __init__ (self) : self.a = 6
def cube (self) : return self.a ** 3
class C (A,B) :
def __init__ (self):
A.__init__ (self)
x = C ()
print x.carre () # affiche 25
print x.cube () # affiche 125
Mais ces héritages multiples peuvent parfois apporter quelques ambiguïtés comme le cas où au moins deux
ancêtres possèdent une méthode du même nom. Dans l’exemple qui suit, la classe C hérite toujours des
classes A et B. Ces deux classes possèdent une méthode calcul. La classe C, qui hérite des deux, possède
aussi une méthode calcul qui, par défaut, sera celle de la classe A.
class A :
def __init__ (self) : self.a = 5
def calcul (self) : return self.a ** 2
class B :
def __init__ (self) : self.a = 6
def calcul (self) : return self.a ** 3
class C (A,B) :
def __init__ (self):
A.__init__ (self)
x = C ()
print x.calcul () # affiche 25
Cette information est disponible via la fonction help appliquée à la classe C. C’est dans ce genre de situa-
tions que l’information apportée par la section Method resolution order est importante (voir remarque 4.37
page 108).
class C(A, B)
| Method resolution order:
| C
| A
4. Classes 112
| B
|
| Methods defined here:
|
| __init__(self)
|
| calcul(self)
Pour préciser que la méthode calcul de la classe C doit appeler la méthode calcul de la classe B et non A,
il faut l’écrire explicitement en surchargeant cette méthode.
class A :
def __init__ (self) : self.a = 5
def calcul (self) : return self.a ** 2
class B :
def __init__ (self) : self.a = 6
def calcul (self) : return self.a ** 3
class C (A,B) :
def __init__ (self):
A.__init__ (self)
x = C ()
print x.calcul () # affiche 125
Le résultat de cette fonction est vrai si la classe B hérite de la classe A, le résultat est faux dans tous
les autres cas. La fonction prend comme argument des classes et non des instances de classes.
L’exemple qui suit utilise cette fonction dont le résultat est vrai même pour des classes qui n’héritent pas
directement l’une de l’autre.
class A (object) : pass
class B (A) : pass
class C (B) : pass
Lorsqu’on souhaite appliquer la fonction à une instance de classe, il faut faire appel à l’attribut __class__.
En reprenant les classes définies par l’exemple précédant cela donne :
a = A ()
b = B ()
print issubclass (a.__class__, B) # affiche False
print issubclass (b.__class__, A) # affiche True
print issubclass (a.__class__, A) # affiche True
La fonction isinstance permet de savoir si une instance de classe est d’une type donné. Elle est équivalente
à la fonction issubclass à ceci près qu’elle prend comme argument une instance et une classe. L’exemple
précédent devient avec la fonction isinstance :
a = A ()
b = B ()
print isinstance (a, B) # affiche False
print isinstance (b, A) # affiche True
print isinstance (a, A) # affiche True
L’utilisation des fonctions issubclass et isinstance n’est pas très fréquente mais elle permet par exemple
d’écrire une fonction qui peut prendre en entrée des types variables.
def fonction_somme_list (ens) :
r = "list "
for e in ens : r += e
return r
5
def fonction_somme_dict (ens) :
r = "dict "
for k,v in ens.items () : r += v
return r
10
def fonction_somme (ens) :
if isinstance (ens, dict) : return fonction_somme_dict (ens)
elif isinstance (ens, list) : return fonction_somme_list (ens)
else : return "erreur"
15
li = ["un", "deux", "trois"]
di = {1:"un", 2:"deux", 3:"trois"}
tu = ("un", "deux", "trois")
print fonction_somme (li) # affiche list undeuxtrois
print fonction_somme (di) # affiche dict undeuxtrois 20
print fonction_somme (tu) # affiche erreur
L’avantage est d’avoir une seule fonction capable de s’adapter à différents type de variables, y compris des
types créés par un programmeur en utilisant les classes.
r = rectangle (3,4)
print r # affiche rectangle 3 x 4
c = carre (5)
print c # affiche carre 5
Comme toute fonction, la fonction compile génère une exception lorsque la chaîne de caractères contient
une erreur. Le programme qui suit essaye de compiler une chaîne de caractères confondant self et seilf.
# coding: latin-1
"""erreur de compilation incluses dans le code inséré dans la
chaîne de caractère s"""
s = """class carre :
def __init__( self, a) :
seilf.a = a # erreur de compilation
def __str__ (self) :
return "carre " + str (self.a) """
c = carre (5)
print c # affiche carre 5
Le message d’erreur est le même que pour un programme ne faisant pas appel à la fonction compile à
ceci près que le fichier où a lieu l’erreur est variable s qui est le second paramètre envoyé à la fonction
compile. Le numéro de ligne fait référence à la troisième ligne de la chaîne de caractères s et non à la
troisième ligne du programme.
l = [0,1,2,3]
print l # affiche [0, 1, 2, 3]
15
l1 = Carre ().calcul_n_valeur (l) # l1 vaut [0, 1, 4, 9]
l2 = Cube () .calcul_n_valeur (l) # l2 vaut [0, 1, 8, 27]
La version suivante mélange héritage et méthodes envoyées comme paramètre à une fonction. Il est préfé-
rable d’éviter cette construction même si elle est très utilisée par les interfaces graphiques. Elle n’est pas
toujours transposable dans tous les langages de programmation tandis que le programme précédent aura
un équivalent dans tous les langages objet.
class Fonction :
def calcul (self, x) : pass
class Carre (Fonction) :
def calcul (self, x) : return x*x
class Cube (Fonction) : 5
def calcul (self, x) : return x*x*x
l = [0,1,2,3]
l1 = calcul_n_valeur (l, Carre ().calcul) # l1 vaut [0, 1, 4, 9]
l2 = calcul_n_valeur (l, Cube ().calcul) # l2 vaut [0, 1, 8, 27]
class Matrice :
def __init__ (self,lin,col,coef):
self.lin, self.col = lin, col
# interface d’échange
def get_lin () : return self.lin
def get_col () : return self.col
def __getitem__(self,i,j): pass
def __setitem__(self,i,j,v): pass
def get_submat(self, i1,j1,i2,j2): pass
def set_submat(self, i1,j1,mat): pass
# fin de l’interface d’échange
Cette construction autorise même la multiplication de matrices de structures différentes. Très répandue,
cette architecture est souvent plus coûteuse au moment de la conception car il faut bien penser l’interface
d’échange mais elle l’est beaucoup moins par la suite. Il existe d’autres assemblages de classes assez
fréquents, regroupés sous le terme de Design Patterns. Pour peu que ceux-ci soient connus de celui qui
conçoit un programme, sa relecture et sa compréhension en sont facilitées si les commentaires font mention
du pattern utilisé.
4. Classes 117
Le petit programme suivant déclenche une erreur parce qu’il effectue une division par zéro.
x = 0
y = 1.0 / x
Le mécanisme des exceptions permet au programme de "rattraper" les erreurs, de détecter qu’une erreur
s’est produite et d’agir en conséquence afin que le programme ne s’arrête pas.
On décide par exemple qu’on veut rattraper toutes les erreurs du programme et afficher un message
d’erreur. Le programme suivant appelle la fonction inverse qui retourne l’inverse d’un nombre.
def inverse (x):
y = 1.0 / x
return y
a = inverse (2)
print a
b = inverse (0)
print b
Lorsque le paramètre x = 0, le programme effectue une division par zéro et déclenche une erreur qui paraît
différente de la première d’après la longueur du message. C’est pourtant la même erreur : cette liste
correspond en fait à ce qu’on appelle la pile d’appels 1 . Si l’erreur se produit dans une fonction elle-même
appelée par une autre... la pile d’appel permet d’obtenir la liste de toutes les fonctions pour remonter
jusqu’à celle où l’erreur s’est produite.
Traceback (most recent call last):
File "cours.py", line 8, in ?
b = inverse (0)
Afin de rattraper l’erreur, on insère le code susceptible de produire une erreur entre les mots clés try et
except.
try :
a = inverse (2)
print a
b = inverse (0) # déclenche une exception
print b
except :
print "le programme a déclenché une erreur"
Le programme essaye d’exécuter les quatre instructions incluses entre les instructions try et except. Si
une erreur se produit, le programme exécute alors les lignes qui suivent l’instruction except. L’erreur se
produit en fait à l’intérieur de la fonction inverse mais celle-ci est appelée à l’intérieur d’un code "protégé"
contre les erreurs. Le programme précédent affiche les deux lignes suivantes.
0.5
le programme a déclenché une erreur
Il est aussi possible d’ajouter une clause else qui sert de préfixe à une liste d’instructions qui ne sera
exécutée que si aucune exception n’est déclenchée.
def inverse (x):
y = 1.0 / x
return y
try :
print inverse (2) # pas d’erreur
print inverse (1) # pas d’erreur non plus
except :
print "le programme a déclenché une erreur"
else :
print "tout s’est bien passé"
Pour résumer, la syntaxe suivante permet d’attraper toutes les erreurs qui se produisent pendant l’exécution
d’une partie du programme. Cette syntaxe permet en quelque sorte de protéger cette partie du programme
5. Exceptions 120
Toute erreur déclenchée alors que le programme exécute les instructions qui suivent le mot-clé try
déclenche immédiatement l’exécution des lignes qui suivent le mot-clé except. Dans le cas contraire,
le programme se poursuit avec l’exécution des lignes qui suivent le mot-clé else. Cette dernière
partie est facultative, la clause else peut ou non être présente. Dans tous les cas, l’exécution du
programme ne s’arrête pas.
try :
print (-2.1) ** 3.1 # première erreur
print inverse (2)
print inverse (0) # seconde erreur
except :
print "le programme a déclenché une erreur"
try :
print inverse (2)
print inverse (0)
except Exception, exc:
print "exception de type ", exc.__class__
# affiche exception de type exceptions.ZeroDivisionError
print "message ", exc
# affiche le message associé à l’exception
Le programme précédent récupère une exception sous la forme d’une variable appelée exc. Cette variable
est en fait une instance d’une classe d’erreur, exc.__class__ correspond au nom de cette classe. A l’aide
de la fonction isinstance, il est possible d’exécuter des traitements différents selon le type d’erreur.
def inverse (x):
y = 1.0 / x
return y
5. Exceptions 121
try :
print (-2.1) ** 3.1 # première erreur
print inverse (2)
print inverse (0) # seconde erreur
except Exception, exc:
if isinstance (exc, ZeroDivisionError) :
print "division par zéro"
else :
print "erreur insoupçonnée : ", exc.__class__
print "message ", exc
L’exemple précédent affiche le message qui suit parce que la première erreur intervient lors du calcul de
(−2.1) ∗ ∗3.1.
Une autre syntaxe plus simple permet d’attraper un type d’exception donné en accolant au mot-clé except
le type de l’exception qu’on désire attraper. L’exemple précédent est équivalent au suivant mais syntaxi-
quement différent.
def inverse (x):
y = 1.0 / x
return y
try :
print (-2.1) ** 3.1
print inverse (2)
print inverse (0)
except ZeroDivisionError:
print "division par zéro"
except Exception, exc:
print "erreur insoupçonnée : ", exc.__class__
print "message ", exc
Toute erreur déclenchée alors que le programme exécute les instructions qui suivent le mot-clé try dé-
clenche immédiatement l’exécution des lignes qui suivent un mot-clé except. Le programme compare
le type d’exception aux types type_exception_1 à type_exception_n. S’il existe une correspondance
alors ce sont les instructions de la clause except associée qui seront exécutées et uniquement ces ins-
tructions. La dernière clause except est facultative, elle est utile lorsque aucun type de ceux prévus
ne correspond à l’exception générée. La clause else est aussi facultative. Si la dernière clause except
n’est pas spécifiée et que l’exception déclenchée ne correspond à aucune de celle listée plus haut, le
programme s’arrête sur cette erreur à moins que celle-ci ne soit attrapée plus tard.
5. Exceptions 122
Le langage Python propose une liste d’exceptions standards (voir table 5.1, page 122). Lorsqu’une erreur
ne correspond pas à l’une de ces exceptions, il est possible de créer une exception propre à un certain
type d’erreur (voir paragraphe 5.2). Lorsqu’une fonction ou une méthode déclenche une exception non
standard, généralement, le commentaire qui lui est associé l’indique.
Elle n’est en principe jamais explicitement générée par les fonctions Python mais
Exception elle permet d’attraper toutes les exceptions car toutes dérivent de la classe
Exception.
AttributeError Une référence à un attribut inexistant ou une affectation a échoué.
ArithmeticError Une opération arithmétique a échoué.
FloatingPointError Une opération avec des nombres réels a échoué.
Une opération concernant les entrées/sorties (Input/Output) a échoué. Cette
IOError
erreur survient par exemple lorsqu’on cherche à lire un fichier qui n’existe pas.
Cette erreur survient lorsqu’on cherche à importer un module qui n’existe pas
ImportError
(voir chapitre 6).
L’interpréteur ne peut interpréter une partie du programme à cause d’un pro-
blème d’indentation. Il n’est pas possible d’exécuter un programme mal indenté
IndentationError
mais cette erreur peut se produire lors de l’utilisation de la fonction compile (voir
le paragraphe 3.4.14.2, page 71).
On utilise un index erroné pour accéder à un élément d’une liste, d’un dictionnaire
IndexError
ou de tout autre tableau.
Une clé est utilisée pour accéder à un élément d’un dictionnaire dont elle ne fait
KeyError
pas partie.
NameError On utilise une variable, une fonction, une classe qui n’existe pas.
Un calcul sur des entiers ou des réels dépasse les capacités de codage des entiers
OverflowError
ou des réels.
Cette exception est utilisée pour signifier qu’un itérateur atteint la fin d’un en-
StopIteration
semble, un tableau, un dictionnaire.
Erreur de type, une fonction est appliquée sur un objet qu’elle n’est pas censée
TypeError
manipuler.
Il faut parfois convertir des chaînes de caractères de type unicode en chaînes de
UnicodeError caractères de type str et réciproquement. Cette exception intervient lorsque la
conversion n’est pas possible (voir également le paragraphe 7.8 page 168).
Cette exception survient lorsqu’une valeur est inappropriée pour une certaine
ValueError
opération, par exemple, l’obtention du logarithme d’un nombre négatif.
ZeroDivisionError Cette exception survient pour une division par zéro.
Table 5.1 : Exceptions standard les plus couramment utilisées. Ces exceptions sont définies par le langage Python.
Il n’est pas nécessaire d’inclure un module. La page http:// docs.python.org/ library/ exceptions.html recense toutes
les exceptions prédéfinies.
try :
5. Exceptions 123
Il est parfois utile d’associer un message à une exception afin que l’utilisateur ne soit pas perdu. Le
programme qui suit est identique au précédent à ceci près qu’il associe à l’exception ValueError qui
précise l’erreur et mentionne la fonction où elle s’est produite. Le message est ensuite intercepté plus bas.
try :
print inverse (0) # erreur
except ValueError, exc:
print "erreur, message : ", exc
Cette instruction lance l’exception exception_type associée au message message. Le message est
facultatif, lorsqu’il n’y en a pas, la syntaxe se résume à raise exception_type.
Et pour attraper cette exception et le message qui lui est associé, il faut utiliser la syntaxe 5.4 décrite au
paragraphe précédent.
Toutes les exceptions sont des cas particuliers de l’exception de type Exception. C’est pourquoi l’instruction
except Exception, e : attrape toutes les exceptions. L’instruction except ArithmeticError : attrape toutes
les erreurs de type ArithmeticError, ce qui inclut les erreurs de type ZeroDivisionError. Autrement dit,
toute exception de type ZeroDivisionError est attrapée par les instructions suivantes :
– except ZeroDivisionError :
– except ArithmeticError :
– except StandardError :
– except Exception :
Plus précisément, chaque exception est une classe qui dérive directement ou indirectement de la classe
Exception. L’instruction except ArithmeticError : par exemple attrape toutes les exceptions de type
ArithmeticError et toutes celles qui en dérivent comme la classe ZeroDivisionError (voir également le
paragraphe 5.2).
5. Exceptions 124
try :
try :
print inverses (0) # fonction inexistante --> exception NameError
print inverse (0) # division par zéro --> ZeroDivisionError
except NameError:
print "appel à une fonction non définie"
except ZeroDivisionError, exc:
print "erreur ", exc
En revanche, dans le second exemple, les deux lignes print inverse(0) et print inverses(0) ont été permu-
tées. La première exception déclenchée est la division par zéro. La première clause except n’interceptera
pas cette erreur puisqu’elle n’est pas du type recherché.
def inverse (x):
y = 1.0 / x
return y
try :
try :
print inverse (0) # division par zéro --> ZeroDivisionError
print inverses (0) # fonction inexistante --> exception NameError
except NameError:
print "appel à une fonction non définie"
except ZeroDivisionError, exc:
print "erreur ", exc
Une autre imbrication possible est l’appel à une fonction qui inclut déjà une partie de code protégée.
L’exemple suivant appelle la fonction inverse qui intercepte les exceptions de type ZeroDivisionError
pour retourner une grande valeur lorsque x = 0. La seconde exception générée survient lors de l’appel à la
fonction inverses qui déclenche l’exception NameError, elle aussi interceptée.
def inverse (x):
try :
y = 1.0 / x
except ZeroDivisionError, exc:
print "erreur ", exc
if x > 0 : return 1000000000
else : return -1000000000
return y
try :
print inverse (0) # division par zéro --> la fonction inverse sait gérer
print inverses (0) # fonction inexistante --> exception NameError
except NameError:
print "appel à une fonction non définie"
5. Exceptions 125
try :
s = "123a"
print s, " = ", conversion (s)
except AucunChiffre, exc :
# on affiche ici le commentaire associé à la classe d’exception
# et le message associé
print AucunChiffre.__doc__, " : ", exc
try :
s = "123a"
i = conversion (s)
print s, " = ", i
except AucunChiffre, exc :
print exc
print "fonction : ", exc.f
Etant donné que le programme déclenche une exception dans la section de code protégée, les deux derniers
affichages sont les seuls exécutés correctement. Ils produisent les deux lignes qui suivent.
exception AucunChiffre, depuis la fonction conversion avec le paramètre 123a
fonction : conversion
#...
class class_iter:
def __init__ (self,ins):
self._n = 0
self._ins = ins
def __iter__ (self) :
return self
def next (self):
if self._n <= 2:
v = self._ins [self._n]
self._n += 1
return v
else :
raise StopIteration
def __iter__(self):
return point_espace.class_iter (self)
# ...
Cet exemple montre seulement que les exceptions n’interviennent pas seulement lors d’erreurs mais font
parfois partie intégrante d’un algorithme.
valeur aberrante. Cette convention permet de signifier à celui qui appelle la fonction que son appel n’a
pu être traité correctement. Dans l’exemple qui suit, la fonction racine_carree retourne un couple de
résultats, True ou False pour savoir si le calcul est possible, suivi du résultat qui n’a un sens que si True
est retournée en première valeur.
def racine_carree(x) :
if x < 0 : return False, 0
else : return True, x ** 0.5
print racine_carree (-1) # (False, 0)
print racine_carree (1) # (True, 1.0)
Plutôt que de compliquer le programme avec deux résultats ou une valeur aberrante, on préfère souvent
déclencher une exception, ici, ValueError. La plupart du temps, cette exception n’est pas déclenchée. Il
est donc superflu de retourner un couple plutôt qu’une seule valeur.
def racine_carree(x) :
if x < 0 : raise ValueError ("valeur négative")
return x ** 0.5
print racine_carree (-1) # déclenche une exception
print racine_carree (1)
# coding: latin-1
def ajoute_resultat_division (nom, x, y) :
"""ajoute le résultat de la division x/y au fichier nom"""
f = open (nom, "a")
f.write (str (x) + "/" + str (y) + "= ") 5
f.write ( str ((float (x)/y)) + "\n" ) # exception si y == 0
f.close ()
Les écritures dans le fichier se font en mode ajout (”a”), le fichier ”essai.txt” contiendra tout ce qui aura
été écrit.
Il n’y pas de surprise du côté des affichages produit par l’instruction print. 1/0 précède 2/1. La différence
vient de la dernière ligne du fichier qui intervient de façon surprenante en dernière position. Lorsque y==0,
la fonction s’interrompt avant de fermer le fichier. C’est le garbage collector qui le fermera bien après
l’appel suivant qui ouvre à nouveau le fichier pour écrire 2/1= 2.0.
Chapitre 6
Modules
Il est souvent préférable de répartir le code d’un grand programme sur plusieurs fichiers. Parmi tous
ces fichiers, un seul est considéré comme fichier principal, il contient son point d’entrée, les premières
instructions exécutées. Les autres fichiers sont considérés comme des modules, en quelque sorte, des annexes
qui contiennent tout ce dont le fichier principal a besoin.
Cet exemple de module contient une fonction, une classe et une variable. Ces trois éléments peuvent être
utilisés par n’importe quel fichier qui importe ce module. Le nom d’un module correspond au nom du
fichier sans son extension.
class exemple_classe : 10 10
"""exemple de classe"""
def __str__ (self) :
return "exemple_classe"
Pour importer un module, il suffit d’insérer l’instruction import nom_module avant d’utiliser une des choses
qu’il définit. Ces importations sont souvent regroupées au début du programme, elles sont de cette façon
mises en évidence même s’il est possible de les faire n’importe où. L’exemple ci-dessus à droite importe
le module défini à gauche. Les modules commencent le plus souvent par une chaîne de caractères comme
dans l’exemple précédent, celle-ci contient l’aide associée à ce module. Elle apparaît avec l’instruction
help(module_exemple).
Rien ne différencie les deux fichiers module_exemple.py et exemple.py excepté le fait que le second utilise
des éléments définis par le premier. Dans un programme composé de plusieurs fichiers, un seul contient le
point d’entrée et tous les autres sont des modules.
6. Modules 129
La syntaxe d’appel d’un élément d’un module est identique à celle d’une classe. On peut considérer un
module comme une classe avec ses méthodes et ses attributs à la seule différence qu’il ne peut y avoir
qu’une seule instance d’un même module. La répétition de l’instruction import module_exemple n’aura
aucun effet : un module n’est importé que lors de la première instruction import nom_module rencontré lors
de l’exécution du programme.
Remarque 6.2 : fichier ∗.pyc
L’utilisation d’un module qu’on a soi-même conçu provoque l’apparition d’un fichier d’extension pyc. Il
correspond à la traduction du module en bytecode plus rapidement exploitable par l’interpréteur Python.
Ce fichier est généré à chaque modification du module. Lorsqu’un module est importé, Python vérifie la
date des deux fichiers d’extension py et pyc. Si le premier est plus récent, le second est recréé. Cela permet
un gain de temps lorsqu’il y a un grand nombre de modules. Il faut faire attention lorsque le fichier source
d’extension py est supprimé, il est alors encore possible de l’importer tant que sa version d’extension pyc
est présente.
Remarque 6.3 : recharger un module
Le module module_exemple contient une variable exemple_variable peut être modifiée au cours de l’exé-
cution du programme. Il est possible de revenir à sa valeur initiale en forçant Python à recharger le module
grâce à la fonction reload.
import module_exemple
module_exemple.exemple_variable = 10
reload (module_exemple)
print module_exemple.exemple_variable # affiche 3
c = alias.exemple_classe ()
print c
print alias.exemple_fonction () 5
help (alias)
Une autre syntaxe permet de se passer d’identificateur pour un module en utilisant le mot-clé from.
En utilisant la syntaxe from module import ∗, tous les identificateurs (fonctions, classes, variables) sont
directement accessibles sans les faire précéder d’un identificateur de module ainsi que le montre l’exemple
suivant.
Fichier : exemple3.py
from module_exemple import *
c = exemple_classe ()
print c
print exemple_fonction () 5
La partie import ∗ permet d’importer toutes les classes, attributs ou fonctions d’un module mais il est
possible d’écrire from module_exemple import exemple_class pour n’importer que cette classe. La fonction
exemple_fonction ne sera pas accessible. Toutefois, cette syntaxe est déconseillée. Elle réduit la longueur de
certaines fonctions ou classes puisqu’il n’est plus besoin de faire apparaître le module d’où elle proviennent
6. Modules 130
et cela ne permet plus de distinguer une classe ou une fonction définie dans ce fichier de celles définies
dans un autre module importé.
Il existe une dernière syntaxe d’importation d’un module qui est utile quand on ne sait pas encore au
moment d’écriture du programme le nom du module à importer. Celui-ci sera précisé à l’aide d’une chaîne
de caractères au moyen de la fonction __import__.
Fichier : exemple4.py
alias = __import__ ("module_exemple")
c = alias.exemple_classe ()
print c
print alias.exemple_fonction () 5
help (alias)
Cette astuce est régulièrement utilisée pour tester les fonctions et classes définies dans un module. Etant
donné que cette partie n’est exécutée que si ce fichier est le programme principal, ajouter du code après le
test if __name__ == ”__main__” : n’a aucune incidence sur tout programme incluant ce fichier comme
module.
La variable sys.path contient les répertoires où Python va chercher les modules. Le premier d’entre eux
est le répertoire du programme. Il suffit d’ajouter à cette liste le répertoire désiré, ici, un répertoire appelé
common situé au même niveau que le répertoire du programme. A ce sujet, il est conseillé d’utiliser le plus
souvent possible des chemins relatifs et non absolus 1 . De cette façon, on peut recopier le programme et
ses modules à un autre endroit du disque dur sans altérer leur fonctionnement.
1. Depuis un répertoire courant, les chemins relatifs permettent de faire référence à d’autres répertoires sans avoir à
prendre en compte leur emplacement sur le disque dur contrairement aux chemins absolus comme C:/Python26/python.exe.
6. Modules 131
# on importe un module
mod = import_fichier (r"D:\Dupre\informatique\programme\corde.py")
# on affiche l’aide associée
help (mod) 20
Lorsque le programme stipule l’import d’un module, Python vérifie s’il n’est pas déjà présent dans cette
liste. Dans le cas contraire, il l’importe. Chaque module n’est importé qu’une seule fois. La première ins-
truction import module_exemple rencontrée introduit une nouvelle entrée dans le dictionnaire sys.modules :
Le dictionnaire sys.modules peut être utilisé pour vérifier la présence d’un module ou lui assigner un autre
identificateur. Un module est un objet qui n’autorise qu’une seule instance.
2. grâce à la fonction exec
6. Modules 132
if "module_exemple" in sys.modules :
m = sys.modules ["module_exemple"]
m.exemple_fonction ()
Ces attributs sont accessibles si le nom du module est utilisé comme préfixe. Sans préfixe, ce sont ceux du
module lui-même.
import os
print os.__name__, os.__doc__
if __name__ == "__main__" : print "ce fichier est le point d’entrée"
else : print "ce fichier est importé"
import mesmodules.extension
import mesmodules.part1.niveaudeux
import mesmodules.part2.niveaudeuxbis
Lors de la première instruction import mesmodules.extension, le langage Python ne s’intéresse pas qu’au
seul fichier extension.py, il exécute également le contenu du fichier __init__.py. Si cela est nécessaire,
c’est ici qu’il faut insérer les instructions à exécuter avant l’import de n’importe quel module du paquetage.
6. Modules 133
Certains de ces modules sont présentés dans les chapitres qui suivent. Le programme suivant par exemple
utilise les modules random et math pour estimer le nombre pi. Pour cela, on tire aléatoirement
deux nombres
√
p
2 2
(x, y) dans l’intervalle [0, 1], si x + y ⩽ 1, on compte 1 sinon 0. Au final, π̂ = 𝔼 11 x2 +y2 ⩽1 .
n o
# coding: latin-1
import random
import math
somme = 0 5
nb = 1000000
for i in range (0,nb) :
x = random.random () # nombre aléatoire entre [0,1]
y = random.random ()
r = math.sqrt (x*x + y*y) # racine carrée 10
if r <= 1 : somme += 1
√
√ f (x) = x qui consiste à tirer des
Le programme suivant calcule l’intégrale de Monte Carlo de la fonction
nombres aléatoires dans l’intervalle a, b puis à faire la moyenne des x obtenu.
import random # import du module random : simulation du hasard
import math # import du module math : fonctions mathématiques
Le programme suivant utilise le module urllib pour télécharger le contenu d’une page et l’afficher.
def get_page_html (url):
import urllib
d = urllib.urlopen(url)
res = d.read ()
d.close ()
return res
url = "http://www.lemonde.fr"
print get_page_html (url)
Néanmoins, de nombreux modules ont été conçus pour un besoin spécifique et ne sont plus maintenus.
Cela convient lors de l’écriture d’un programme qui remplit un besoin ponctuel. Pour une application
plus ambitieuse, il est nécessaire de vérifier quelques informations comme la date de création, celle de la
dernière mise à jour, la présence d’une documentation, une prévision pour la sortie de la future version,
si c’est une personne lambda qui l’a conçu ou si c’est une organisation comme celle qui fournit le module
pygoogledesktop 5 . C’est le cas de tous les modules présentés au pragraphe 1.6.
Concernant leur installation, certains modules externes comme SciPy peuvent être installés à l’aide d’un
fichier exécutable sous Windows. Il suffit d’exécuter ce fichier pour pouvoir se servir du module par la
suite dans un programme Python. Ce mode d’installation est disponible pour la plupart des modules de
taille conséquente.
D’autres modules apparaissent compressés dans un fichier. Une fois décompressés, ils incluent un fichier
setup.py. Le langage Python fournit une procédure d’installation standard : il suffit d’écrire quelques
lignes dans une fenêtre de commande ouverte dans le répertoire où a été décompressé le fichier contenant
le module à installer.
c:\python26\python setup.py install
6.4.2 Langage C
Il existe deux façons de construire un programme mêlant Python et langage C. La première méthode est la
réalisation de modules Python écrits en C ou C++. A moins de devoir construire un module le plus efficace
5. http://code.google.com/p/pythongoogledesktop/
6. http://www.jython.org/Project/
6. Modules 136
possible, il est peu conseillé de le faire directement car cela implique de gérer soi-même le référencement
des variables créées en C ou C++ et exportées vers Python 7 . Il est préférable d’utiliser des outils comme
la librairie SWIG 8 , ou encore la librairie Boost Python 9 qui simplifie le travail d’intégration. C’est cette
dernière option qui est détaillée dans ce livre.
L’utilisation d’un autre langage est en effet indispensable lorsque l’exigence de rapidité est trop grande.
Les modules scientifiques tels que scipy ou numpy ne répondent pas à tous les besoins. Il faut parfois
développer soi-même des outils numériques en C++ 10 même si la partie interface est assurée par Python.
L’autre façon de mélanger les deux langages est l’inclusion de petites parties C dans un programme Python.
Ce point a déjà été évoqué au paragraphe 1.6.7 (page 21). Cela permet une optimisation essentiellement
locale et numérique mais pas l’utilisation sous Python d’un projet existant programmé en C++ ou C. Sous
Microsoft Windows, il existe également une version du langage IronPython 11 développée pour ce système
d’exploitation. L’intégration des DLL y est plus facile.
D’une manière générale, il est préférable de scinder nettement les parties d’un même programme qui
s’occupent de calculs numériques de celles qui prennent en charge l’interface graphique. Construire un
module écrit en C++ pour gérer les calculs numériques est une façon drastique et efficace d’opérer cette
scission.
#include <Python.h>
#include <stdio.h>
/**
Une fois importe, le module definit deux fonctions : 5
- exemple qui prend une chaine en argument et retourne 0
- exemple2 qui leve une exception creee pour l’occasion
*/
10
7. Il faut tenir compte dans le module en C des opérations nécessaires au garbage collector.
8. http://www.swig.org/
9. http://www.boost.org/doc/libs/1_36_0/libs/python/doc/index.html
10. en finance par exemple
11. http://ironpython.net/
12. http://www.mingw.org/
6. Modules 137
////////////////////////////////////////////////////
//////////// export des fonctions //////////////////
////////////////////////////////////////////////////
35
const char * module_name = "sample_module" ;
char buffer [100] ;
PyMODINIT_FUNC initsample_module(void) 45
{
PyObject* m ;
m = Py_InitModule (module_name, fonctions) ;
50
sprintf (buffer, "%s.Exception", module_name) ;
ExempleErreur = PyErr_NewException(buffer, NULL, NULL) ;
Py_INCREF (ExempleErreur) ;
PyModule_AddObject (m, "Exception", ExempleErreur) ;
} 55
Le module suivant évite l’installation manuelle d’un module écrit en C++. Il détecte que le module a été
modifié et qu’il doit être recompilé. Toutefois, il ne gère qu’un seul fichier.
# coding: latin-1
"""
import d’un module C (un fichier), inclut la recompilation
si le module a évolué depuis sa dernière compilation
""" 5
def import_c_module (name, mingw = r"c:\MinGW\bin", cpp = True, remove = False, path = None) :
"""
@ingroup SQLAppModels 25
6. Modules 138
@warning remove = True must be used when the module is compiled for the first time.
Otherwise, the module is already loaded and impossible to remove until Python is closed. 35
"""
if os.path.splitext (name) [1] == "" :
if cpp :
name += ".cpp"
ext = "cpp" 40
else :
name += ".c"
ext = "c"
else :
ext = os.path.splitext (name) [1] [1:] 45
mfile = name
mod = os.path.splitext (mfile) [0]
if path != None :
mypath = os.path.normpath (os.path.realpath (path)) 50
allpath = [mypath, "."]
else : allpath = ["."]
for p in sys.path :
if p not in allpath : allpath.append (p) 55
if cond :
mfile = mod
95
file = ("""
# coding: latin-1
from distutils.core import setup
from distutils.core import Extension
100
setup(name = ’%s’,
version = ’0.1’,
ext_modules = [Extension(’%s’, [’%s.%s’]), ],
url = ’’,
author = ’’, 105
author_email = ’...’,
)
""" % (mfile,mfile,mfile,ext)).replace (" ", "")
wr = os.path.join (path, "setup.py")
f = open (wr, "w") 110
f.write (file)
f.close ()
cwd = os.getcwd ()
os.chdir (path)
120
py = sys.executable.replace ("pythonw.exe", "python.exe")
if sys.platform == "win32" :
cmd = "%s setup.py build_ext --inplace -c mingw32" % (py)
else : cmd = "%s setup.py build_ext --inplace" % (py)
child_stdin, stdout, child_stderr = os.popen3 (cmd) 125
res = stdout.read ()
err = child_stderr.read ()
stdout.close ()
else : 145
mfile = mod
mod = __import__ (mfile)
return mod
print sample_module.exemple("e")
print sample_module.exemple2() 155
6. Modules 140
6.6.1 Exemple
Vous pourrez trouver à l’adresse suivante 17 l’ensemble des sources nécessaires à la réalisation d’un module
avec Boost Python 18 . Ce projet est configuré pour Microsoft Visual Studio C++ et définit un premier
module appelé PythonSample qui contient quelques fonctions et une classe. Le résultat est une DLL
(Dynamic Link Library) qui sera appelée par le langage Python. Cet exemple est conçu pour Windows.
Un point important est l’utilisation de la librairie c : \python26\python26.lib qui explique qu’un module
compilé pour une version donnée de Python ne puisse être utilisé avec une autre version : un module
compilé avec la version 2.4 ne fonctionnera pas avec la version 2.6.
L’exemple proposé contient également différentes fonctions de conversion des structures Python vers des
containers de la STL ou Standard Template Library. Il suffit d’ouvrir le fichier pythonsample.sln avec Micro-
soft Visual C++ pour avoir accès au code source, c’est ce que montre la figure 6.2. Le fichier exesample.sln
définit un programme exécutable permettant de tester le module sans Python (voir figure 6.3). Les para-
graphes qui suivent présentent quelques éléments de syntaxe et se concluent par un exercice qui consiste
à ajouter une fonction au module.
sa conception.
Une fois le module prêt, il est utilisable dans tout programme Python. Il suffit de l’importer. Lors de
cette étape, une fonction va être exécutée pour indiquer au langage Python l’ensemble des classes et des
fonctions présentes dans le module.
L’initialisation d’un module correspond à la déclaration au langage Python de l’ensemble des classes et
des fonctions à exporter. Cette partie peut contenir des tâches qui doivent être faites la première fois que
Python importe le module (initialisation de variables globales par exemple, chargement de données, ...).
L’objectif de cette initialisation est de construire pour chaque fonction ou classe ou méthode un objet
Python correspondant.
BOOST_PYTHON_MODULE(PythonSample)
{
// étapes d’initialisation
} ;
La fonction boost :: python :: def permet de déclarer une fonction. On peut y préciser les arguments, des
valeurs par défaut, un message d’aide retourné par l’instruction Python help.
int fonction (int r, const char * s, double x = 4) ;
BOOST_PYTHON_MODULE(PythonSample)
6. Modules 142
{
boost::python::def ("fonction", fonction,
(boost::python::arg ("r"),
boost::python::arg ("s"),
boost::python::arg ("x") = 4),
"aide associée à la fonction") ;
} ;
La surcharge de fonction est possible avec Boost Python mais elle ne sera pas détaillée ici. Le langage
Python n’autorise pas la surcharge, la rendre possible revient à créer une fonction qui appellera telle ou
telle autre fonction selon le type et le nombre des paramètres qu’elle aura reçus.
La déclaration d’une classe suit le même schéma que pour une fonction mais avec le template boost :: python :: class_.
Lorsque le constructeur n’a pas d’argument, la déclaration suit le schéma suivant :
boost::python::class_<PythonClassSample> obj (
"ClassSample",
"help on PythonClassSample") ) ;
Le template class_ est instancié sur la classe à exporter, ici PythonClassSample. On crée un objet obj de
type boost :: python :: class_ < PythonClassSample >. Son constructeur prend comme arguments :
1. Le nom de la classe en Python.
2. L’aide qui lui est associée. Ce second paramètre est facultatif mais il est conseillé de le renseigner.
Au cas où la classe aurait un constructeur avec des paramètres, il faudrait ajouter le code suivant :
boost::python::class_<PythonClassSample> obj (
"ClassSample",
"help on PythonClassSample",
boost::python::init<int,const char*> ( // ajout
(boost::python::arg ("a"), // ajout
boost::python::arg ("s") = "default value for s"), // ajout
"help on PythonClassSample constructor" ) // ajout
) ;
C’est-à-dire un autre template boost :: python :: init < ... > dont les arguments sont les types des para-
mètres du constructeur, c’est-à-dire entier et chaîne de caractères dans l’exemple précédent. La suite est
identique à la déclaration d’une fonction. Il est également possible de définir un héritage de la manière qui
suit :
6. Modules 143
boost::python::class_<PythonClassSample,
boost::python::bases<ClassBase> > obj (
"ClassSample",
"help on PythonClassSample") ;
Le template class_ peut recevoir un autre argument qui est boost :: python :: bases < ClassBase > avec
ClassBase désignant la classe mère de la classe déclarée. Cette dernière doit avoir été déclarée au préalable
avec cette même syntaxe boost :: python :: class_.
boost::python::class_<PythonClassSample,
boost::python::bases<ClassBase> > obj (
// ...
La déclaration d’une classe passe par la déclaration d’une instance du template class_. Dans les exemples
utilisés, cette instance porte le nom obj. C’est cette instance qui va permettre de déclarer une méthode
exactement de la même manière qu’une fonction :
obj.def ( "Random",
&PythonClassSample::Random,
(boost::python::arg ("pos")),
"help on the method") ;
Cette même méthode def permet également de surcharge le constructeur au cas où la classe pourrait être
initialisée de différentes manières. Cela donne :
boost::python::class_<MyVector> obj ("Vector", "help on vector",
boost::python::init<int> (
(PY_ARG ("n")),
"crée un vecteur de dimension n")) ;
// ajout d’un constructeur sans paramètre
obj.def (boost::python::init<MyVector> ()) ;
Le premier paramètre sera son nom sous Python, le second le paramètre lui-même, le troisième l’aide
associée. La méthode def_readwrite permet d’exporter un attribut de sorte qu’il soit lisible et modifiable.
6. Modules 144
Son résultat est une référence sur un objet existant. Si aucune mention spécifique n’est précisée lors de
l’export de la méthode (avec def), lors d’un appel à l’opérateur +=, Python va considérer que le résultat est
un objet différent alors qu’il s’agit de deux identificateurs Python faisant référence au même objet C++.
C’est pour cela qu’on ajoute l’argument suivant à la méthode def :
x.def ("__iadd__",
&PythonClassSample::__iadd__,
boost::python::return_internal_reference<>(),
"addition") ;
De cette façon, il y aura bien deux identificateurs Python faisant référence au même objet C++. C’est
aussi cette syntaxe qui permet de déclarer une fonction retournant un pointeur sur un objet C++.
Voici ce fichier pour l’exemple présenté au paragraphe 6.6.1. La fonction importante de ce fichier est
load_dynamic qui charge la DLL en mémoire au cas où elle n’aurait jamais été chargée.
import sys
if "PythonSample" not in sys.modules :
PythonSample = imp.load_dynamic (’PythonSample’, PythonSample.dll)
sys.modules ["PythonSample"] = PythonSample
De cette façon, passer d’une DLL à l’autre est simple et permet de débugger, tâche qu’introduit le pa-
ragraphe suivant. Si le programme Python s’étale sur plusieurs fichiers, il est plus pratique d’ajouter au
début de chacun fichier qui utilise le module C++ :
import sys
if "PythonSampled" in sys.modules : PythonSample = sys.modules ["PythonSampled"]
else : import PythonSample
6.6.7 Debuggage
Il ne serait pas pensable de développer des outils sans pouvoir utiliser un débuggeur. Dans l’exemple
présenté au paragraphe 6.6.1, le fichier testd.py n’utilise pas le module compilé dans sa version release
mais dans sa version debug . L’exécution est plus lente mais permet de faire appel au débuggeur pour
corriger une éventuelle erreur d’exécution.
Une manière de débugger consiste à momentanément arrêter l’exécution du programme Python (à l’aide
de la fonction raw_input par exemple), le temps d’attacher le débuggeur de Microsoft Visual C++ au
processus dont le nom est python.exe ou pythonw.exe. Il ne reste plus qu’à placer des pointeurs d’arrêt
puis à continuer l’exécution du programme Python jusqu’à ce que ces pointeurs d’arrêt soient atteints.
Une seconde méthode consiste à volontairement insérer dans le code C++ du module l’instruction __debugbreak().
Lorsque le module est exécuté via un programme Python et qu’il arrive sur cette ligne, il vous demande
l’autorisation de continuer l’exécution via un débuggeur. Il faudra penser à enlever la ligne une fois le
module corrigé.
Il reste toutefois une dernière façon plus classique de débugger qui est l’ajout dans le code d’instructions
écrivant des messages dans un fichier texte. On écrit des logs ou des traces. De cette façon, lorsque le
6. Modules 146
programme provoque une erreur, on connaît la séquence de messages générés par le programme. S’ils sont
suffisamment explicites, cela permet de corriger l’erreur. Sinon, l’ajout de messages supplémentaires puis
une nouvelle exécution du programme permettront d’être plus précis quant à la cause de l’erreur.
Outre ces trois méthodes de débuggage, il est possible qu’un module doivent retourner un code d’erreur
ou générer une exception qui soit attrapée par l’interpréteur Python puis afficher. C’est l’objet des lignes
qui suivent :
if (<condition>)
throw std::runtime_error ("message d’erreur") ;
Toute exception dérivant de runtime_error sera interceptée par l’interpréteur Python. La librairie Boost
Python génère également des exceptions notamment lorsque les paramètres envoyés à une fonction du
module ne correspondent pas avec sa signature.
En terme d’architecture, il est conseillé de bien scinder la partie utilisant Boost Python des algorithmes.
Boost Python n’est qu’une passerelle vers Python. Il pourrait être nécessaire de construire une autre passe-
relle vers d’autres langages tel que le Visual Basic pour Applications (VBA). De même, outre la passerelle
vers Python, il est utile de construire un programme exécutable permettant de tester les algorithmes écrits
en C++ avant de les exporter. Cela réduit beaucoup le temps de débuggage qui peut alors gérer séparément
la partie C++ de la partie interfaçage entre Python et C++.
21. http://www.boost.org/libs/python/doc/
6. Modules 147
Figure 6.4 : Six fichiers modifiés pour ajouter une fonction dans un module Python écrit en C++ à partir de
l’exemple accessible depuis l’adresse http:// www.xavierdupre.fr/ .
Chapitre 7
Fichiers, expressions régulières, dates
Lorsqu’un programme termine son exécution, toutes les informations stockées dans des variables sont
perdues. Un moyen de les conserver est de les enregistrer dans un fichier sur disque dur. A l’intérieur de
celui-ci, ces informations peuvent apparaître sous un format texte qui est lisible par n’importe quel éditeur
de texte, dans un format compressé, ou sous un autre format connu par le concepteur du programme.
On appelle ce dernier type un format binaire, il est adéquat lorsque les données à conserver sont très
nombreuses ou lorsqu’on désire que celles-ci ne puissent pas être lues par une autre application que le
programme lui-même. En d’autres termes, le format binaire est illisible excepté pour celui qui l’a conçu.
Ce chapitre abordera pour commencer les formats texte, binaire et compressé (zip) directement manipu-
lable depuis Python. Les manipulations de fichiers suivront pour terminer sur les expressions régulières
qui sont très utilisées pour effectuer des recherches textuelles. A l’issue de ce chapitre, on peut envisager
la recherche à l’intérieur de tous les documents textes présents sur l’ordinateur, de dates particulières, de
tous les numéros de téléphones commençant par 06... En utilisant des modules tels que reportlab 1 ou
encore win32com 2 , il serait possible d’étendre cette fonctionnalité aux fichiers de type pdf et aux fichiers
Microsoft Word, Excel.
7.1.1 Ecriture
La première étape est l’écriture. Les informations sont toujours écrites sous forme de chaînes de caractères
et toujours ajoutées à la fin du fichier qui s’allonge jusqu’à ce que toutes les informations y soient écrites.
L’écriture s’effectue toujours selon le même schéma.
1. création ou ouverture du fichier,
2. écriture,
3. fermeture.
Lors de l’ouverture, le fichier dans lequel seront écrites les informations est créé s’il n’existe pas ou nettoyé
s’il existe déjà. La fermeture permet à d’autres programmes de lire ce que vous avez placé dans ce fichier.
Sans cette dernière étape, il sera impossible d’y accéder à nouveau pour le lire ou y écrire à nouveau. A
l’intérieur d’un programme informatique, écrire dans un fichier suit toujours le même schéma :
1. http://www.reportlab.com/software/opensource/rl-toolkit/
2. http://python.net/crew/mhammond/win32/Downloads.html
7. Fichiers, expressions régulières, dates 149
f.close () # fermeture
Les étapes d’ouverture et de fermeture sont toujours présentes en ce qui concerne les fichiers. Il s’agit
d’indiquer au système d’exploitation que le programme souhaite accéder à un fichier et interdire à tout
autre programme l’accès à ce fichier. Un autre programme qui souhaiterait créer un fichier du même nom
ne le pourrait pas tant que l’étape de fermeture n’est pas exécutée. En revanche, il pourrait tout à fait le
lire car la lecture ne perturbe pas l’écriture.
Remarque 7.1 : fichiers ouverts et non fermés
Lorsque que le programme se termine, même s’il reste des fichiers "ouverts" pour lesquels la méthode
close n’a pas été appelée, ils seront automatiquement fermés.
Certains caractères sont fort utiles lors de l’écriture de fichiers texte afin d’organiser les données. Le
symbole ; est très utilisé comme séparateur de colonnes pour une matrice, on utilise également le passage
à la ligne ou la tabulation. Comme ce ne sont pas des caractères "visibles", ils ont des codes :
\n passage à la ligne
\t insertion d’une tabulation, indique un passage à la colonne suivante dans le logiciel Microsoft
Excel
Il existe peu de manières différentes de conserver une matrice dans un fichier, le programme ressemble
dans presque tous les cas à celui qui suit :
mat = ... # matrice de type liste de listes
f = open ("mat.txt", "w")
for i in range (0,len (mat)) : # la fonction join est aussi
for j in range (0, len (mat [i])) : # fréquemment utilisée
f.write ( str (mat [i][j]) + "\t") # pour assembler les chaînes
f.write ("\n") # un une seule et réduire le
f.close () # nombre d’appels à f.write
d’informations avant de les écrire physiquement sur le disque dur. Les informations sont placées dans un
tampon ou buffer. Lorsque le tampon est plein, il est écrit sur disque dur. Pour éviter ce délai, il faut soit
fermer puis réouvrir le fichier soit appeler la méthode flush qui ne prend aucun paramètre. Ce mécanisme
vise à réduire le nombre d’accès au disque dur, il n’est pas beaucoup plus long d’y écrire un caractère
plutôt que 1000 en une fois.
Pour comprendre la différence entre ces deux modes d’ouverture, voici deux programmes. Celui de gauche
n’utilise pas le mode ajout tandis que celui de droite l’utilise lors de la seconde ouverture.
Le premier programme crée un fichier ”essai.txt” qui ne contient que les informations écrites lors de
la seconde phase d’écriture, soit seconde fois. Le second utilise le mode ajout lors de la seconde ouver-
ture. Le fichier ”essai.txt”, même s’il existait avant l’exécution de ce programme, est effacé puis rempli
avec l’information premiere fois. Lors de la seconde ouverture, en mode ajout, une seconde chaîne de
caractères est ajoutée. le fichier ”essai.txt”, après l’exécution du programme contient donc le message :
premiere fois seconde fois.
Remarque 7.5 : fichier de traces
Un des moyens pour comprendre ou suivre l’évolution d’un programme est d’écrire des informations dans
un fichier ouvert en mode ajout qui est ouvert et fermé sans cesse. Ce sont des fichiers de traces ou de log.
Ils sont souvent utilisés pour vérifier des calculs complexes. Ils permettent par exemple de comparer deux
versions différentes d’un programme pour trouver à quel endroit ils commencent à diverger.
7.1.3 Lecture
La lecture d’un fichier permet de retrouver les informations stockées grâce à une étape préalable d’écriture.
Elle se déroule selon le même principe, à savoir :
1. ouverture du fichier en mode lecture,
2. lecture,
3. fermeture.
Une différence apparaît cependant lors de la lecture d’un fichier : celle-ci s’effectue ligne par ligne alors
que l’écriture ne suit pas forcément un découpage en ligne. Les instructions à écrire pour lire un fichier
diffèrent rarement du schéma qui suit où seule la ligne indiquée par (∗) change en fonction ce qu’il faut
faire avec les informations lues.
f = open ("essai.txt", "r") # ouverture du fichier en mode lecture
for ligne in f : # pour toutes les lignes du fichier
7. Fichiers, expressions régulières, dates 151
Pour des fichiers qui ne sont pas trop gros (< 100000 lignes), il est possible d’utiliser la méthode readlines
qui récupère toutes les lignes d’un fichier texte en une seule fois. Le programme suivant donne le même
résultat que le précédent.
f = open ("essai.txt", "r") # ouverture du fichier en mode lecture
l = f.readlines () # lecture de toutes les lignes, placées dans une liste
f.close () # fermeture du fichier
Les informations peuvent être structurées de façon plus élaborée dans un fichier texte, c’est le cas des
formats HTML ou XML. Pour ce type de format plus complexe, il est déconseillé de concevoir soi-même
un programme capable de les lire, il existe presque toujours un module qui permette de le faire. C’est le
cas du module HTMLParser pour le format HTML ou xml.sax pour le format XML. De plus, les modules
sont régulièrement mis à jour et suivent l’évolution des formats qu’ils décryptent.
Un fichier texte est le moyen le plus simple d’échanger des matrices avec un tableur et il n’est pas besoin
de modules dans ce cas. Lorsqu’on enregistre une feuille de calcul sous format texte, le fichier obtenu est
organisé en colonnes : sur une même ligne, les informations sont disposées en colonne délimitées par un
séparateur qui est souvent une tabulation (\t) ou un point virgule comme dans l’exemple suivant :
Pour lire ce fichier, il est nécessaire de scinder chaque ligne en une liste de chaînes de caractères, on utilise
pour cela la méthode split des chaînes de caractères.
mat = [] # création d’une liste vide,
f = open ("essai.txt", "r") # ouverture du fichier en mode lecture
for li in f : # pour toutes les lignes du fichier
s = li.strip ("\n\r") # on enlève les caractères de fin de ligne
l = s.split (";") # on découpe en colonnes
mat.append (l) # on ajoute la ligne à la matrice
f.close () # fermeture du fichier
Ce format de fichier texte est appelé CSV 3 , il peut être relu depuis un programme Python comme le
montre l’exemple précédent ou être chargé depuis Microsoft Excel en précisant que le format du fichier
est le format CSV. Pour les valeurs numériques, il ne faut pas oublier de convertir en caractères lors de
l’écriture et de convertir en nombres lors de la lecture.
3. Comma Separated Value
7. Fichiers, expressions régulières, dates 152
7.2.1 Lecture
L’exemple suivant permet par exemple d’obtenir la liste des fichiers inclus dans un fichier zip :
import zipfile
file = zipfile.ZipFile ("exemplezip.zip", "r")
for info in file.infolist () :
print info.filename, info.date_time, info.file_size
file.close ()
Les fichiers compressés ne sont pas forcément des fichiers textes mais de tout format. Le programme suivant
extrait un fichier parmi ceux qui ont été compressés puis affiche son contenu (on suppose que le fichier lu
est au format texte donc lisible).
import zipfile
file = zipfile.ZipFile ("exemplezip.zip", "r")
data = file.read ("informatique/testzip.py")
file.close ()
print data
On retrouve dans ce cas les étapes d’ouverture et de fermeture même si la première est implicitement
inclus dans le constructeur de la classe ZipFile.
7.2.2 Ecriture
Pour créer un fichier zip, le procédé ressemble à la création de n’importe quel fichier. La seule différence
provient du fait qu’il est possible de stocker le fichier à compresser sous un autre nom à l’intérieur du fichier
zip, ce qui explique les deux premiers arguments de la méthode write. Le troisième paramètre indique si
le fichier doit être compressé (zipfile.ZIP_DEFLATED) ou non (zipfile.ZIP_STORED).
import zipfile
file = zipfile.ZipFile ("test.zip", "w")
file.write ("fichier.txt", "nom_fichier_dans_zip.txt", zipfile.ZIP_DEFLATED)
file.close ()
Une utilisation possible de ce procédé serait l’envoi automatique d’un mail contenant un fichier zip en
pièce jointe. Une requête comme python précédant le nom de votre serveur de mail permettra, via un
moteur de recherche, de trouver des exemples sur Internet.
4. D’autres formats proposent de meilleurs taux de compressions sur les fichiers textes existent, voir http://www.7-zip.org/.
7. Fichiers, expressions régulières, dates 153
Selon les serveurs de mails, le programme permettant d’envoyer automatiquement un mail en Python peut
varier. L’exemple suivant permet d’envoyer un email automatiquement via un serveur de mails, il montre
aussi comment attacher des pièces jointes. Il faut bien sûr être autorisé à se connecter. De plus, il est
possible que l’exécution de ce programme ne soit pas toujours couronnée de succès si le mail est envoyé
plusieurs fois à répétition, ce comportement est en effet proche de celui d’un spammeur.
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import formatdate 5
from email import Encoders
import os
res.append (r)
folder = r"D:\Dupre\_data\informatique" 35
filter = "*.tex"
file,fold = liste_fichier_repertoire (folder, filter)
Le programme repose sur l’utilisation d’une fonction récursive qui explore d’abord le premier répertoire.
Elle se contente d’ajouter à une liste les fichiers qu’elle découvre puis cette fonction s’appelle elle-même
sur le premier sous-répertoire qu’elle rencontre. La fonction walk permet d’obtenir la liste des fichiers et
des sous-répertoire. Cette fonction parcourt automatiquement les sous-répertoires inclus, le programme
est plus court mais elle ne prend pas en compte le filtre qui peut être alors pris en compte grâce aux
expressions régulières (paragraphe 7.6).
# coding: latin-1
import os
Ou la lecture :
1. On lit une suite d’octets depuis un fichier (méthode read affiliée aux fichiers).
2. On transforme cette suite d’octets pour retrouver l’information qu’elle formait initialement (fonction unpack
du module struct).
L’utilisation de fichiers binaires est moins évidente qu’il n’y paraît et il faut faire appel à des modules
spécialisés alors que la gestion des fichiers texte ne pose aucun problème. Cela vient du fait que Python ne
donne pas directement accès à la manière dont sont stockées les informations en mémoire contrairement à
des langages tels que le C++. L’intérêt de ces fichiers réside dans le fait que l’information qu’ils contiennent
prend moins de place stockée en binaire plutôt que convertie en chaînes de caractères au format texte 9 .
L’écriture et la lecture d’un fichier binaire soulèvent les mêmes problèmes que pour un fichier texte : il faut
organiser les données avant de les enregistrer pour savoir comment les retrouver. Les types immuables (réel,
entier, caractère) sont assez simples à gérer dans ce format. Pour les objets complexes, Python propose
une solution grâce au module pickle.
# écriture
file = open ("info.bin", "wb")
file.write ( struct.pack ("i" , i) ) 10
file.write ( struct.pack ("d" , x) )
file.write ( struct.pack ("cccc" , *s) )
file.close ()
# lecture 15
file = open ("info.bin", "rb")
i = struct.unpack ("i", file.read (4))
x = struct.unpack ("d", file.read (8))
s = struct.unpack ("cccc", file.read (4))
file.close () 20
# affichage pour vérifier que les données ont été bien lues
print i # affiche (10,)
print x # affiche (3.1415692000000002,)
print s # affiche (’A’, ’B’, ’C’, ’D’) 25
Les résultats de la méthode unpack apparaissent dans un tuple mais les données sont correctement récu-
pérées. Ce programme fait aussi apparaître une des particularité du format binaire. On suppose ici que
la chaîne de caractères est toujours de longueur 4. En fait, pour stocker une information de dimension
variable, il faut d’abord enregistrer cette dimension puis s’en servir lors de la relecture pour connaître le
nombre d’octets à lire. On modifie le programme précédent pour sauvegarder une chaîne de caractères de
longueur variable.
# coding: latin-1
import struct
# écriture
file = open ("info.bin", "wb")
file.write ( struct.pack ("i" , i) ) 10
file.write ( struct.pack ("d" , x) )
file.write ( struct.pack ("i" , len(s)) ) # on sauve la dimension de s
file.write ( struct.pack ("c" * len(s) , *s) )
file.close ()
15
# lecture
file = open ("info.bin", "rb")
i = struct.unpack ("i", file.read (4))
x = struct.unpack ("d", file.read (8))
l = struct.unpack ("i", file.read (4)) # on récupère la dimension de s 20
l = l [0] # l est un tuple, on s’intéresse à son unique élément
s = struct.unpack ("c" * l, file.read (l))
file.close ()
Cette méthode utilisée pour les chaînes de caractères est applicable aux listes et aux dictionnaires de
longueur variable : il faut d’abord stocker leur dimension. Il faut retenir également que la taille d’un réel
est de huit octets, celle d’un entier de quatre octets et celle d’un caractère d’un octet 12 . Cette taille doit
être passée en argument à la méthode read.
dico = {’a’: [1, 2.0, 3, "e"], ’b’: (’string’, 2), ’c’: None}
lis = [1, 2, 3]
12. Cette règle est toujours vrai sur des ordinateurs 32 bits. Cette taille varie sur les ordinateurs 64 bits qui commencent
à se répandre. Le programme suivant donnera la bonne réponse.
from struct import pack
print len(pack(0 i0 , 0)) # longueur d’un entier
print len(pack(0 d0 , 0)) # longueur d’un réel
print len(pack(0 c0 , ”e”)) # longueur d’un caractère
7. Fichiers, expressions régulières, dates 159
Un des avantages du module pickle est de pouvoir gérer les références circulaires : il est capable d’enre-
gistrer et de relire une liste qui se contient elle-même, ce peut être également une liste qui en contient une
autre qui contient la première...
Le module pickle peut aussi gérer les classes définies par un programmeur à condition qu’elles puissent
convertir leur contenu en un dictionnaire dans un sens et dans l’autre, ce qui correspond à la plupart des
cas.
import pickle
import copy
class Test :
def __init__ (self) : 5
self.chaine = "a"
self.entier = 5
self.tuple = { "h":1, 5:"j" }
t = Test () 10
Lorsque la conversion nécessite un traitement spécial, il faut surcharger les opérateurs __getstate__ et
__setstate__. Ce cas se produit par exemple lorsqu’il n’est pas nécessaire d’enregistrer tous les attributs
de la classe car certains sont calculés ainsi que le montre l’exemple suivant :
import pickle
import copy
class Test :
def __init__ (self) : 5
self.x = 5
self.y = 3
self.calcule_norme () # attribut calculé
def calcule_norme (self) :
self.n = (self.x ** 2 + self.y ** 2) ** 0.5 10
def __getstate__ (self) :
"""conversion de Test en un dictionnaire"""
d = copy.copy (self.__dict__)
del d ["n"] # attribut calculé, on le sauve pas
return d 15
def __setstate__ (self,dic) :
"""conversion d’un dictionnaire dic en Test"""
self.__dict__.update (dic)
self.calcule_norme () # attribut calculé
20
t = Test ()
Cette tâche est en plus exécutée sur deux répertoires et on ne voudrait pas avoir deux programmes différents
alors que la tâche est la même quelque soit le couple de répertoire choisi. On souhaite pouvoir lancer le
programme Python et lui spécifier les deux répertoires, c’est-à-dire être capable de lancer le programme
comme suit (voir également figure 7.1) :
Pour lancer un programme Python en ligne de commande, il faut écrire dans une fenêtre de commande ou
un programme d’extension bat les instructions suivantes séparées par des espaces :
interpréteur_python programme_python argument1 argument2 ...
Il faut maintenant récupérer ces paramètres au sein du programme Python grâce au module sys qui contient
une variable argv contenant la liste des paramètres de la ligne de commande. S’il y a deux paramètres,
cette liste argv contiendra trois éléments :
1. nom du programme
2. paramètre 1
3. paramètre 2
Le programme suivante utilise ce mécanisme avec le programme synchro contenant la fonction copie_repertoire :
# coding: latin-1
import glob
import shutil
def copie_repertoire (rep1, rep2) :
"""copie tous les fichiers d’un répertoire rep1 vers un autre rep2""" 5
li = glob.glob (rep1 + "/*.*")
7. Fichiers, expressions régulières, dates 161
for l in li :
to = l.replace (rep1, rep2) # nom du fichier copié
# (on remplace rep1 par rep2)
shutil.copy (l, to) 10
import sys
# sys.argv [0] --> nom du programme (ici, synchro.py)
rep1 = sys.argv [1] # récupération du premier paramètre
rep2 = sys.argv [2] # récupération du second paramètre 15
copie_repertoire (rep1, rep2)
Pour des besoins plus complexes, le module getopt propose des fonctions plus élaborées pour traiter
les paramètres de la ligne de commande. Il est également possible de lancer un programme en ligne de
commande depuis Python grâce à la fonction system du module os comme ceci :
import os
os.system ("c:\python26\python.exe c:\batch\synchro.py " \
"\"c:\Program Files\source\" \"c:\Program Files\backup\"")
Le programme suivant est un autre exemple de l’utilité de la fonction system. Il écrit une matrice au
format HTML 13 puis utilise la fonction system pour lancer le navigateur Firefox ou Internet Explorer afin
de pouvoir visualiser la page créée.
mat = ["Victor Hugo 6".split (), "Marcel Proust 3".split () ]
f = open ("tableau.html", "w")
f.write ("<body><html>\n")
f.write ("<table border=\"1\">\n")
for m in mat : 5
f.write ("<tr>")
for c in m :
f.write ("<td>" + c + "</td>")
f.write ("</tr>\n")
f.write ("</table>") 10
f.close ()
import os
os.system ("\"C:\\Program Files\\Mozilla Firefox\\firefox.exe\" tableau.html")
os.system ("\"C:\\Program Files\\Internet Explorer\\iexplore.exe\"" \ 15
" d:\\temp\\tableau.html")
Lorsqu’on remplit un formulaire, on voit souvent le format "MM/JJ/AAAA" qui précise sous quelle forme
on s’attend à ce qu’une date soit écrite. Les expressions régulières permettent de définir également ce
format et de chercher dans un texte toutes les chaînes de caractères qui sont conformes à ce format.
La liste qui suit contient des dates de naissance. On cherche à obtenir toutes les dates de cet exemple
sachant que les jours ou les mois contiennent un ou deux chiffres, les années deux ou quatre.
s = """date 0 : 14/9/2000
date 1 : 20/04/1971 date 2 : 14/09/1913 date 3 : 2/3/1978
date 4 : 1/7/1986 date 5 : 7/3/47 date 6 : 15/10/1914
date 7 : 08/03/1941 date 8 : 8/1/1980 date 9 : 30/6/1976"""
Le premier chiffre du jour est soit 0, 1, 2, ou 3 ; ceci se traduit par [0 − 3]. Le second chiffre est compris
entre 0 et 9, soit [0 − 9]. Le format des jours est traduit par [0 − 3][0 − 9]. Mais le premier jour est facultatif,
ce qu’on précise avec le symbole ? : [0 − 3]?[0 − 9]. Les mois suivent le même principe : [0 − 1]?[0 − 9]. Pour
les années, ce sont les deux premiers chiffres qui sont facultatifs, le symbole ? s’appliquent sur les deux
premiers chiffres, ce qu’on précise avec des parenthèses : ([0-2][0-9]) ?[0-9][0-9]. Le format final d’une date
devient :
[0-3]?[0-9]/[0-1]?[0-9]/([0-2][0-9])?[0-9][0-9]
Le module re gère les expressions régulières, celui-ci traite différemment les parties de l’expression régulière
qui sont entre parenthèses de celles qui ne le sont pas : c’est un moyen de dire au module re que nous
nous intéressons à telle partie de l’expression qui est signalée entre parenthèses. Comme la partie qui nous
intéresse - une date - concerne l’intégralité de l’expression régulière, il faut insérer celle-ci entre parenthèses.
La première étape consiste à construire l’expression régulière, la seconde à rechercher toutes les fois qu’un
morceau de la chaîne s définie plus haut correspond à l’expression régulière.
import re
# première étape : construction
expression = re.compile ("([0-3]?[0-9]/[0-1]?[0-9]/([0-2][0-9])?[0-9][0-9])")
# seconde étape : recherche
res = expression.findall (s)
print res
C’est une liste de couples dont chaque élément correspond aux parties comprises entre parenthèses qu’on
appelle des groupes. Lorsque les expressions régulières sont utilisées, on doit d’abord se demander comment
définir ce qu’on cherche puis quelles fonctions utiliser pour obtenir les résultats de cette recherche. Les
deux paragraphes qui suivent y répondent.
7.6.2 Syntaxe
La syntaxe des expressions régulières est décrite sur le site officiel de Python 14 . Comme toute grammaire,
celle des expressions régulières est susceptible d’évoluer au fur et à mesure des versions du langage Python.
14. La page http://docs.python.org/library/re.html#regular-expression-syntax décrit la syntaxe, la page http://docs.python.
org/howto/regex.html#regex-howto décrit comment se servir des expressions régulières, les deux pages sont en anglais.
7. Fichiers, expressions régulières, dates 163
Lors d’une recherche, on s’intéresse aux caractères et souvent aux classes de caractères : on cherche un
chiffre, une lettre, un caractère dans un ensemble précis ou un caractère qui n’appartient pas à un ensemble
précis. Certains ensembles sont prédéfinis, d’autres doivent être définis à l’aide de crochets.
Pour définir un ensemble de caractères, il faut écrire cet ensemble entre crochets : [0123456789] désigne
un chiffre. Comme c’est une séquence de caractères consécutifs, on peut résumer cette écriture en [0-9].
Pour inclure les symboles −, +, il suffit d’écrire : [-0-9+]. Il faut penser à mettre le symbole − au début
pour éviter qu’il ne désigne une séquence.
Le caractère ^ inséré au début du groupe signifie que le caractère cherché ne doit pas être un de ceux
qui suivent. Le tableau suivant décrit les ensembles prédéfinis 15 et leur équivalent en terme d’ensemble de
caractères :
A l’instar des chaînes de caractères, comme le caractère \ est un caractère spécial, il faut le doubler : [\\].
Avec ce système, le mot "taxinomie" qui accepte deux orthographes s’écrira : tax[io]nomie.
Remarque 7.10 : caractères spéciaux et expressions régulières
Le caractère \ est déjà un caractère spécial pour les chaînes de caractères en Python, il faut donc le
quadrupler pour l’insérer dans un expression régulière. L’expression suivante filtre toutes les images dont
l’extension est png et qui sont enregistrées dans un répertoire image.
s = r"D:\Dupre\_data\informatique\support\vba\image/vbatd1_4.png"
print re.compile ( "[\\\\/]image[\\\\/].*[.]png").search(s) # résultat positif
print re.compile (r"[\\/]image[\\/].*[.]png").search(s) # même résultat
Les multiplicateurs permettent de définir des expressions régulières comme : un mot entre six et huit lettres
qu’on écrira [\w]{6, 8}. Le tableau suivant donne la liste des multiplicateurs principaux 17 :
L’algorithme des expressions régulières essaye toujours de faire correspondre le plus grand morceau à
l’expression régulière. Par exemple, dans la chaîne <h1>mot</h1>, <.*> correspond avec <h1>, </h1> ou
15. La page http://docs.python.org/library/re.html#regular-expression-syntax les recense tous.
16. Ces caractères sont spéciaux, les plus utilisés sont \t qui est une tabulation, \n qui est une fin de ligne et qui \r qui
est un retour à la ligne.
17. La page http://docs.python.org/library/re.html#regular-expression-syntax les recense tous.
7. Fichiers, expressions régulières, dates 164
encore <h1>mot</h1>. Par conséquent, l’expression régulière correspond à trois morceaux. Par défaut, il
prendra le plus grand. Pour choisir les plus petits, il faudra écrire les multiplicateurs comme ceci : ∗?, +?,
??.
import re
s = "<h1>mot</h1>"
print re.compile ("(<.*>)") .match (s).groups () # (’<h1>mot</h1>’,)
print re.compile ("(<.*?>)").match (s).groups () # (’<h1>’,)
7.6.2.3 Groupes
Lorsqu’un multiplicateur s’applique sur plus d’un caractère, il faut définir un groupe à l’aide de parenthèses.
Par exemple, le mot yoyo s’écrira : (yo){2}. Les parenthèses jouent un rôle similaire à celui qu’elles jouent
dans une expression numérique. Tout ce qui est compris entre deux parenthèses est considéré comme un
groupe.
On peut assembler les groupes de caractères les uns à la suite des autres. Dans ce cas, il suffit de les
juxtaposer comme pour trouver les mots commençant par s : s[a − z]∗. On peut aussi chercher une chaîne
ou une autre grâce au symbole |. Chercher dans un texte l’expression Xavier Dupont ou M. Dupont s’écrira :
(Xavier)|(M[.])Dupont.
7.6.3 Fonctions
La fonction compile du module re permet de construire un objet "expression régulière". A partir de
cet objet, on peut vérifier la correspondance entre une expression régulière et une chaîne de caractères
(méthode match). On peut chercher une expression régulière (méthode search). On peut aussi remplacer
une expression régulière par une chaîne de caractères (méthode sub). La table 7.3 récapitule ces méthodes.
Les méthodes search et match retournent toutes des objets Match dont les méthodes sont présentées par
la table 7.4. Appliquées à l’exemple décrit page 162 concernant les dates, cela donne :
expression = re.compile ("([0-3]?[0-9]/[0-1]?[0-9]/([0-2][0-9])?[0-9][0-9])[^\d]")
print expression.search (s).group(1,2) # affiche (’14/9/2000’, ’20’)
c = expression.search (s).span(1) # affiche (9, 18)
print s [c[0]:c[1]] # affiche 14/9/2000
group([g1, ...]) Retourne la chaîne de caractères validée par les groupes g1...
Retourne la liste des chaînes de caractères qui ont été validées par chacun des
groups([default])
groupes.
Retourne les positions dans la chaîne originale des chaînes extraites validées le
span([gr])
groupe gr.
Table 7.4 : Liste non exhaustive des méthodes qui s’appliquent à un objet de type Match qui est le résultat des
méthodes search et match. Les groupes sont des sous-parties de l’expression régulière, chacune d’entre elles incluses
entre parenthèses. Le nème correspond au groupe qui suit la nème parenthèse ouvrante. Le premier groupe a pour
indice 1. La page http:// docs.python.org/ library/ re.html contient la documentation associée au module re.
# coding: latin-1
import mutagen.mp3, mutagen.easyid3, os, re
import re
date = "05/22/2010"
exp = "([0-9]{1,2})/([0-9]{1,2})/(((19)|(20))[0-9]{2})"
com = re.compile (exp)
print com.search (date).groups () # (’05’, ’22’, ’2010’, ’20’, None, ’20’)
Il n’est pas toujours évident de connaître le numéro du groupe qui contient l’information à extraire. C’est
pourquoi il paraît plus simple de les nommer afin de les récupérer sous la forme d’un dictionnaire et non
plus sous forme de tableau. La syntaxe ( ?P<nom_du_groupe>expression) permet de nommer un groupe.
Elle est appliquée à l’exemple précédent.
exp = "(?P<jj>[0-9]{1,2})/(?P<mm>[0-9]{1,2})/(?P<aa>((19)|(20))[0-9]{2})"
com = re.compile (exp)
print com.search (date).groupdict () # {’mm’: ’22’, ’aa’: ’2010’, ’jj’: ’05’}
Le programme suivant est un exemple d’utilisation des expressions régulières dont l’objectif est de détecter
les fonctions définies dans un programme mais jamais utilisées. Les expressions servent à détecter les défi-
7. Fichiers, expressions régulières, dates 167
nitions de fonctions (d’après le mot-clé def) puis à détecter les appels. On recoupe ensuite les informations
en cherchant les fonctions définies mais jamais appelées.
Il n’est pas toujours évident de construire une expression régulière qui correspondent précisément à tous les
cas qu’on veut détecter. Une stratégie possible est de construire une expression régulière plus permissive
puis d’éliminer les cas indésirables à l’aide d’une seconde expression régulière, c’est le cas ici pour détecter
les appels.
# coding: latin-1
"""ce programme détermine toutes les fonctions définies dans
un programme et jamais appelées"""
import glob, os, re
5
def trouve_toute_fonction (s, exp, gr, expm = "^$") :
""" à partir d’une chaîne de caractères correspondant
à un programme Python, cette fonction retourne
une liste de 3-uples, chacun contient :
- le nom de la fonction 10
- (debut,fin) de l’expression dans la chaîne
- la ligne où elle a été trouvée
Paramètres:
- s : chaîne de caractères 15
- exp : chaîne de caractères correspond à l’expression
- gr : numéro de groupe correspondant au nom de la fonction
- expm : expression négative
"""
exp = re.compile (exp) 20
res = []
pos = 0
r = exp.search (s, pos) # première recherche
while r != None :
temp = (r.groups () [gr], r.span (gr), r.group (gr)) 25
x = re.compile ( expm.replace ("function", temp [0]) )
if not x.match (temp [2]) :
# l’expression négative n’est pas trouvé, on peut ajouter ce résultat
res.append (temp)
r = exp.search (s, r.end (gr) ) # recherche suivante 30
return res
if f [0] not in f2 :
ligne = sfile [:f [1][0]].count ("\n")
res.append ( (f [0], ligne+2)) 60
return res
7.7 Dates
Le module datetime 19 fournit une classe datetime qui permet de faire des opérations et des comparaisons
sur les dates et les heures. L’exemple suivant calcule l’âge d’une personne née le 11 août 1975.
import datetime
naissance = datetime.datetime (1975,11,8,10,0,0)
jour = naissance.now () # obtient l’heure et la date actuelle
print jour # affiche 2010-05-22 11:24:36.312000
age = jour - naissance # calcule une différence
print age # affiche 12614 days, 1:25:10.712000
L’objet datetime autorise les soustractions et les comparaisons entre deux dates. Une soustraction retourne
un objet de type timedelta qui correspond à une durée. qu’on peut multiplier par un réel ou ajouter à un
objet de même type ou à un objet de type datetime. L’utilisation de ce type d’objet évite de se pencher
sur tous les problèmes de conversion.
Le module calendar est assez pratique pour construire des calendriers. Le programme ci-dessous affiche
une liste de t-uples incluant le jour et le jour de la semaine du mois d’août 1975. Dans cette liste, on y
trouve le t-uple (11, 0) qui signifie que le 11 août 1975 était un lundi. Cela permet de récupérer le jour de
la semaine d’une date de naissance.
import calendar
c = calendar.Calendar ()
for d in c.itermonthdays2 (1975,8) : print d
3. Le jeu de caractères de la sortie, utilisé pour chaque instruction print, il est désigné par le code cp1252 sur
un système Windows.
4. Le jeu de caractères dans lequel les chaînes de caractères sont manipulées. Un jeu standard qui permet de
représenter toutes les langues est le jeu de caractères utf − 8. Il peut être différent pour chaque variable.
5. Le jeu de caractères d’un fichier texte. Il peut être différent pour chaque fichier.
Le langage Python offre deux classes pour représenter les chaînes de caractères. La classe str qui est adaptée
aux langues latines, le jeu de caractères n’est pas précisé. La classe unicode représente un caractère sur un
à quatre octets avec un jeu de caractères désigné par l’appellation unicode. Il est impératif de savoir quel
jeu est utilisé à quel endroit et il faut faire des conversions de jeux de caractères pour passer l’information
d’un endroit à un autre. Il existe deux méthodes pour cela présentées par la table 7.5.
# coding: latin-1
st = "eé"
su = u"eé" # raccourci pour su = unicode ("eé", "latin-1")
# ou encore su = unicode ("eé".decode ("latin-1"))
Lorsqu’on manipule plusieurs jeux de caractères, il est préférable de conserver un unique jeu de référence
pour le programme par l’intermédiaire de la classe unicode. Il "suffit" de gérer les conversions depuis ces
chaînes de caractères vers les entrées sorties du programme comme les fichiers texte. Par défaut, ceux-ci
sont écrits avec le jeu de caractères du système d’exploitation. Dans ce cas, la fonction open suffit. En
revanche, si le jeu de caractères est différent, il convient de le préciser lors de l’ouverture du fichier. On
utilise la fonction open du module codecs qui prend comme paramètre supplémentaire le jeu de caractères
du fichier. Toutes les chaînes de caractères seront lues et converties au format unicode.
import codecs
f = codecs.open ("essai.txt", "r", "cp1252") # jeu Windows
s = "".join (f.readlines ())
f.close ()
print type (s) # affiche <type ’unicode’>
print s.encode ("cp1252") # pour l’afficher,
# il faut convertir l’unicode en "cp1252"
Cette fonction permet de passer d’un jeu de caractères, celui de la variable, au jeu
de caractères précisé par enc à moins que ce ne soit le jeu de caractères par dé-
faut. Le paramètre err permet de préciser comment gérer les erreurs, doit-on in-
encode(
terrompre le programme (valeur 0 strict0 ) ou les ignorer (valeur 0 ignore0 ). La do-
[enc[, err]])
cumentation Python recense toutes les valeurs possibles pour ces deux paramètres
aux adresses http://docs.python.org/library/codecs.html#id3 et http://docs.python.org/
library/stdtypes.html#id4. Cette fonction retourne un résultat de type str.
decode( Cette fonction est la fonction inverse de la fonction encode. Avec les mêmes paramètres,
[enc[, err]]) elle effectue la transformation inverse.
Table 7.5 : Conversion de jeux de caractères, ce sont deux méthodes qui fonctionnent de façons identiques pour
les deux classes de chaînes de caractères disponibles en Python : str et unicode.
Le programme suivant permet d’obtenir le jeu de caractères par défaut et celui du système d’exploitation.
import sys
import locale
7. Fichiers, expressions régulières, dates 170
Les problèmes de jeux de caractères peuvent devenir vite compliqués lorsqu’on manipule des informations
provenant de plusieurs langues différentes. Il est rare d’avoir à s’en soucier tant que le programme gère
les langues anglaise et française. Dans le cas contraire, il est préférable d’utiliser le type unicode pour
toutes les chaînes de caractères. Il est conseillé de n’utiliser qu’un seul jeu de caractères pour enregistrer
les informations dans des fichiers et le jeu de caractères utf − 8 est le plus indiqué.
Chapitre 8
Interface graphique
Les interfaces graphiques servent à rendre les programmes plus conviviaux. Elles sont pratiques à utiliser
mais elles demandent un peu de temps pour les concevoir. Un programme sans interface exécute des
instructions les unes à la suite des autres, le programme a un début - un point d’entrée - et une fin.
Avec une interface, le programme fonctionne de manière différente. Il n’exécute plus successivement les
instructions mais attend un événement - pression d’une touche du clavier, clic de souris - pour exécuter
une fonction. C’est comme si le programme avait une multitude de points d’entrée.
Il existe plusieurs modules permettant d’exploiter les interfaces graphiques. Le plus simple est le module
Tkinter présent lors de l’installation du langage Python. Ce module est simple mais limité. Le module
wxPython est plus complet mais un peu plus compliqué dans son utilisation 1 . Toutefois, le fonctionnement
des interfaces graphiques sous un module ou un autre est identique. C’est pourquoi ce chapitre n’en
présentera qu’un seul, le module Tkinter. Pour d’autres modules, les noms de classes changent mais la
logique reste la même : il s’agit d’associer des événements à des parties du programme Python.
Les interfaces graphiques évoluent sans doute plus vite que les autres modules, des composantes de plus
en plus complexes apparaissent régulièrement. Un module comme wxPython change de version plusieurs
fois par an. Il est possible de trouver sur Internet des liens 2 qui donnent des exemples de programme. Une
excellente source de documentation sont les forums de discussion qui sont un lieu où des programmeurs
échangent questions et réponses. Un message d’erreur entré sur un moteur de recherche Internet permet
souvent de tomber sur des échanges de ce type, sur des problèmes résolus par d’autres.
8.1 Introduction
Un programme muni d’une interface graphique fonctionne différemment d’un programme classique. Un
programme classique est une succession presque linéaire d’instructions. Il y a un début ou point d’entrée du
programme et aucun événement extérieur ne vient troubler son déroulement. Avec une interface graphique,
le point d’entrée du programme est masqué : il est pris en compte automatiquement. Du point de vue
du programmeur, le programme a plusieurs points d’entrée : une simple fenêtre avec deux boutons (voir
figure 8.1) propose deux façons de commencer et il faut prévoir une action associée à chaque bouton.
La conception d’une interface graphique se déroule généralement selon deux étapes. La première consiste
à dessiner l’interface, c’est-à-dire choisir une position pour les objets de la fenêtre (boutons, zone de saisie,
liste déroulante, ...). La seconde étape définit le fonctionnement de la fenêtre, c’est-à-dire associer à chaque
objet des fonctions qui seront exécutées si un tel événement se réalise (pression d’un bouton, pression d’une
touche, ...).
Pour le moment, nous allons supposer que ces deux étapes sont scindées même si elles sont parfois entre-
mêlées lorsqu’un événement implique la modification de l’apparence de la fenêtre. La section qui suit décrit
des objets que propose le module Tkinter. La section suivante présente la manière de les disposer dans une
fenêtre. La section d’après décrit les événements et le moyen de les relier à des fonctions du programme.
Ce chapitre se termine par quelques constructions courantes à propos des interfaces graphiques.
1. Le paragraphe 1.6.5 page 20 présente d’autres alternatives.
2. L’adresse http://gnuprog.info/prog/python/pwidget.php illustre chaque objet de Tkinter, on peut citer également http:
//effbot.org/tkinterbook/.
8. Interface graphique 172
Figure 8.1 : Une fenêtre contenant deux boutons : ce sont deux points d’entrée du
programme.
Il est possible que le texte de cette zone de texte doive changer après quelques temps. Dans ce cas, il faut
appeler la méthode config comme suit :
zone_texte = Tkinter.Label (text = "premier texte")
# ...
# pour changer de texte
zone_texte.config (text = "second texte")
La figure 8.2 montre deux zones de texte. La seconde est grisée par rapport à la première. Pour obtenir
cet état, il suffit d’utiliser l’instruction suivante :
zone_texte.config (state = Tkinter.DISABLED)
3. On les appelle aussi contrôle. Comme ils reçoivent des événements, en un sens, ce sont ces objets qui pilotent un
programme ou qui le contrôlent.
4. La page http://wiki.python.org/moin/Tkinter recense quelques liens utiles autour de Tkinter dont la documentation
officielle.
8. Interface graphique 173
Figure 8.2 : Exemple de zone de texte ou Label associée à une zone de saisie.
La seconde image montre une zone de texte dans l’état DISABLED.
Ces deux dernières options sont communes à tous les objets d’une interface graphique. Cette option sera
rappelée au paragraphe 8.2.11.
8.2.2 Bouton
Un bouton a pour but de faire le lien entre une fonction et un clic de souris. Un bouton correspond à la
classe Button du module Tkinter. Pour créer un bouton, il suffit d’écrire la ligne suivante :
bouton = Tkinter.Button (text = "zone de texte")
Il est possible que le texte de ce bouton doive changer après quelques temps. Dans ce cas, il faut appeler
la méthode config comme suit :
bouton = Tkinter.Button (text = "premier texte")
# ...
# pour changer de texte
bouton.config (text = "second texte")
La figure 8.3 montre trois boutons. Le troisième est grisé par rapport au premier. Les boutons grisés ne
peuvent pas être pressés. Pour obtenir cet état, il suffit d’utiliser l’instruction suivante :
bouton.config (state = Tkinter.DISABLED)
C’est pour cet objet que cette option est la plus intéressante car elle permet d’interdire la possibilité pour
l’utilisateur de presser le bouton tout en le laissant visible.
Il est possible également d’associer une image à un bouton. Par exemple, les trois lignes suivantes créent
un bouton, charge une image au format gif puis l’associe au bouton b. Lors de l’affichage de la fenêtre,
le bouton b ne contient pas de texte mais une image.
b = Tkinter Button ()
im = Tkinter.PhotoImage (file = "chameau.gif")
b.config (image = im)
Les images qu’il est possible de charger sont nécessairement au format GIF, le seul que le module Tkinter
puisse lire.
8. Interface graphique 174
Pour modifier le contenu de la zone de saisie, il faut utiliser la méthode insert qui insère un texte à une
position donnée.
# le premier paramètre est la position
# où insérer le texte (second paramètre)
saisie.insert (pos, "contenu")
Pour supprimer le contenu de la zone de saisie, il faut utiliser la méthode delete. Cette méthode supprime
le texte entre deux positions.
# supprime le texte entre les positions pos1, pos2
saisie.delete (pos1, pos2)
Par exemple, pour supprimer le contenu d’une zone de saisie, on peut utiliser l’instruction suivante :
saisie.delete (0, len (saisie.get ()))
Figure 8.4 : Exemple de zones de saisie, normale et grisée, la zone grisée ne peut être
modifiée.
La figure 8.4 montre deux zones de saisie. La seconde est grisée par rapport à la première. Les zones de
saisie grisées ne peuvent pas être modifiées. Pour obtenir cet état, il suffit d’utiliser la méthode config
comme pour les précédents objets. Cette option sera rappelée au paragraphe 8.2.11.
Pour modifier le contenu de la zone de saisie, il faut utiliser la méthode insert qui insère un texte à
une position donnée. La méthode diffère de celle de la classe Entry puisque la position d’insertion est
maintenant une chaîne de caractères contenant deux nombres séparés par un point : le premier nombre
désigne la ligne, le second la position sur cette ligne.
# le premier paramètre est la position
# où insérer le texte (second paramètre)
pos = "0.0"
saisie.insert (pos, "première ligne\nseconde ligne")
8. Interface graphique 175
Pour obtenir le contenu de la zone de saisie, il faut utiliser la méthode get qui retourne le texte entre deux
positions. La position de fin n’est pas connue, on utilise la chaîne de caractères "end" pour désigner la fin
de la zone de saisie.
# retourne le texte entre deux positions
pos1 = "0.0"
pos2 = "end" # ou Tkinter.END
contenu = saisie.get (pos1, pos2)
Pour supprimer le contenu de la zone de saisie, il faut utiliser la méthode delete. Cette méthode supprime
le texte entre deux positions.
# supprime le texte entre les positions pos1, pos2
saisie.delete (pos1, pos2)
Par exemple, pour supprimer le contenu d’une zone de saisie à plusieurs lignes, on peut utiliser l’instruction
suivante :
saisie.delete ("0.0", "end")
# on peut aussi utiliser
# saisie.delete ("0.0", Tkinter.END)
Pour modifier les dimensions de la zone de saisie à plusieurs lignes, on utilise l’instruction suivante :
# modifie les dimensions de la zone
# width <--> largeur
# height <--> hauteur en lignes
saisie.config (width = 10, height = 5)
L’image précédente montre une zone de saisie à plusieurs lignes. Pour griser cette zone, il suffit d’utiliser
la méthode config rappelée au paragraphe 8.2.11.
En fait, ce sont deux objets qui sont créés. Le premier, de type IntVar, mémorise la valeur de la case
à cocher. Le second objet, de type CheckButton, gère l’apparence au niveau de l’interface graphique. La
raison de ces deux objets est plus évidente dans le cas de l’objet RadioButton décrit au paragraphe suivant.
Pour savoir si la case est cochée ou non, il suffit d’exécuter l’instruction :
v.get () # égal à 1 si la case est cochée, 0 sinon
La figure 8.5 montre trois cases. La troisième est grisée par rapport à la première. Les cases grisées
ne peuvent pas être cochées. Pour obtenir cet état, il suffit d’utiliser la méthode config rappelée au
paragraphe 8.2.11.
8. Interface graphique 176
Figure 8.5 : Exemples de cases à cocher, non cochée, cochée, grisée. Lorsqu’elle
est grisée, son état ne peut être modifié. La dernière image montre une case à cocher
associée à un texte.
La variable v est partagée par les trois cases rondes. L’option value du constructeur permet d’associer
un bouton radio à une valeur de v. Si v == 10, seul le premier bouton radio sera sélectionné. Si v == 20,
seul le second bouton radio le sera. Si deux valeurs sont identiques pour deux boutons radio, ils seront
cochés et décochés en même temps. Et pour savoir quel bouton radio est coché ou non, il suffit d’exécuter
l’instruction :
v.get () # retourne le numéro du bouton radio coché (ici, 10, 20 ou 30)
Figure 8.6 : Exemples de cases rondes ou boutons radio, non cochée, cochée, grisée.
Lorsqu’elle est grisée, son état ne peut être modifiée. La seconde image présente un groupe
de bouton radio. Un seul peut être sélectionné à la fois à moins que deux boutons ne soient
associés à la même valeur. Dans ce cas, ils agiront de pair.
La figure 8.6 montre trois cases. La troisième est grisée par rapport à la première. Si un des boutons radio
est grisé parmi les trois, le choix du bouton à cocher s’effectue parmi les deux restants ou aucun si le
bouton radio grisé est coché.
8.2.7 Liste
Un objet liste contient une liste d’intitulés qu’il est possible de sélectionner. Une liste correspond à la
classe ListBox du module Tkinter. Pour la créer, il suffit d’écrire la ligne suivante :
8. Interface graphique 177
li = Tkinter.Listbox ()
Pour modifier les dimensions de la zone de saisie à plusieurs lignes, on utilise l’instruction suivante :
# modifie les dimensions de la liste
# width <--> largeur
# height <--> hauteur en lignes
li.config (width = 10, height = 5)
Les intitulés de cette liste peuvent ou non être sélectionnés. Cliquer sur un intitulé le sélectionne mais la
méthode select_set permet aussi de le faire.
pos1 = 0
li.select_set (pos1, pos2 = None)
# sélectionne tous les éléments entre les indices pos1 et
# pos2 inclus ou seulement celui d’indice pos1 si pos2 == None
La méthode curselection permet d’obtenir la liste des indices des éléments sélectionnés.
sel = li.curselection ()
La méthode get permet récupérer un élément de la liste tandis que la méthode size retourne le nombre
d’éléments :
for i in range (0,li.size ()) :
print li.get (i)
La figure 8.7 montre deux listes. La seconde est grisée par rapport à la première. Elle ne peut être modifiée.
Pour obtenir cet état, il faut appeler la méthode config rappelée au paragraphe 8.2.11.
Remarque 8.1 : liste et barre de défilement
Il est possible d’adjoindre une barre de défilement verticale. Il faut pour cela inclure l’objet dans une
sous-fenêtre Frame qui est définie au paragraphe 8.3.2 comme dans l’exemple suivant :
8. Interface graphique 178
Il suffit de transposer cet exemple pour ajouter une barre de défilement horizontale. Toutefois, il est
préférable d’utiliser un objet prédéfini présent dans le module Tix qui est une extension du module Tkinter.
Elle est présentée au paragraphe 8.2.8.
Remarque 8.2 : plusieurs Listbox
Lorsqu’on insère plusieurs objets Listbox dans une seule fenêtre, ces objets partagent par défaut la même
sélection. Autrement dit, lorsqu’on clique sur un élément de la seconde Listbox, l’élément sélectionné dans
la première ne l’est plus. Afin de pouvoir sélectionner un élément dans chaque Listbox, il faut ajouter dans
les paramètres du constructeur l’option exportselection = 0 comme l’illustre l’exemple suivant :
li = Tkinter.Listbox (frame, width = 88, height = 6, exportselection=0)
Il existe des méthodes plus avancées qui permettent de modifier l’aspect graphique d’un élément comme
la méthode itemconfigure. Son utilisation est peu fréquente à moins de vouloir réaliser une belle interface
graphique. Le paragraphe 8.6.2 montre l’utilisation qu’on peut en faire.
o = Tk.ScrolledListBox (root) 5
for k in range (0,100) : o.listbox.insert (Tk.END, "ligne " + str (k))
o.pack ()
Le module Tix quoique assez riche puisqu’il propose des fenêtres permettant de sélectionner un fichier
ou de remplir un tableau est mal documenté sur Python. Il existe une documentation officielle 5 et des
exemples 6 . La plupart du temps, on trouve un exemple approché. Dans l’exemple précédent comme dans
le suivant avec une "ComboBox", la pression du bouton print écrit la zone sélectionnée. La figure 8.8 illustre
graphiquement ce qu’est une Combobox.
# coding: latin-1
import Tix as Tk
root = Tk.Tk ()
root.mainloop () # idem
L’avantage du module Tix est d’être installé automatiquement avec le langage Python. Il reste malgré tout
très peu documenté et arriver à ses fins peut nécessiter quelques heures de recherche.
Figure 8.8 : Exemple de fenêtre Combobox définie par le module interne Tix qui
est une extension du module Tkinter. La seconde image est la même Combobox
mais dépliée.
8.2.9 Canevas
Pour dessiner, il faut utiliser un objet canevas, correspondant à la classe Canvas du module Tkinter. Pour
la créer, il suffit d’écrire la ligne suivante :
ca = Tkinter.Canvas ()
Pour modifier les dimensions de la zone de saisie à plusieurs lignes, on utilise l’instruction suivante :
# modifie les dimensions du canevas
# width <--> largeur en pixels
# height <--> hauteur en pixels
ca.config (width = 10, height = 5)
Cet objet permet de dessiner des lignes, des courbes, d’écrire du texte grâce aux méthodes create_line,
create_rectangle, create_text. La figure 8.9 illustre ce que donnent les quelques lignes qui suivent.
# dessine deux lignes du point 10,10 au point 40,100 et au point 200,60
# de couleur bleue, d’épaisseur 2
ca.create_line (10,10,40,100, 200,60, fill = "blue", width = 2)
# dessine une courbe du point 10,10 au point 200,60
# de couleur rouge, d’épaisseur 2, c’est une courbe de Bézier
# pour laquelle le point 40,100 sert d’assise
ca.create_line (10,10, 40,100, 200,60, smooth=1, fill = "red", width = 2)
# dessine un rectangle plein de couleur jaune, de bord noir et d’épaisseur 2
ca.create_rectangle (300,100,60,120, fill = "gray", width = 2)
# écrit du texte de couleur noire au point 80,80 et avec la police arial
ca.create_text (80,80, text = "écrire", fill = "black", font = "arial")
8. Interface graphique 180
8.2.10 Menus
Les menus apparaissent en haut des fenêtres. La plupart des applications arborent un menu commençant
par Fichier Edition Affichage... Le paragraphe 8.4.5 page 188 les décrit en détail.
Elle permet également de modifier le texte d’un objet, sa position, ... De nombreuses options sont com-
munes à tous les objets et certaines sont spécifiques. L’aide associée à cette méthode 8 ne fournit aucun
renseignement. En fait, le constructeur et cette méthode ont les mêmes paramètres optionnels. Il est
équivalent de préciser ces options lors de l’appel au constructeur :
l = Tkinter.Label (text = "légende")
Tandis que l’aide associée au constructeur de la classe Label 9 donne plus d’informations :
__init__(self, master=None, cnf={}, **kw) unbound Tkinter.Label method
Construct a label widget with the parent MASTER.
STANDARD OPTIONS
highlightbackground, highlightcolor,
highlightthickness, image, justify,
padx, pady, relief, takefocus, text,
textvariable, underline, wraplength
WIDGET-SPECIFIC OPTIONS
Cette aide mentionne les options communes à tous les objets (ou widgets) et les options spécifiques à
cet objet, ici de type Label. Toutes ont une valeur par défaut qu’il est possible de changer soit dans le
constructeur, soit par la méthode config. Quelques-unes ont été décrites, d’autres permettent de modifier
entre autres la police avec laquelle est affiché le texte de l’objet (option font), la couleur du fond (option
background), l’alignement du texte, à gauche, à droite, centré (option justify), l’épaisseur du bord (option
borderwidth), le fait qu’un objet reçoive le focus 10 après la pression de la touche tabulation (takefocus)...
Cette méthode empile les objets les uns à la suite des autres. Par défaut, elle les empile les uns en dessous
des autres. Par exemple, l’exemple suivant produit l’empilement des objets de la figure 8.10.
l = Tkinter.Label (text = "première ligne")
l.pack ()
s = Tkinter.Entry ()
s.pack ()
e = Tkinter.Label (text = "seconde ligne")
e.pack ()
On peut aussi les empiler les uns à droite des autres grâce à l’option side.
l = Tkinter.Label (text = "première ligne")
l.pack (side = Tkinter.RIGHT)
s = Tkinter.Entry ()
La méthode grid suppose que la fenêtre qui les contient est organisée selon une grille dont chaque case
peut recevoir un objet. L’exemple suivant place trois objets dans les cases de coordonnées (0, 0), (1, 0) et
(0, 1). Le résultat apparaît dans la figure 8.11.
Figure 8.11 : Les objets sont placés dans une grille à l’aide de
la méthode grid. Une fois que chaque objet a reçu une position, à
l’affichage, il ne sera pas tenu compte des lignes et colonnes vides.
Enfin, comme pour la méthode pack, il existe une méthode grid_forget qui permet de faire disparaître
les objets.
s.grid_forget () # disparition
8. Interface graphique 183
La méthode place est sans doute la plus simple à comprendre puisqu’elle permet de placer chaque objet
à une position définie par des coordonnées. Elle peut être utilisée en parallèle avec les méthodes pack et
grid.
La méthode place_forget permet de faire disparaître un objet placer avec cette méthode. L’inconvénient
de cette méthode survient lorsqu’on cherche à modifier l’emplacement d’un objet : il faut en général revoir
les positions de tous les autres éléments de la fenêtre. On procède souvent par tâtonnement pour construire
une fenêtre et disposer les objets. Ce travail est beaucoup plus long avec la méthode place.
8.3.2 Sous-fenêtre
Les trois méthodes précédentes ne permettent pas toujours de placer les éléments comme on le désire. On
souhaite parfois regrouper les objets dans des boîtes et placer celles-ci les unes par rapport aux autres. La
figure 8.12 montre deux objets regroupés dans un rectangle avec à sa gauche une zone de texte. Les boîtes
sont des instances de la classe Frame. Ce sont des objets comme les autres excepté le fait qu’une boîte
contient d’autres objets y compris de type Frame. Pour créer une boîte, il suffit d’écrire la ligne suivante :
f = Tkinter.Frame ()
Ensuite, il faut pouvoir affecter un objet à cette boîte f. Pour cela, il suffit que f soit le premier paramètre
du constructeur de l’objet créé :
L’exemple qui suit correspond au code qui permet d’afficher la fenêtre de la figure 8.12 :
f = Tkinter.Frame ()
l = Tkinter.Label (f, text = "première ligne")
l.pack () # positionne l à l’intérieur de f
s = Tkinter.Entry (f)
s.pack () # positionne s à l’intérieur de f
f.pack (side = Tkinter.LEFT) # positionne f à l’intérieur
# de la fenêtre principale
e = Tkinter.Label (text = "seconde ligne")
e.pack_forget ()
e.pack (side = Tkinter.RIGHT) # positionne e à l’intérieur
# de la fenêtre principale
L’utilisation de ces blocs Frame est pratique lorsque le même ensemble de contrôles apparaît dans plusieurs
fenêtres différentes ou au sein de la même fenêtre. Cette possibilité est envisagée au paragraphe 8.6.3.
Figure 8.12 : Les deux premiers objets - une zone de texte au-dessus d’une
zone de saisie - sont regroupés dans une boîte - rectangle rouge, invisible
à l’écran. A droite et centrée, une dernière zone de texte. Cet alignement
est plus simple à réaliser en regroupant les deux premiers objets dans un
rectangle (objet Frame).
8. Interface graphique 184
8.4 Evénements
8.4.1 Fenêtre principale
Tous les exemples des paragraphes précédents décrivent les différents objets disponibles et comment les
disposer dans une fenêtre. Pour afficher cette fenêtre, il suffit d’ajouter au programme les deux lignes
suivantes :
root = Tkinter.Tk ()
# ici, on trouve le code qui définit les objets
# et leur positionnement
root.mainloop ()
La première ligne permet d’obtenir un identificateur relié à la fenêtre principale. La seconde ligne, outre
le fait qu’elle affiche cette fenêtre, lance ce qu’on appelle une boucle de messages. Cette fonction récupère
- intercepte - les événements comme un clic de souris, la pression d’une touche. Elle parcourt ensuite tous
les objets qu’elle contient et regarde si l’un de ces objets est intéressé par cet événement. S’il est intéressé,
cet objet prend l’événement et le traite. On peut revenir ensuite à la fonction mainloop qui attend à
nouveau un événement. Cette fonction est définie par Tkinter, il reste à lui indiquer quels événements un
objet désire intercepter et ce qu’il est prévu de faire au cas où cet événement se produit.
Remarque 8.3 : instruction root = Tkinter.Tk()
Cette première instruction doit se trouver au début du programme. Si elle intervient alors qu’une méthode
de positionnement (pack, grid, ou place) a déjà été appelée, le programme affichera deux fenêtres dont
une vide.
8.4.2 Focus
Une fenêtre peut contenir plusieurs zones de saisie, toutes capables d’intercepter la pression d’une touche
du clavier et d’ajouter la lettre correspondante à la zone de saisie. Or la seule qui ajoute effectivement une
lettre à son contenu est celle qui a le focus. La pression de la touche tabulation fait passer le focus d’un
objet à l’autre. La figure 8.13 montre un bouton qui a le focus. Lorsqu’on désire qu’un objet en particulier
ait le focus, il suffit d’appeler la méthode focus_set.
e = Tkinter.Entry ()
e.pack ()
e.focus_set ()
Figure 8.13 : Ce bouton est entouré d’un cercle noir en pointillé, il a le focus.
import Tkinter
root = Tkinter.Tk ()
b = Tkinter.Button (text = "fonction change_legende") 5
b.pack ()
def change_legende () :
global b
b.config (text = "nouvelle légende") 10
Lorsque le bouton b est pressé, on vérifie qu’il change bien de légende (voir figure 8.14).
Figure 8.14 : La première fenêtre est celle qui apparaît lorsque le programme de la
page 184 est lancé. Comme le bouton change de légende la première fois qu’il est pressé,
l’apparence de la fenêtre change aussi, ce que montre la seconde image.
Lorsqu’une touche a été pressée, cet attribut contient son code, il ne tient pas compte
char des touches dites muettes comme les touches shift, ctrl, alt. Il tient pas compte
non plus des touches return ou suppr.
Lorsqu’une touche a été pressée, cet attribut contient son code, quelque soit la touche,
keysym
muette ou non.
num Contient un identificateur de l’objet ayant reçu l’événement.
Coordonnées relatives de la souris par rapport au coin supérieur gauche de l’objet
x, y
ayant reçu l’événement.
x_root, y_root Coordonnées absolues de la souris par rapport au coin supérieur gauche de l’écran.
widget Identifiant permettant d’accéder à l’objet ayant reçu l’événement.
Table 8.1 : Attributs principaux de la classe Event, ils décrivent les événements liés au clavier et à la souris. La
liste complète est accessible en utilisant l’instruction help(Tkinter.Event).
w est l’identificateur de l’objet devant intercepter l’événement désigné par la chaîne de caractères ev
(voir table 8.2). fonction est la fonction qui est appelée lorsque l’événement survient. Cette fonction
ne prend qu’un paramètre de type Event.
L’exemple suivant utilise la méthode bind pour que le seul bouton de la fenêtre intercepte toute pression
d’une touche, tout mouvement et toute pression du premier bouton de la souris lorsque le curseur est
au dessus de la zone graphique du bouton. La fenêtre crée par ce programme ainsi que l’affichage qui en
résulte apparaissent dans la figure 8.15.
import Tkinter
root = Tkinter.Tk ()
b = Tkinter.Button (text = "appuyer sur une touche")
b.pack ()
5
def affiche_touche_pressee (evt) :
print "--------------------------- touche pressee"
print "evt.char = ", evt.char
print "evt.keysym = ", evt.keysym
print "evt.num = ", evt.num 10
print "evt.x,evt.y = ", evt.x, ",", evt.y
8. Interface graphique 187
root.mainloop () 20
evt.char = ?? evt.char =
evt.keysym = ?? evt.keysym = Return
evt.num = 1 evt.num = ??
evt.x,evt.y = 105 , 13 evt.x,evt.y = 105 , 13
evt.x_root,evt.y_root = evt.x_root,evt.y_root =
292 , 239 292 , 239
evt.widget = .9261224 evt.widget = .9261224
Figure 8.15 : Fenêtre affichée par le programme du paragraphe 8.4.4. La pression d’une touche déclenche l’affichage
des caractéristiques de l’événement. La seconde colonne correspond à la pression du premier bouton de la souris. La
dernière colonne correspond à la pression de la touche Return.
Lors de l’exécution, le programme déclenche la succession d’exceptions suivantes qui signifie que l’événe-
ment <button-1> n’existe pas.
Traceback (most recent call last):
File "exemple_bind.py", line 17, in ?
b.bind ("<button-1>", affiche_touche_pressee)
File "c:\python26\lib\lib-tk\Tkinter.py", line 933, in bind
return self._bind((’bind’, self._w), sequence, func, add)
File "c:\python26\lib\lib-tk\Tkinter.py", line 888, in _bind
self.tk.call(what + (sequence, cmd))
_tkinter.TclError: bad event type or keysym "button"
On utilise peu cette fonction, on préfère construire des objets propres à un programme comme suggéré au
paragraphe 8.6.2.
Remarque 8.9 : désactiver un événement
De la même manière qu’il est possible d’associer un événement à un objet d’une fenêtre, il est possible
d’effectuer l’opération inverse qui consiste à supprimer cette association. La méthode unbind désactive un
8. Interface graphique 188
événement associé à un objet. La méthode unbind_all désactive un événement associé pour tous les objets
d’une fenêtre.
w est l’identificateur de l’objet interceptant l’événement désigné par la chaîne de caractères ev (voir
table 8.2). Après l’appel à la méthode unbind, l’événement n’est plus intercepté par l’objet w. Après
l’appel à la méthode unbind_all, l’événement n’est plus intercepté par aucun objet.
8.4.5 Menu
Les menus fonctionnent de la même manière que les boutons. Chaque intitulé du menu est relié à une
fonction qui sera exécutée à la condition que l’utilisateur sélectionne cet intitulé. L’objet Menu ne désigne
pas le menu dans son ensemble mais seulement un niveau. Par exemple, le menu présenté par la figure 8.17
est en fait un assemblage de trois menus auquel on pourrait ajouter d’autres sous-menus.
Figure 8.16 : La représentation d’un menu tient plus d’un graphe que d’une
liste. Chaque intitulé du menu peut être connecté à une fonction ou être le
point d’entrée d’un nouveau sous-menu.
Ce menu peut être le menu principal d’une fenêtre auquel cas, il suffit de préciser à la fenêtre en question
que son menu est le suivant :
root.config (menu = m)
root est ici la fenêtre principale mais ce pourrait être également une fenêtre de type TopLevel (voir
paragraphe 8.5.1). Ce menu peut aussi être le sous-menu associé à un intitulé d’un menu existant. La
méthode add_cascade permet d’ajouter un sous-menu associé à un label :
mainmenu = Tkinter.Menu ()
msousmenu = Tkinter.Menu ()
mainmenu.add_cascade (label = "sous-menu 1", menu = msousmenu)
m = Tkinter.Menu ()
mainmenu.add_command (label = "fonction 1", command = fonction1)
m = Tkinter.Menu ()
sm1 = Tkinter.Menu ()
sm2 = Tkinter.Menu () 10
nb = 0 15
Chaque intitulé d’un menu est ajouté en fin de liste, il est possible d’en supprimer certains à partir de leur
position avec la méthode delete :
m = Tkinter.Menu ()
m.add_command (...)
m.delete (1, 2) # supprime le second intitulé
# supprime les intitulés compris entre 1 et 2 exclu
8. Interface graphique 190
La table 8.3 regroupe les fonctions les plus utilisées. Celles-ci s’applique à une fenêtre de type Toplevel
qui est aussi le type de la fenêtre principale.
Un cas d’utilisation simple est par exemple un bouton pressé qui fait apparaître une fenêtre permettant
de sélectionner un fichier, cette seconde fenêtre sera un objet Toplevel. Il n’est pas nécessaire de s’étendre
plus sur cet objet, son comportement est identique à celui de la fenêtre principale, les fonctions décrites au
paragraphe 8.4.6 s’appliquent également aux objets Toplevel. Il reste néanmoins à préciser un dernier point.
Tous les objets précédemment décrits au paragraphe 8.2 doivent inclure un paramètre supplémentaire dans
leur constructeur pour signifier qu’ils appartiennent à un objet Toplevel et non à la fenêtre principale.
Par exemple, pour créer une zone de texte, la syntaxe est la suivante :
# zone_texte appartient à la fenêtre principale
zone_texte = Tkinter.Label (text = "premier texte")
Lors de la définition de chaque objet ou widget, si le premier paramètre est de type Toplevel, alors ce
paramètre sera affecté à la fenêtre passée en premier argument et non à la fenêtre principale. Ce principe
est le même que celui de la sous-fenêtre Frame (voir paragraphe 8.3.2, page 183). La seule différence provient
du fait que l’objet Toplevel est une fenêtre autonome qui peut attendre un message grâce à la méthode
mainloop, ce n’est pas le cas de l’objet Frame.
Toutefois, il est possible d’afficher plusieurs fenêtre Toplevel simultanément. Le programme suivant en est
un exemple :
# coding: latin-1
import Tkinter
class nouvelle_fenetre :
resultat = [] 5
def top (self) :
sec = Tkinter.Toplevel ()
Tkinter.Label (sec, text="entrer quelque chose").pack ()
saisie = Tkinter.Entry (sec)
saisie.pack() 10
Tkinter.Button (sec, text = "valider", command = sec.quit).pack ()
sec.mainloop ()
nouvelle_fenetre.resultat.append ( saisie.get () )
sec.destroy ()
15
root = Tkinter.Tk() #fenetre principale
a = Tkinter.Button (text = "fenêtre Toplevel",
command = nouvelle_fenetre ().top)
a.pack()
root.mainloop() 20
for a in nouvelle_fenetre.resultat :
print "contenu ", a
la partie interface du reste du programme. La gestion événementielle a pour défaut parfois de disséminer
un traitement, un calcul à plusieurs endroits de l’interface. C’est le cas par exemple de longs calculs dont
on souhaite connaître l’avancée. Le calcul est lancé par la pression d’un bouton puis son déroulement est
"espionné" par un événement régulier comme un compte à rebours.
Le principal problème des interfaces survient lors du traitement d’un événement : pendant ce temps,
l’interface n’est pas réactive et ne réagit plus aux autres événements jusqu’à ce que le traitement de
l’événement en cours soit terminé. Pour contourner ce problème, il est possible soit de découper un calcul
en petites fonctions chacune très rapide, cela suppose que ce calcul sera mené à bien par une succession
d’événements. Il est également possible de lancer un thread, principe décrit au paragraphe 9.3 (page 202).
C’est pourquoi la première règle est de bien scinder interface et calculs scientifiques de façon à pouvoir
rendre le programme plus lisible et ainsi être en mesure d’isoler plus rapidement la source d’une erreur.
Les paragraphes qui suivent présentent quelques aspects récurrents qu’il est parfois utile d’avoir en tête
avant de se lancer.
import Tkinter
root = Tkinter.Tk ()
l = Tkinter.Label (text = "0 secondes")
l.pack ()
sec = 0 5
id = None
def change_legende() :
global l
global sec 10
global id
sec += 1
l.config (text = "%d secondes" % sec)
id = l.after (1000, change_legende)
15
l.after (1000, change_legende)
root.mainloop ()
8. Interface graphique 193
La méthode after retourne un entier permettant d’identifier le compte à rebours qu’il est possible d’inter-
rompre en utilisant la méthode after_cancel. Dans l’exemple précédent, il faudrait utiliser l’instruction
suivante :
l.after_cancel (id)
root = Tkinter.Tk ()
b = MaListbox ()
b.insert ("end", "ligne 1")
b.insert ("end", "ligne 2") 20
b.insert ("end", "ligne 3")
b.pack ()
b.focus_set ()
root.mainloop ()
Dans ce cas précis, on fait en sorte que le contrôle intercepte le mouvement du curseur. Lorsque celui-ci
bouge, la méthode mouvement est appelée comme le constructeur de MaListbox l’a spécifié. La méthode
nearest permet de définir l’intitulé le plus proche du curseur. La méthode itemconfigure permet de
changer le fond de cet intitulé en gris après avoir modifié le fond de l’intitulé précédent pour qu’il retrouve
sa couleur d’avant. Le résultat est illustré la figure 8.20.
Figure 8.20 : Aspect de la classe MaListbox définie par le programme 193. L’intitulé
sous le curseur de la souris a un fond gris.
8. Interface graphique 194
class MaFenetre :
def __init__ (self, win) : 5
self.win = win
self.creation ()
if __name__ == "__main__" :
root = Tk.Tk ()
f = MaFenetre (root)
root.mainloop () 35
Ce programme crée trois boutons et attache à chacun d’entre eux une méthode de la classe MaFenetre. Le
constructeur de la classe prend comme unique paramètre un pointeur sur un objet qui peut être la fenêtre
principale, un objet de type Frame ou Toplevel. Cette construction permet de considérer cet ensemble de
trois boutons comme un objet à part entière ; de ce fait il peut être inséré plusieurs fois comme le montre
l’exemple suivant illustré par la figure 8.21.
root = Tk.Tk ()
f = Tk.Frame ()
f.pack ()
MaFenetre (f) # première instance
g = Tk.Frame ()
g.pack ()
MaFenetre (g) # seconde instance
root.mainloop ()
est plus complexe. En effet, le premier d’événement de la séquence active une fonction, il n’est pas possible
d’attendre le second événement dans cette même fonction, ce dernier ne sera observable que si on sort de
cette première fonction pour revenir à la fonction mainloop, la seule capable de saisir le prochain événement.
La figure 8.22 précise la gestion des messages. Tkinter se charge de la réception des messages puis de
l’appel au traitement correspondant indiqué par la méthode ou la fonction attachée à l’événement. Le
programmeur peut définir les traitements associés à chaque événement. Ces deux parties sont scindées et à
moins de reprogrammer sa boucle de message, il n’est pas évident de consulter les événements intervenus
depuis le début du traitement de l’un d’eux.
Figure 8.22 : La réception des événements est assurée par la fonction mainloop
qui consiste à attendre le premier événement puis à appeler la fonction ou la
méthode qui lui est associée si elle existe.
Les classes offrent un moyen simple de gérer les séquences d’événements au sein d’une fenêtre. Celle-ci
fera l’objet d’une classe qui mémorise les séquences d’événements. Tous les événements feront appel à
des méthodes différentes, chacune d’elles ajoutera l’événement à une liste. Après cette ajout, une autre
méthode sera appelée pour rechercher une séquence d’événements particulière. Le résultat est également
illustré par la figure 8.23.
# coding: latin-1
import Tkinter as Tk
class MaFenetreSeq :
def __init__ (self, win) : 5
self.win = win
self.creation ()
self.sequence = []
if __name__ == "__main__" : 45
root = Tk.Tk ()
f = MaFenetreSeq (root)
root.mainloop ()
Ce principe est plus utilisé lorsque l’interface graphique est couplée avec les threads, l’ensemble est présenté
au paragraphe 9.3.
Chapitre 9
Threads
Jusqu’aux années 2003-2004, l’évolution des microprocesseurs était une course vers une augmentation
de la puissance, autant en terme de nombre de transistors qu’en fréquence de fonctionnement. Arrivant
aux limites de la technologie actuelle, cette évolution s’est tournée maintenant vers la construction de
processeurs multicœurs, c’est-à-dire des machines capables d’exécuter des programmes simultanément, de
maintenir plusieurs fils d’exécution en parallèle.
Les threads ou fils d’exécution ont trois usages principaux. Le premier est relié au calcul distribué ou calcul
parallèle. Par exemple, le calcul d’une intégrale sur un intervalle peut être effectué sur deux intervalles
disjoints. Le résultat final est la somme des deux résultats sur chacun des intervalles. De plus, ces deux
calculs sont indépendants et peuvent être menés de front. Le calcul intégral sera donc deux fois plus rapide
puisque les deux intervalles seront traités en même temps. C’est la parallélisation des calculs : les deux
calculs sur chaque intervalle seront affectés à deux threads simultanés.
Le second usage est couplé aux interfaces graphiques. Lorsque l’utilisateur entame un processus long après
avoir cliqué sur un bouton, l’interface graphique ne réagit plus jusqu’à ce que ce processus s’achève. Afin
d’éviter cet inconvénient, l’interface graphique va commencer un thread qui va exécuter ce processus.
L’interface graphique n’a plus qu’à attendre la fin du thread, et pendant tout ce temps, elle sera également
capable de traiter tout autre événement provenant de l’utilisateur.
Le dernier usage concerne la communication entre ordinateurs ou plus généralement la communication
Internet. C’est une communication asynchrone : l’ordinateur effectue des tâches en même temps qu’il
écoute un port par lequel d’autres ordinateurs communiquent avec lui. Plus précisément, le programme
suit deux fils d’exécution : le fil principal et un thread qui ne fait qu’attendre et traiter les messages qu’il
reçoit via un port.
La synchronisation est un point commun à ces trois usages. Ce terme désigne la dépendance entre les
threads. Lors d’un calcul distribué, le résultat final dépend des résultats retournés par chaque thread, il
faut donc attendre que les deux fils d’exécution aient produit le résultat attendu : il faut que les deux fils
d’exécution se synchronisent.
Ce document ne s’étendra pas longuement sur les threads bien qu’ils soient amenés à devenir un élément
incontournable de tout programme désirant tirer parti des derniers processeurs.
4. créer une instance de la nouvelle classe et appeler la méthode start pour lancer le thread secondaire qui
formera le second fil d’exécution.
Le programme principal est appelé le thread principal. Voici ce que cela donne dans un exemple :
# coding: cp1252
import threading, time
Le programme affiche des lignes qui proviennent du thread principal et du thread secondaire dont les
affichages diffèrent.
programme 0
thread 0
thread 1
programme 1
thread 2
programme 2
programme 3
thread 3
programme 4
thread 4
...
Le précédent programme a été adapté pour lancer deux threads secondaires en plus du thread principal.
Les lignes modifiées par rapport au programme précédent sont commentées.
# coding: cp1252
import threading, time
9.2 Synchronisation
9.2.1 Attente
La première situation dans laquelle on a besoin de synchroniser deux threads est l’attente d’un thread
secondaire par le thread principal. Et pour ce faire, on a besoin de l’accès par les deux fils d’exécution à
une même variable qui indiquera l’état du thread. Dans le programme suivant, on ajoute l’attribut etat à
la classe MonThread qui va indiquer l’état du thread :
– True pour en marche
– False pour à l’arrêt
Le thread principal va simplement vérifier l’état du thread de temps en temps. Le premier point important
est tout d’abord d’attendre que le thread se lance car sans la première boucle, le thread pourrait passer
à l’état True après être passé dans la seconde boucle d’attente. Le second point important est de ne pas
oublier d’insérer la fonction sleep du module time afin de permettre au thread principal de temporiser.
Dans le cas contraire, le thread principal passe l’essentiel de son temps à vérifier l’état du thread secondaire,
ce faisant, il ralentit l’ordinateur par la répétition inutile de la même action un trop grand nombre de fois.
Ici, le thread principal vérifie l’état du thread secondaire tous les 100 millisecondes. Cette durée dépend
de ce que fait le thread secondaire.
# coding: latin-1
import threading, time
print "début"
Ce mécanisme d’attente peut également être codé en utilisation les objets Condition et Event du module
threading. Ces deux objets permettent d’éviter l’utilisation de la méthode sleep.
# coding: latin-1
import threading, time
La méthode wait de l’objet Event attend que l’objet soit activé. Elle peut attendre indéfiniment ou attendre
pendant une durée donnée seulement. Pour afficher la durée d’attente, on pourrait utiliser une boucle
comme la suivante :
m.start () La méthode isSet permet de savoir si l’événement est
while not event.isSet (): bloquant ou non. Le programme affiche ”j0 attends”
print "j’attends" puis attend le thread un dixième de secondes. Au delà
event.wait (0.1) de cette durée, il vérifie l’état de l’événement puis re-
print "fin"
commence si le thread n’est pas fini.
Ces objets de synchronisation sont plus efficaces que le mécanisme décrit dans le premier programme car
il fait appel aux fonctions du système d’exploitation.
ou modifie en même temps qu’un autre modifie la même variable. Le second cas de synchronisation est
l’ajout de verrous qui permettent de protéger une partie du code d’un programme contre plusieurs accès
simultanés. Ce verrou est également un objet du module threading.
Dans cet exemple, l’information partagée est la chaîne de caractères message, le verrou sert à protéger la
fonction ajoute contre des ajouts simultanés. Si les deux threads veulent modifier message en même temps,
un thread va entrer dans la fonction ajoute alors que l’autre n’en est pas encore sorti. Les résultats seraient
imprévisibles car cette fonction modifie la variable qu’ils utilisent. On aboutit à l’exemple suivant :
# coding: latin-1
import threading, time
message = ""
verrou = threading.Lock () 5
# synchronisation attente
e1 = threading.Event ()
e2 = threading.Event () 35
e1.clear ()
e2.clear ()
e1.wait ()
e2.wait () 45
Les trois instructions protégées pourraient être résumées en une seule : message += c ; le résultat res-
terait inchangé. En revanche, en commentant les instructions verrou.acquire() et verrou.release() de ce
programme 1 , la longueur du résultat final message est variable alors qu’elle devrait être de 20 puisque les
1. Celles marquées d’une étoile (∗).
9. Threads 202
deux threads appellent chacun 10 fois dans la fonction ajoute. Le tableau suivant montre l’évolution des
variables message, c, s durant deux premiers appels qui s’entremêlent. Le résultat devrait être ”12” pour
message mais un caractère a été perdu. Il faut retenir que si la variable message est globale, les deux autres
c, s sont locales et donc différentes pour les deux threads.
Le verrou empêche d’exécuter une même portion de code en même temps, un code qui modifie des données
partagées. C’est pourquoi le verrou est souvent déclaré au même endroit que les données qu’il protège. Le
verrou de type Lock n’autorise qu’un seul thread à la fois à l’intérieur de la portion de code protégée ce
qui aboutit au schéma suivant :
# coding: latin-1
import threading, time, random, copy
# définition du thread
class MonThread (threading.Thread) : 5
def __init__ (self, win, res) :
threading.Thread.__init__ (self)
self.win = win # on mémorise une référence sur la fenêtre
self.res = res
10
def run (self) :
for i in range (0, 10) :
print "thread ", i
time.sleep (0.1)
15
# afin que le thread retourne un résultat
# self.res désigne thread_resultat qui reçoit un nombre de plus
h = random.randint (0,100)
self.res.append (h)
20
# on lance un événement <<thread_fini>> à la fenêtre principale
# pour lui dire que le thread est fini, l’événement est ensuite
# géré par la boucle principale de messages
# on peut transmettre également le résultat lors de l’envoi du message
# en utilisant un attribut de la classe Event pour son propre compte 25
self.win.event_generate ("<<thread_fini>>", x = h)
thread_resultat = []
def lance_thread () : 30
global thread_resultat
# fonction appelée lors de la pression du bouton
# on change la légnde de la zone de texte
text .config (text = "thread démarré")
text2.config (text = "thread démarré") 35
# on désactive le bouton pour éviter de lancer deux threads en même temps
bouton.config (state = TK.DISABLED)
# on lance le thread
m = MonThread (root, thread_resultat)
m.start () 40
import Tkinter as TK
# on crée la fenêtre
root = TK.Tk () 55
bouton = TK.Button (root, text = "thread départ", command = lance_thread)
text = TK.Label (root, text = "rien")
text2 = TK.Label (root, text = "rien")
bouton.pack ()
text.pack () 60
text2.pack ()
65
# on active la boucle principale de message
root.mainloop ()
La figure 9.1 contient la fenêtre affichée par le programme lorsqu’elle attend la pression du bouton qui
lance le thread et lorsqu’elle attend la fin de l’exécution de ce thread.
Remarque 9.4 : méthode event_generate
Le programme précédent utilise une astuce pour retourner un résultat autrement qu’un utilisant un pa-
ramètre global. On peut adjoindre lors de l’appel à la méthode event_generate quelques informations
supplémentaires attachées à l’événement en utilisant les attributs prédéfinis de la classe Event. Dans cet
exemple, on utilise l’attribut x pour retourner le dernier entier tiré aléatoirement.
Cette pile est utilisée dans l’exemple qui suit pour simuler deux joueurs qui essaye de découvrir le nombre
que l’autre joueur a tiré au hasard. A chaque essai, un joueur envoie un message de type (”essai”, n)
à l’autre joueur pour dire qu’il joue le nombre n. Ce joueur lui répond avec des messages de type
(”dessous”, n), (”dessus”, n), (”gagne”, n).
# coding: latin-1
import threading, time, Queue, random
break
elif n < x : self.autre.Dessus (n)
else : self.autre.Dessous (n)
elif m == "dessus" :
a = max (a, n+1) 50
continue # assure l’équité en mode l’un après l’autre
elif m == "dessous" :
b = min (b, n-1)
continue # assure l’équité en mode l’un après l’autre
elif m == "gagne" : 55
print self.nom, " : j’ai gagné en ", i, " essais, solution ", n
break
# fini
print self.nom, " : j’arrête"
self.event.set ()
70
# on crée des verrous pour attendre la fin de la partie
e1 = threading.Event ()
e2 = threading.Event ()
e1.clear ()
e2.clear () 75
# le jeu commence 85
A.start ()
B.start ()
Si la méthode get est choisie, les joueurs doivent attendre une tentative de l’adversaire avant de proposer la
leur. Dans l’autre cas, la méthode get_nowait permet de ne pas attendre sa réponse et d’envoyer plusieurs
propositions à l’adversaire qui ne répondra pas plus vite pour autant. Dans cette configuration, le joueur
A est trois fois plus réactif ce qui explique les résultats qui suivent.
A : je joue ( 8 )
B : je joue ( 569 )
A : je tente 42 écart 1000 à traiter 0
A : je tente 791 écart 1000 à traiter 0
...
A : je tente 528 écart 62 à traiter 0
B : je tente 20 écart 43 à traiter 57
A : je tente 508 écart 62 à traiter 0
A : je tente 548 écart 62 à traiter 0
B : je tente 8 écart 43 à traiter 59
A : j’ai perdu après 67 essais
A : j’arrête
9. Threads 207
Les affichages se chevauchent parfois, il faudrait pour éviter cela synchroniser l’affichage à l’aide d’un
verrou.
Deuxième partie
Cette partie regroupe quelques énoncés que les élèves de l’ENSAE ont étudié ou à partir desquels ils
ont été évalués entre les années 2005 et 2008. On retient souvent que le boulot d’un informaticien n’est
pas compliqué, qu’il est plutôt plutôt ennuyeux et redondant. On oublie parfois que sans l’informatique,
certaines tâches seraient tout autant longues et fastidieuses. Cette partie présente certaines tournures
d’esprits qui permettent de gagner du temps lors de la conception de programmes et peut-être derendre
la programmation moins répétitive, le travail moins fastidieux.
Les premiers énoncés illustrent quelques schémas récurrents de programmation. Les langages de program-
mation qui nécessitent de reprogrammer un tri sont très rares, la plupart proposent des fonctions qui
exécutent un tri rapide - ou quicksort -. Mais connaître certains algorithmes classiques permet de pro-
grammer plus rapidement qu’avec la seule connaissance des principes de base - boucle, test -. A l’instar
de certains jeux de réflexion - les ouvertures avec les échecs -, il n’est pas nécessaire de réinventer certains
algorithmes ou raisonnements fréquemment utilisés.
Cette partie se termine par trois années d’énoncés d’examens d’informatique. Les exercices proposés illus-
trent parfois le côté ennuyeux d’un programme informatique : sa mise en œuvre dépasse souvent de loin
sa conception. La correction des erreurs ou bugs est la partie la plus longue et la plus rébarbative. Ces
exercices ont pour objectif d’accélérer cette étape en proposant des situations qui reviennent fréquemment.
C’est aussi pour cela que lors d’entretiens d’embauche, des exercices similaires sont présentés aux candidats
pour vérifier leur expérience en programmation.
Chapitre 10
Exercices pratiques pour s’entraîner
Fonctions utiles :
A l’intérieur de la chaîne s, remplace toutes les occurrences de la chaîne old
par la chaîne new. Pour l’utiliser, il faut ajouter au début du programme la
replace(s, old, new)
ligne import string puis écrire l = string . replace (”dix − neuf”, ” − ”, ” ”), par
exemple.
Retourne une liste contenant tous les mots de la chaîne de caractères s. Si le pa-
ramètre facultatif sep est renseigné, c’est cette chaîne qui fait office de séparateur
split(s[, sep]) entre mot et non les espaces. Pour l’utiliser, il faut ajouter au début du pro-
gramme la ligne import string puis écrire l = string . split (”vingt et un”, ” ”),
par exemple.
L’appel à la fonction replace peut se faire en la considérant comme une méthode de la classe str ou une
fonction du module string.
import string
s = "dix-neuf"
1) Ecrire une fonction qui prend un nombre littéral et retourne une liste contenant tous ses mots.
2) Ecrire une fonction qui reçoit une chaîne de caractères correspondant à un nombre compris entre 0 et
16 inclus ou à un nombre parmi 20, 30, 40, 50, 60. La fonction doit retourner un entier correspondant à
sa valeur.
3) A l’aide de la fonction précédente, écrire une fonction qui reçoit une chaîne de caractères contenant un
nombre compris entre 0 et 99 inclus et qui retourne sa valeur.
4) Pour vérifier que la fonction précédente marche bien dans tous les cas, écrire une fonction qui écrit un
nombre sous sa forme littérale puis vérifier que ces deux fonctions marchent correctement pour les nombres
compris entre 0 et 99.
10. Exercices pratiques pour s’entraîner 211
10.1.2 Correction
A chaque question correspond une fonction utilisant celles décrites dans les questions précédentes. Le
programme complet est obtenu en juxtaposant chaque morceau.
1)
# coding: latin-1
def lire_separation(s):
"""divise un nombre littéral en mots"""
s = s.replace ("-", " ") # on remplace les tirets par des espaces
# pour découper en mots même
# les mots composés
return s.split ()
On pourrait écrire cette fonction en utilisant les expressions régulières en utilisant les expressions régulières
(voir split décrite au paragraphe 7.6.3). page 164.
import re
def lire_separation(s):
"""divise un nombre littéral en mots avec les expressions régulières"""
return re.compile ("[- ]").split (s)
2) Il n’existe pas qu’une seule manière de rédiger cette fonction. La première, la plus simple, est sans doute
la suivante :
def valeur_mot (s) :
"""convertit numériquement les nombres inclus entre 0 et 16 inclus,
20, 30, 40, 50, 60, s est une chaîne de caractères, le résultat est entier"""
if s == "zéro" : return 0
elif s == "un" : return 1
elif s == "deux" : return 2
elif s == "trois" : return 3
elif s == "quatre" : return 4
elif s == "cinq" : return 5
elif s == "six" : return 6
elif s == "sept" : return 7
elif s == "huit" : return 8
elif s == "neuf" : return 9
elif s == "dix" : return 10
elif s == "onze" : return 11
elif s == "douze" : return 12
elif s == "treize" : return 13
elif s == "quatorze" : return 14
elif s == "quinze" : return 15
elif s == "seize" : return 16
elif s == "vingt" : return 20
elif s == "trente" : return 30
elif s == "quarante" : return 40
elif s == "cinquante" : return 50
elif s == "soixante" : return 60
else : return 0 # ce cas ne doit normalement pas
# se produire
La solution suivante utilise un dictionnaire. Cette écriture peut éviter un copier-coller long pour la dernière
question.
def valeur_mot (s) :
dico = {’cinquante’: 50, ’quarante’: 40, ’onze’: 11, ’huit’: 8, ’six’: 6, \
’quinze’: 15, ’trente’: 30, ’douze’: 12, ’cinq’: 5, ’deux’: 2, \
’quatorze’: 14, ’neuf’: 9, ’soixante’: 60, ’quatre’: 4, \
’zéro’: 0, ’treize’: 13, ’trois’: 3, ’seize’: 16, \
’vingt’: 20, ’un’: 1, ’dix’: 10, ’sept’: 7}
10. Exercices pratiques pour s’entraîner 212
if s not in dico : return 0 # cas imprévu, on peut résumer ces deux lignes
else : return dico [s] # par return dico.get (s, 0)
3) La réponse à cette question est divisée en deux fonctions. La première traduit une liste de mots en
un nombre numérique. Ce nombre est la somme des valeurs retournées par la fonction valeur_mot pour
chacun des mots : soixante dix neuf = 60 + 10 + 9. Cette règle s’applique la plupart du temps sauf pour
quatre vingt.
def lire_dizaine_liste(s):
"""convertit une liste de chaînes de caractères dont
juxtaposition forme un nombre littéral compris entre 0 et 99"""
r = 0 # contient le résultat final
dizaine = False # a-t-on terminé le traitement des dizaines ?
for mot in s:
n = lire_unite (mot)
if n == 20 :
if not dizaine and r > 0 and r != 60 :
r *= n # cas 80
dizaine = True
else : r += n
else : r += n
return r
La seconde fonction remplace tous les traits d’union par des espaces puis divise un nombre littéral en liste
de mots séparés par des espaces.
def lire_dizaine(s):
li = lire_separation (s)
return lire_dizaine_liste (li)
Cette fonction pourrait être simplifiée en utilisant le même dictionnaire que pour la fonction valeur_mot
mais inversé : la valeur devient la clé et réciproquement.
def mot_valeur (x):
"""convertit un nombre compris inclus entre 0 et 16 inclus,
20, 30, 40, 50, 60 en une chaîne de caractères"""
dico = {’cinquante’: 50, ’quarante’: 40, ’onze’: 11, ’huit’: 8, ’six’: 6, \
’quinze’: 15, ’trente’: 30, ’douze’: 12, ’cinq’: 5, ’deux’: 2, \
’quatorze’: 14, ’neuf’: 9, ’soixante’: 60, ’quatre’: 4, \
’zéro’: 0, ’treize’: 13, ’trois’: 3, ’seize’: 16, \
’vingt’: 20, ’un’: 1, ’dix’: 10, ’sept’: 7}
inv = {}
for k,v in dico.iteritems () : inv [v] = k
inv [70] = "soixante-dix"
inv [80] = "quatre-vingt"
inv [90] = "quatre-vingt-dix"
return inv [x]
Le programme continue avec l’une ou l’autre des fonctions précédente. La fonction suivante construit le
montant littéral pour un nombre à deux chiffres. Elle traite le chiffre des dizaines puis des unités.
def ecrit_dizaine(x):
"""convertit un nombre entre 0 et 99 sous sa forme littérale"""
s = ""
dizaine = x / 10
unite = x % 10
s = mot_valeur (dizaine*10)
s += " "
s += mot_valeur (unite)
return s
Le morceau de code ci-dessous permet de vérifier que le passage d’un nombre sous sa forme littérale et
réciproquement fonctionne correctement pour tous les nombres compris entre 0 et 99 inclus.
for i in xrange(0,100):
s = ecrit_dizaine (i)
j = lire_dizaine (s)
if i != j : print "erreur ", i, " != ", j, " : ", s
– deux coordonnées comprises entre 1 et 10, ou (0,0) si le pion n’est plus sur le damier
– un entier qui vaut 1 pour blanc, 2 pour noir
2. Un tableau d’entiers à deux dimensions, chaque case contient :
– soit 0 s’il n’y a pas de pion
– soit 1 si la case contient un pion blanc
– soit 2 si la case contient un pion noir
Y a-t-il d’autres représentations de ces informations ? Si on considère que l’efficacité d’une méthode est
reliée à sa vitesse - autrement dit aux coûts des algorithmes qu’elles utilisent -, parmi ces deux représen-
tations, quelle est celle qui semble la plus efficace pour savoir si un pion donné du damier est en mesure
d’en prendre un autre ?
2) Comment représenter un tableau d’entiers à deux dimensions en langage Python à l’aide des types
standards qu’il propose, à savoir t-uple, liste ou dictionnaire ?
3) On cherche à écrire l’algorithme qui permet de savoir si un pion donné est un mesure de prendre un
pion. Quels sont les paramètres d’entrées et les résultats de cet algorithme ?
4) Il ne reste plus qu’à écrire cet algorithme.
10.2.2 Correction
1) La seconde représentation sous forme de tableau à deux dimensions est plus pratique pour effectuer les
tests de voisinages. Chaque case a quatre voisines aux quatre coins, il est ensuite facile de déterminer si
ces quatre voisines sont libres ou si elles contiennent un pion. On sait rapidement le contenu d’une case.
Avec la première représentation - le tableau des pions - pour savoir s’il existe un pion dans une case voisine,
il faut passer en revue tous les pions pour savoir si l’un d’eux occupe ou non cette case. Avec la seconde
représentation - le tableau à deux dimensions - on accède directement à cette information sans avoir à la
rechercher. On évite une boucle sur les pions avec la seconde représentation.
2) Pour représenter le tableau en deux dimensions, il existe trois solutions :
1. Une liste de listes, chaque ligne est représentée par une liste. Toutes ces listes sont elles-mêmes assemblées
dans une liste globale.
2. Une seule liste, il suffit de numéroter les cases du damier de 0 à 99, en utilisant comme indice pour la case
(i, j) : k = 10 ∗ i + j. Réciproquement, la case d’indice k aura pour coordonnées (k/10, k%10).
3. Un dictionnaire dont la clé est un couple d’entiers.
3) On désire savoir si le pion de la case (i, j) peut en prendre un autre. On suppose que le tableau à deux
dimensions est une liste de dix listes appelée damier. damier[i][j] est donc la couleur du pion de la case
(i, j), à savoir 0 si la case est vide, 1 si le pion est blanc, 2 si le pion est noir. Pour ces deux derniers cas,
la couleur des pions de l’adversaire sera donc 3 − damier[i][j]. Les entrées de la fonctions sont donc les
indices i, j et le damier damier. La sortie est une variable booléenne qui indique la possibilité ou non de
prendre. On ne souhaite pas déplacer les pions.
4)
def pion_prendre(i,j,damier):
c = damier [i][j]
if c == 0: return False # case vide, impossible de prendre
c = 3 - c # couleur de l’adversaire
5
if damier [i-1][j-1] == c : # s’il y a un pion adverse en haut à gauche
if damier [i-2][j-2] == 0 : # si la case d’après en diagonale est vide
return True # on peut prendre
Voici une fonction équivalente lorsque le damier est un dictionnaire dont la clé est un couple d’entiers.
def pion_prendre(i,j,damier):
c = damier [(i,j)] # ou encore damier [i,j]
if c == 0: return False # case vide, impossible de prendre
c = 3 - c # couleur de l’adversaire
5
# test pour une prise du pion dans les quatre cases voisines
if damier [i-1,j-1] == c and damier [i-2,j-2] == 0: return True
if damier [i-1,j+1] == c and damier [i-2,j+2] == 0: return True
if damier [i+1,j-1] == c and damier [i+2,j-2] == 0: return True
if damier [i+1,j+1] == c and damier [i+2,j+2] == 0: return True 10
La même fonction lorsque le damier est représenté par une seule liste.
def pion_prendre(i,j,damier):
c = damier [10*i+j]
if c == 0: return False # case vide, impossible de prendre
c = 3 - c # couleur de l’adversaire
5
# test pour une prise du pion dans les quatre cases voisines
if damier [10*(i-1)+j-1] == c and damier [10*(i-2)+j-2] == 0: return True
if damier [10*(i-1)+j+1] == c and damier [10*(i-2)+j+2] == 0: return True
if damier [10*(i+1)+j-1] == c and damier [10*(i+2)+j-2] == 0: return True
if damier [10*(i+1)+j+1] == c and damier [10*(i+2)+j+2] == 0: return True 10
return False
Pour ces trois cas, aucun effet de bord n’a été envisagé. Si la case est trop près d’un des bords, un des
indices i, j, i − 1, j − 1, i + 1, j + 1, i − 2, j − 2, i + 2, j + 2 désignera une case hors du damier. Le code
de la fonction pion_prendre devra donc vérifier que chaque case dont elle vérifie le contenu appartient au
damier.
def pion_prendre(i,j,damier):
c = damier [i][j]
if c == 0: return False # case vide, impossible de prendre
c = 3 - c # couleur de l’adversaire
5
# on répète ce test pour les trois autres cases
if i >= 2 and j >= 2 and \
damier [i-1][j-1] == c and damier [i-2][j-2] == 0: return True
if i >= 2 and j < len (damier)-2 and \
damier [i-1][j+1] == c and damier [i-2][j+2] == 0: return True 10
return False
Une autre option consiste à entourer le damier d’un ensemble de cases dont le contenu sera égal à une
constante différente de 0, −1 par exemple. Dans ce cas, si le damier est représenté par une liste de listes,
la première case du damier aura pour coordonnées (1, 1) au lieu de (0, 0) car les listes n’acceptent pas les
indices négatifs. Ce n’est pas le cas lorsque le damier est représenté par un dictionnaire car une case peut
tout à fait avoir pour coordonnées (−1, −1).
10. Exercices pratiques pour s’entraîner 216
Avec cette convention, les tests introduits dans le dernier programme ne seront plus nécessaires. Il faudra
juste réécrire la seconde ligne de chaque fonction pion_prendre par :
if c <= 0 : return False # au lieu de if c == 0 : return False
possible d’écrire une fonction qui détermine automatiquement la langue d’un texte à condition que celle-ci
soit l’anglais ou le français.
10.3.2 Correction
1) La fonction répondant à la première question revient régulièrement dans beaucoup de programmes.
def lit_fichier (nom) :
f = open (nom, "r") # ouverture du fichier
l = f.read () # on récupère le contenu
f.close () # on ferme le fichier
return l # on retourne le contenu
On pourrait également souhaiter récupérer un texte directement depuis Internet à condition d’en connaître
l’adresse, ce que fait la fonction suivante. La première fonction doit recevoir un nom de fichier, la seconde
une adresse Internet.
import urllib # import du module urllib
def lit_url (nom) :
f = urllib.urlopen (nom) # on ouvre l’url
res = f.read () # on lit son contenu
f.close () # on termine la lecture
return res # on retourne le résultat
On peut regrouper ces deux fonctions et appeler soit l’une soit l’autre selon que le nom du texte est un
fichier texte ou une adresse Internet :
s = lit ("hugo.txt")
Une autre option consiste à utiliser les exceptions : on essaye d’abord d’ouvrir un fichier avec la fonction
open. Si cela ne fonctionne pas, on peut supposer que le nom du fichier fait référence à une adresse Internet.
s = lit ("hugo.txt")
2) La fonction suivante compte les occurrences de chaque lettre dans la chaîne de caractères texte. Une
première solution consiste à appeler 26 fois la méthode count des chaînes de caractères.
def compte_lettre_count (texte) :
texte = texte.upper () # pour éviter les accents
res = { } # résultat, vide pour le moment
for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" : # pour tout l’alphabet
res [c] = texte.count (c) # on compte les occurrences de c
return res
10. Exercices pratiques pour s’entraîner 218
Une autre solution consiste à parcourir le texte puis à compter les lettres au fur et à mesure qu’elles
apparaissent.
def compte_lettre (texte) :
texte = texte.upper () # pour éviter les accents
res = { } # résultat, vide pour le moment
for c in texte : # pour tous les caractères du texte
if not("A" <= c <= "Z") : continue # si ce n’est pas une lettre, on passe
if c not in res : res [c] = 1 # si elle n’est pas là, on lui affecte 1
else : res [c] += 1 # sinon, on augmente son nombre d’apparitions
return res
Il n’est pas évident de choisir l’une ou l’autre des méthodes. Calculer le coût algorithmique de chacune
des fonctions n’est pas toujours évident car la première utilise la fonction count dont le fonctionnement
est inconnu. Il paraît plus facile dans ce cas de comparer la vitesse d’exécution de chaque fonction.
Dans l’exemple suivant, la fonction comparaison appelle les deux méthodes sur le même texte. Le module
profile va ensuite mesurer le temps passé dans chacune des fonctions.
def comparaison () :
s = lit_fichier ("hugo.txt")
compte_lettre_count (s) # on ne mémorise pas les résultats
compte_lettre (s) # car on souhaite mesurer le temps passé
A la fin du programme, le temps passé dans chaque fonction est affiché par le module profile. On peut y
lire le nombre de fois qu’une fonction a été appelée, le temps moyen passé dans cette fonction.
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.160 0.160 0.174 0.174 langue.py:19(compte_lettre)
1 0.000 0.000 0.017 0.017 langue.py:31(compte_lettre_count)
1 0.000 0.000 0.190 0.190 langue.py:43(comparaison)
La première solution est clairement la plus rapide. Ce résultat s’explique par la rapidité de la méthode
count. Mais cela ne permet de pas de conclure dans tous les cas. Il faudrait connaître le coût algorithmique
de chaque fonction pour cela. Par exemple, lorsqu’il s’agit de calculer les fréquences de tous les caractères
et non plus des seules 26 lettres, on s’aperçoit que la seconde solution n’est plus que 1,5 fois plus lente et
non 10 fois.
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.120 0.120 0.133 0.133 langue.py:19(compte_lettre)
1 0.002 0.002 0.082 0.082 langue.py:32(compte_lettre_count)
Lorsque le nombre d’éléments dont il faut calculer la fréquence est assez grand, la seconde solution est
préférable. La première solution est efficace dans des cas particuliers comme celui-ci. On s’aperçoit égale-
ment que la fonction compte_lettre est plus rapide pour cette seconde expérience (0.133 < 0.174) mais
cette observation est peu pertinente car cette fonction n’a été exécutée qu’une fois. Il y a en effet peu de
10. Exercices pratiques pour s’entraîner 219
chance pour que la fonction s’exécute à chaque fois à la même vitesse car l’ordinateur exécute un grand
nombre de tâches simultanément.
Pour obtenir un temps d’exécution fiable, il faut appeler la fonction un grand nombre de fois. Le tableau
suivant montre le temps moyen total passé dans chaque fonction après 100 appels à la fonction comparaison.
La fonction compte_lettre est effectivement plus rapide dans le second cas. Comme on s’intéresse main-
tenant à tous les caractères, le test if not (”A” <= c <= ”Z”) n’est plus nécessaire : la fonction est d’au-
tant plus rapide que cette instruction était exécutée un grand nombre de fois. On remarque également
que la fonction compte_lettre ne paraît pas dépendre du nombre de caractères considérés. La fonction
compte_lettre est un meilleur choix.
4) Le tableau suivant montre les probabilités obtenues pour les quatre lettres H,U,W,Y pour lesquelles
les résultats montrent des différences significatives. Les deux textes sélectionnés sont Le dernier jour d’un
condamné de Victor Hugo et sa traduction anglaise The Man Who Laughs.
Ces chiffres ont été obtenus avec le programme suivant qui télécharge ces deux textes directement depuis le
site Gutenberg 2 . Les fréquences de ces quatre lettres montrent des différences significatives, ce que montre
de façon plus visuelle la figure 10.1 pour les lettres W et H.
# le dernier jour d’un condamné
c1 = compte_lettre (lit_url ("http://www.gutenberg.org/dirs/etext04/8ldrj10.txt"))
# the man who laughs
c2 = compte_lettre (lit_url ("http://www.gutenberg.org/files/12587/12587-8.txt"))
car = c1.keys ()
car.sort ()
for k in car :
print k, " : ", "% 2.2f" % (c1 [k] * 100), "%", " % 2.2f" % (c2 [k] * 100), "%"
5) La fréquence de la lettre W ou H devrait suffire à départager un texte français d’un texte anglais. Pour
vérifier cette hypothèse, on décrit chaque texte par deux coordonnées correspondant aux fréquences des
lettres H et W, ce que calcule la fonction suivante :
def langue_lettre (texte) :
if "http" in texte : s = lit_url (texte) # cas URL
else : s = lit_fichier (texte) # cas fichier
c = compte_lettre (s) # on compte les lettres
return c ["W"], c ["H"] # on retourne deux fréquences
2. Il est conseillé de vérifier ces adresses depuis le site http://www.gutenberg.net/, ces liens peuvent changer.
10. Exercices pratiques pour s’entraîner 220
La dernière instruction de cette fonction suppose que tous les textes incluent au moins un W et un H. Dans
le cas contraire, la fonction produirait une erreur car elle ne pourrait pas trouver la clé ”W” ou ”H” dans le
dictionnaire c. Pour remédier à cela, on utilise la méthode get des dictionnaires qui permet de retourner
une valeur lorsqu’une clé est introuvable.
def langue_lettre (texte) :
... # lignes inchangées
return c.get ("W", 0.0), c.get ("H", 0.0) # on retourne deux fréquences
Après cette correction, la fonction retourne une valeur nulle lorsque les lettres W ou H n’apparaissent pas.
La fonction suivante permet de construire deux listes cx et cy qui contiennent les fréquences des lettres W
et H pour une liste de textes.
def curve (li) :
cx,cy = [], []
for l in li : # pour tous les textes de la liste
x,y = langue_lettre (l) # coordonnées d’un texte, fréquence W et H
cx.append (x) # on ajoute x à la liste des abscisses
cy.append (y) # on ajoute y à la liste des ordonnées
return cx,cy
On utilise cette dernière fonction pour faire apparaître sur un graphique plusieurs textes anglais et français.
Chaque texte est représenté par deux coordonnées : la fréquence des lettres W et H. On obtient un nuage
de points dans un plan ce que montre le graphe de la figure 10.1. Ce dernier a été tracé à l’aide du module
matplotlib 3 . Il s’inspire du logiciel Matlab dans la manière de constuire des graphes 4 .
La figure 10.1 montre qu’au delà d’une fréquence de 1% pour la lettre W, le texte est anglais. On en déduit
la fonction qui permet de déterminer la langue d’un texte.
def est_anglais (texte) :
w,h = langue_lettre (texte)
return w > 0.01
La réponse à la dernière question pourrait s’arrêter ici. Toutefois, les paragraphes qui suivent s’intéressent
à la fiabilité de la fonction de la fonction est_anglais dans un autre contexte : Internet.
Interprétation
En effet, ce dernier résultat peut être critiqué : ce seuil s’appuie sur seulement quatre textes. Ces derniers
sont longs : la fréquence des lettres en est d’autant plus fiable. Sur un texte d’une centaine de lettres, la
3. http://matplotlib.sourceforge.net/
4. Il a l’inconvénient de ne pas supporter les accents français mais il est possible de contourner cet obstacle en associant
MatPlotLib et Latex qui permet également d’afficher des formules mathématiques sur le graphique. L’utilisation de ce genre
de modules est plutôt facile, de nombreux exemples de graphiques sont proposés avec le programme Python qui a permis de
les réaliser. Il suffit souvent des recopier et de les adapter. Une autre solution consiste à écrire les courbes dans un fichier texte
puis à les récupérer avec un tableur tel que OpenOffice pour les représenter sous forme de graphe (voir l’exemple page 149).
10. Exercices pratiques pour s’entraîner 221
fréquence de la lettre W est soit nulle, soit supérieure à 1%. Au delà de ce cas difficile et peu fréquent, on
vérifie la fiabilité de la fonction est_anglais sur des articles de journaux français et anglais.
Internet va nous aider à construire une base de textes : il est facile des récupérer automatiquement de
nombreux articles de journaux sur des sites tels que celui du journal Le Monde ou le New-York Times. On
pourra ensuite calculer la fréquence des lettres W et H pour chacun d’entre eux.
Récupération de pages HTML
Une page Internet contient du texte dont la présentation est décrite par le format HTML. On y trouve
tout type de contenu, des images, de la vidéo, du texte, et des hyperliens. Le site du journal Le Monde 5
contient beaucoup de liens vers des articles qui eux-mêmes contiennent des liens vers d’autres articles
(voir figure 10.2). A partir d’une seule page, on récupére une myriade de liens Internet. L’objectif est ici
d’explorer quelques pages pour récupérer des liens vers des articles de ce journal. La récupération des pages
Internet d’un site s’inspire de la récupération de la liste de tous les fichiers inclus dans un répertoire 6 .
La principale différence provient du fait que les liens ne désignent pas toujours une page nouvelle mais
parfois la première page du site ou une autre page déjà vue dont il ne faut pas tenir compte. L’autre
différence vient du faite que le nombre de pages sur un site comme Le Monde peut s’avérer très grand : il
faut bien souvent se limiter aux premières pages et à celles qui appartiennent à ce site.
Le format HTML fonctionne par balises : une information est encadrée par deux balises : <balise> texte
... </balise>. Les hyperliens utilisent une syntaxe à peine plus compliquée puisqu’une balise peut aussi
recevoir quelques paramètres : <balise param1="valeur1" param2="valeur2"> texte ... </balise>.
Selon cette syntaxe, un lien vers une autre page est indiqué comme suit :
<a href="http://...."> texte qui apparaît à l’écran </a>
Les expressions régulières 7 permettent de retrouver aisément tous les liens insérés dans une page sans avoir
à se soucier du format HTML. L’algorithme de recherche consiste à recenser tous les liens de la première
page. La syntaxe HTML nous apprend que ces liens sont compris entre guillemets. On considère ensuite
5. http://www.lemonde.fr/
6. voir paragraphe 7.3.3 page 154
7. voir paragraphe 7.6 page 161
10. Exercices pratiques pour s’entraîner 222
chacune des pages recensées et on procède de même. On s’arrête après avoir obtenu le nombre de pages
souhaité.
import urllib # pour accéder une adresse internet
import re # pour traiter les expressions régulières
# expression régulières
# tous les liens commençant par root et entre guillemets
# exemple : "http://www.lemonde.fr/index,0.26.html"
# on place entre parenthèses la partie intéressante :
# c’est-à-dire tout ce qu’il y a entre les guillemets
s = "\"(" + root + "[-_~a-zA-Z0-9/.?,]*?)\""
exp = re.compile (s, re.IGNORECASE)
res = [ ] # résultat
pile = [ site ] # page à explorer
try :
f = urllib.urlopen (u) # accès à l’url
text = f.read () # on lit son contenu
f.close () # fin de l’accès
return res
Il ne reste plus qu’à appeler cette fonction pour récupérer la liste des URLs :
url = "http://www.lemonde.fr/" # un journal français
res = list_url (url, url)
for r in res : print r
Résultats
On détermine alors la fréquence des lettres sur 88 textes français et 181 textes anglais. Les résultats obtenus
sont ceux de la figure 10.3. La frontière est nettement plus floue et montre à première vue qu’il n’est pas
10. Exercices pratiques pour s’entraîner 223
toujours possible de distinguer une page française d’une page anglaise. Toutefois, la plupart des pages mal
classées par cette méthode contiennent peu de texte et plus d’images : les fréquences portent plus sur la
syntaxe HTML que sur un contenu littéraire. Il faudrait pour améliorer la qualité des résultats filtrer les
pages pour ne conserver que leur contenu textuel. Cela montre aussi que cette méthode est plutôt robuste
puisqu’on voit deux classes se dessiner nettement malgré les caractères parasites HTML.
import mdp,copy,numpy
nbfr,nben = len (fr), len (en)
all = numpy.array (fr + en) # construction du nuage de points
L’ACP donne de bons résultats dans la mesure où les deux classes sont visiblement disctinctes et concen-
trées. Dans un cas comme celui-ci pourtant, il serait plus adéquat d’utiliser une Analyse Discriminante
Linéaire ou Fisher Discriminant Analysis (FDA) en anglais. Il s’agit de déterminer la meilleure séparation
linéaire entre ces deux classes. Les résultats sont dans ce cas assez proches.
8. voir le livre Probabilités, analyse des données et statistique de Gilbert Saporta (éditions Technip).
9. http://mdp-toolkit.sourceforge.net/
10. Exercices pratiques pour s’entraîner 224
Figure 10.4 : La frontière entre les textes anglais et français est beau-
coup plus nette dans ce plan de projection issu de l’analyse en composantes
principales.
node = mdp.nodes.FDANode ()
node.train (allfr, "fr")
node.train (allen, "en")
node.stop_training ()
node.train (allfr, "fr")
node.train (allen, "en")
y = node (all)
1 1 1
1 1 1
1 1 1
3) Ecrire une méthode qui calcule la somme des chiffres sur la ligne i.
4) Ajouter une ligne dans la méthode __str__ afin d’afficher aussi la somme des chiffres sur la ligne 0.
Cette information correspond à la somme des chiffres sur chaque ligne ou chaque colonne si le carré est
magique.
5) Ecrire une méthode qui calcule la somme des chiffres sur la colonne j.
6) Sans tenir compte des diagonales, écrire une méthode qui détermine si un carré est magique.
7) Il ne reste plus qu’à inclure les diagonales.
8) Ecrire une fonction qui détermine si tous les nombres du carré magique sont différents.
9) Terminer le programme pour déterminer tous les carrés magiques 3x3 dont les nombres sont tous
différents.
Remarque 10.3 : différence entre print et return
A la fin d’un calcul, afin de voir son résultat, on utilise souvent l’instruction print. On peut se demander
alors si à la fin de chaque fonction, il ne faudrait pas utiliser l’instruction print. A quoi servirait alors
l’instruction return ? On suppose qu’un calcul est en fait le résultat de trois calculs à la suite :
a = calcul1 (3)
b = calcul2 (a)
c = calcul3 (b) # c résultat souhaité et affiché
Chaque terme calculx cache une fonction or seul le résultat de la dernière nous intéresse et doit être
affiché. Pour les deux premières, la seule chose importante est que leur résultat soit transmis à la fonction
suivante et ceci ne peut se faire que grâce à l’instruction return. L’instruction print insérée dans le code
de la fonction calcul1 ou calcul2 permettra d’afficher le résultat mais ne le transmettra pas et il sera
perdu. L’instruction return est donc indispensable, print facultative.
En revanche, dans la dernière fonction calcul3, il est possible de se passer de return et de se contenter
uniquement d’un print. Cependant, il est conseillé d’utiliser quand même return au cas où le résultat de
la fonction calcul3 serait utilisé par une autre fonction.
10.4.2 Correction
1) Il suffit de créer instance de type CarreMagique.
cm = CarreMagique (m)
2) Le message affiché par l’instruction print cm avant d’ajouter la méthode __str__ correspond au sui-
vant :
<__main__.CarreMagique instance at 0x01A254E0>
La réponse à cette question et aux suivantes sont présentées d’un seul tenant. Des commentaires permettent
de retrouver les réponses aux questions de l’énoncé excepté pour la dernière question.
# coding: latin-1
class CarreMagique :
# réponse à la question 2 10
10. Exercices pratiques pour s’entraîner 226
# réponse à la question 3
def somme_ligne (self, i) :
s = 0
for j in range (0, len (self.nb)) : s += self.nb [i][j] 25
return s
# réponse à la question 5
def somme_colonne (self, j) :
s = 0 30
for i in range (0, len (self.nb)) : s += self.nb [i][j]
return s
# réponse à la question 6
def est_magique (self) : 35
# on stocke toutes les sommes
l = []
for i in range (0, len (self.nb)) : l.append ( self.somme_ligne (i) )
for j in range (0, len (self.nb)) : l.append ( self.somme_colonne (j) )
40
# réponse à la question 7
l.append ( self.somme_diagonale (0))
l.append ( self.somme_diagonale (1))
# on trie la liste 45
l.sort ()
# réponse à la question 7
def somme_diagonale (self, d) :
"""d vaut 0 ou 1, première ou seconde diagonale"""
s = 0 55
if d == 0 :
for i in range (0, len (self.nb)) : s += self.nb [i][i]
else :
for i in range (0, len (self.nb)) :
s += self.nb [i][len(self.nb)-i-1] 60
return s
# réponse à la question 8
def nombre_differents (self) :
"""retourne True si tous les nombres sont différents, 65
on place les nombres un par un dans un dictionnaire,
dès que l’un d’eux s’y trouve déjà,
on sait que deux nombres sont identiques, le résultat est False"""
k = { }
for i in range (0, len (self.nb)) : 70
for j in range (0, len (self.nb)) :
c = self.nb [i][j]
if c in k : return False # pas besoin d’aller plus loin
# il y a deux nombres identiques
else : k [c] = 0 75
10. Exercices pratiques pour s’entraîner 227
return True
La dernière question consiste à trouver tous les carrés magiques dont les nombres sont tous différents. Il faut
donc passer en revue tous les carrés 3x3 et ne conserver que ceux pour lesquels les méthodes est_magique
et nombre_differents retournent un résultat positif. On suppose que le programme précédent contenant
la classe CarreMagique porte le nom de carre_magique.py. Ceci explique la ligne from....
from carre_magique import CarreMagique
res = []
for a in range (1,10) :
for b in range (1,10) :
for c in range (1,10) : 5
for d in range (1,10) :
for e in range (1,10) :
for f in range (1,10) :
for g in range (1,10) :
for h in range (1,10) : 10
for i in range (1,10) :
l = [ [a,b,c], [d,e,f], [g,h,i] ]
cm = CarreMagique (l)
if cm.nombre_differents () and \
cm.est_magique () : 15
res.append (cm)
L’inconvénient de cette solution est qu’elle est inadaptée pour des carrés d’une autre dimension que 3x3.
Il faudrait pouvoir inclure un nombre de boucles variable ce qui est impossible. L’autre solution est de
programmer un compteur en reproduisant l’algorithme d’une addition. C’est l’objectif du programme
suivant.
# coding: latin-1
from carre_magique import CarreMagique
dim = 3
nb = dim*dim 5
res = [] # contiendra la liste des carrés magiques
# résultat final
print len (res)
for r in res : print r 35
Il existe une autre manière de parcourir les carrés magiques en décomposant un entier compris entre 1 et
99 en une Psuccession de neuf entiers. On utilise pour cela la décomposition d’un nombre en base neuf :
n − 1 = 9k=1 αk (ik − 1) où tous les nombres ik sont dans l’ensemble {1, . . . , 9}.
# coding: latin-1
from carre_magique import CarreMagique
dim = 3
nb = dim*dim 5
M = 9 ** nb # on va tester 9^9 carrés possibles
res = [] # contiendra la liste des carrés magiques
Il faut noter l’utilisation de la fonction xrange au lieu de la fonction range lors de la première boucle.
La fonction xrange ne crée pas de liste, elle simule un itérateur qui parcourt une liste. Dans notre cas, la
fonction range crée une liste de 99 éléments qui ne tient pas en mémoire, l’erreur suivante est déclenchée :
Ces trois solutions sont très lentes car elles explorent 99 solutions. Comme on ne cherche que les carrés
magiques dont les éléments sont tous différents, on pourrait n’explorer que les permutations soit 9! confi-
gurations. On crée une fonction permutation qui parcourt toutes les permutations possibles par récurrence
(voir figure 10.5).
# coding: latin-1
from carre_magique import CarreMagique
# permutations
res = []
permutation ( res, 3 )
# résultats 35
print "nombre de carrés ", len (res)
for r in res : print r
Le programme indique huit carrés magiques qui sont en fait huit fois le même carré obtenus par rotation
ou symétrie. La somme des chiffres est 15.
10. Exercices pratiques pour s’entraîner 230
Les nœuds avant et apres sont appelés les successeurs. Le terme opposé est prédécesseur. Ces deux nœuds
ont nécessairement un prédécesseur mais un nœud n’a pas forcément de successeurs. S’il en avait toujours
un, l’arbre serait infini.
1) On cherche à construire une classe ayant pour nom NoeudTri et qui contient une chaîne de caractères
initialisée lors de la création de la classe : n = NoeudTri(”essai”).
2) On écrit la méthode __str__ de sorte que l’instruction print n affiche la chaîne de caractères que
contient n.
3) On cherche maintenant à définir d’autres nœuds, reliés à des attributs avant et apres. On suppose que
les nœuds utilisent l’attribut mot, on crée alors une méthode insere(s) qui :
– Si s < self.mot, alors on ajoute l’attribut avant = NoeudTri(s).
– Si s > self.mot, alors on ajoute l’attribut apres = NoeudTri(s).
Fonction utile :
Compare deux chaînes de caractères, retourne -1,0,1 selon que s1 est classée avant,
cmp(s1, s2)
est égale ou est classée après s2.
4) La méthode __str__ n’affiche pour le moment qu’un mot. Il s’agit maintenant de prendre en compte
les attributs avant et apres afin que l’instruction print n affiche avant.__str__() et apres.__str__(). Il
faudra également faire en sorte que la méthode avant.__str__() ne soit appelée que si l’attribut avant
existe. Comme la liste des mots à trier est finie, il faut bien que certains nœuds n’aient pas de successeurs.
On pourra s’inspirer du programme page 85 (attribut __dict__). Qu’est-ce qu’affiche le programme
suivant ?
racine = NoeudTri ("un")
racine.insere ("unite")
racine.insere ("deux")
print racine
10. Lorsqu’on parle de coût moyen, cela signifie que le coût n’est pas constant en fonction de la dimension du problème.
Ici, le coût moyen désigne le coût moyen d’un tri quicksort obtenu en faisant la moyenne du coût du même algorithme sur
toutes les permutations possibles de l’ensemble de départ.
10. Exercices pratiques pour s’entraîner 231
5) Est-il possible de trier plus de trois mots avec ce programme ? Que faut-il modifier dans la méthode
insere afin de pouvoir trier un nombre quelconque de mots ?
6) Ajouter le code nécessaire afin que la méthode insere génère une exception lorsqu’un mot déjà présent
dans l’arbre est à nouveau inséré.
7) On se propose de construire une image représentant l’arbre contenant les mots triés par l’algorithme
quicksort. Cette représentation utilise le module pydot qui utilise l’outil Graphviz 11 ainsi que le module
pyparsing 12 . Leur installation est assez facile sous Windows puisqu’elle consiste seulement à exécuter un
programme d’installation. Il faut ensuite installer manuellement le module pydot 13 . Après avoir décom-
pressé les fichiers de ce module, il faut se placer dans le même répertoire et utiliser la ligne de commande
suivante :
c:\python26\python setup.py install
Le tracé d’un graphe passe par l’écriture d’un fichier dans lequel on indique les nœuds et les arcs. Un
nœud est un numéro suivi de [label = ”deux”] qui indique que ce nœud contient le mot deux. Si on ajoute
, style = filled, shape = record, le nœud est représenté par un rectangle au fond grisé. Un arc est un
couple de numéros séparés par ->. Le texte [label="<"] permet d’étiqueter cet arc. Selon cette syntaxe,
le texte suivant décrit l’image située à droite :
digraph GA {
2 [label="deux",style=filled,shape=record]
3 [label="abc" ,style=filled,shape=record]
2 -> 3 [label="<"]
}
Fonction utile :
id(obj) Retourne un identifiant entier unique pour chaque variable obj.
Une fois que cette chaîne de caractères a été construite, il suffit de l’écrire dans un fichier puis d’appeler
le module pydot pour le convertir en image avec le code suivant :
g = open ("graph.txt", "w") #
g.write (graph) # partie écriture dans un fichier
g.close () #
dot = pydot.graph_from_dot_file ("graph.txt") # partie graphe
dot.write_png ("graph.png", prog="dot") # avec pydot
L’objectif de cette question est de construire une chaîne de caractères pour l’ensemble du graphe. La
correction de cette exercice envisage également de construire une page HTML contenant le graphe et la
liste triée ainsi que la création automatique d’un fichier au format PDF par le biais de Latex 14 .
10.5.2 Correction
1) La chaîne de caractères que contient NoeudTri s’appelle mot.
class NoeudTri (object):
def __init__(self,s):
self.mot = s
11. http://www.graphviz.org/
12. http://pyparsing.wikispaces.com/
13. http://code.google.com/p/pydot/
14. Latex est un langage adapté aux publications scientifiques, également à l’écriture de livres comme celui-ci. En utilisant
sa syntaxe, il permet de créer rapidement un fichier au format PDF.
10. Exercices pratiques pour s’entraîner 232
2)
3)
La méthode insere prévoit de ne rien faire dans le cas où le mot s passé en argument est égal à l’attribut
mot : cela revient à ignorer les doublons dans la liste de mots à trier.
4)
def __str__(self):
s = ""
if "avant" in self.__dict__: s += self.avant.__str__ ()
s += self.mot + "\n"
if "apres" in self.__dict__: s += self.apres.__str__()
return s
5) 6) Il reste à compléter la fonction insere afin qu’elle puisse trouver le bon nœud où insérer un nouveau
mot. Cette méthode est récursive : si un nœud contient deux attributs avant et apres, cela signifie que le
nouveau mot doit être inséré plus bas, dans des nœuds reliés soit à avant soit à apres. La méthode insere
choisit donc un des attributs et délègue le problème à la méthode insere de ce nœud.
# coding: latin-1
import string
class NoeudTri :
15
def __str__(self):
s = ""
if "avant" in self.__dict__: s += self.avant.__str__ ()
s += self.mot + "\n"
if "apres" in self.__dict__: s += self.apres.__str__() 20
return s
racine = None
for mot in l :
if racine == None :
# premier cas : aucun mot --> on crée le premier noeud 40
racine = NoeudTri (mot)
else :
# second cas : il y a déjà un mot, on ajoute le mot suivant
# à l’arbre
racine.insere (mot) 45
print racine
Chaque nouveau mot va partir du tronc pour s’accrocher à une feuille de l’arbre pour devenir à son tour
une feuille. La méthode nouveau_noeud crée un nouveau nœud dans le graphe. Son utilité est mise en
évidence par le prochain programme.
7) La figure 10.6 détient le graphe obtenu par le programme qui suit. Plutôt que de modifier la classe
NoeudTri, une seconde est créée qui hérite de la première. On lui adjoint la méthode chaine_graphe qui
convertit un graphe en une chaîne de caractères dont le format reprend celui énoncé plus haut. Cette
fonction s’occupe de construire récursivement cette chaîne de caractères. Pour identifier chaque nœud, on
utilise la fonction id qui retourne un identifiant distinct pour chaque instance de classe.
Figure 10.6 : Graphe de tri obtenu lors du tri quicksort. Chaque nœud du graphe
inclut un mot. Les symboles "<" et ">" des arcs désignent les membres avant et
apres de la classe NoeudTri. Tous les mots attachés à un arc "<" d’un nœud sont
classés avant le mot de ce nœud. De même, tous les mots attachés à un arc ">"
d’un nœud sont classés après le mot de ce nœud.
10. Exercices pratiques pour s’entraîner 234
# coding: latin-1
import string
import pydot
import quicksort
5
class NoeudTri2 (quicksort.NoeudTri):
def construit_arbre () : 40
# même code que dans le programme précédent
# mais inclus dans une fonction
l = ["un", "deux", "unite", "dizaine", "exception", "dire", \
"programme", "abc", "xyz", "opera", "quel"]
racine = None 45
for mot in l :
if racine == None : racine = NoeudTri2 (mot)
else : racine.insere (mot)
return racine
50
racine = construit_arbre ()
print racine
racine.image ("graph.txt", "graph.png")
La méthode nouveau_noeud permet de s’assurer que tous les nœuds insérés lors de la création du graphe
seront bien du type NoeudTri2 qui inclut la méthode chaine_graphe. Cette méthode serait inutile s’il n’y
avait qu’une seule classe NoeudTri contenant toutes les méthodes. Si on la met en commentaire, le message
d’erreur suivant apparaît :
Traceback (most recent call last):
File "quicksort2.py", line 53, in <module>
racine.image ("graph.txt", "graph.png")
File "quicksort2.py", line 27, in image
10. Exercices pratiques pour s’entraîner 235
graph = self.chaine_graphe ()
File "quicksort2.py", line 14, in chaine_graphe
h = self.avant.chaine_graphe ()
AttributeError: NoeudTri instance has no attribute ’chaine_graphe’
L’erreur signifie que le programmeur cherche à appeler une méthode qui n’existe pas dans la classe NoeudTri
parce que seul le premier nœud de l’arbre est de type NoeudTri2 contrairement aux nœuds insérés par la
méthode nouveau_noeud de la classe NoeudTri. En surchargeant cette méthode, on s’assure que tous les
nœuds sont du même type NoeudTri2. Il existe néanmoins une façon d’éviter de surcharger cette fonction
à chaque fous. Il suffit qu’elle crée automatiquement la bonne classe, que l’objet soit une instance de
NoeudTri ou NoeudTri2. C’est ce que fait l’exemple suivant où self.__class__ correspond à la classe de
l’objet.
Au final, la méthode image construit l’image du graphe. Le fichier graphe.txt doit ressembler à ce qui suit :
digraph GA {
18853120 [label="un",style=filled,shape=record]
28505472 [label="deux",style=filled,shape=record]
28505712 [label="abc",style=filled,shape=record]
28505472 -> 28505712 [label="<"]
28505552 [label="dizaine",style=filled,shape=record]
28505592 [label="dire",style=filled,shape=record]
28505552 -> 28505592 [label="<"]
28505632 [label="exception",style=filled,shape=record]
28505672 [label="programme",style=filled,shape=record]
28505792 [label="opera",style=filled,shape=record]
28505672 -> 28505792 [label="<"]
28505832 [label="quel",style=filled,shape=record]
28505672 -> 28505832 [label=">"]
28505632 -> 28505672 [label=">"]
28505552 -> 28505632 [label=">"]
28505472 -> 28505552 [label=">"]
18853120 -> 28505472 [label="<"]
28505512 [label="unite",style=filled,shape=record]
28505752 [label="xyz",style=filled,shape=record]
28505512 -> 28505752 [label=">"]
18853120 -> 28505512 [label=">"]
}
La numérotation des nœuds importe peu du moment que chaque nœud reçoit un identifiant unique. C’est
pour cela que la fonction id est pratique dans ce cas-là. Le programme suivant construit une sortie au
format HTML mélangeant image et texte. Il commence par importer le programme quicksort2 qui n’est
autre que celui incluant la classe NoeudTri2. Il termine en appelant le navigateur Mozilla Firefox afin
d’afficher le résultat automatiquement.
# coding: latin-1
import quicksort2
# construction de l’arbre
racine = quicksort2.construit_arbre () 5
# construction de l’image du graphe
racine.image ("graph.txt", "graph.png")
Le fichier page.html contient les lignes suivantes excepté les points de suspension qui remplacent la partie
tronquée qu’on peut aisément deviner.
<body><html>
<H1> liste triée </H1>
abc<BR>
deux<BR>
...
unite<BR>
xyz<BR>
<H1> graphe </H1>
<img src="graph.png" width=400/>
<H1> code du graphe </H1>
<pre>
13697312 [label="un",style=filled,shape=record,fontsize=60]
13697192 [label="deux",style=filled,shape=record,fontsize=60]
34692472 [label="abc",style=filled,shape=record,fontsize=60]
...
13697232 -> 34692592 [label=">",fontsize=60]
13697312 -> 13697232 [label=">",fontsize=60]
</pre>
</html></body>
On veut modifier ce programme pour que la sortie ne soit plus au format HTML mais au format PDF.
Pour cela, on utilise comme intermédiaire le langage Latex qui s’occupe de créer ce fichier au format PDF.
Les dernières lignes sont propres au système Windows où on suppose que la version de Latex installée est
Miktex 2.7 15 . Il faut également le logiciel Adobe Reader 16 .
# coding: latin-1
import quicksort2
# construction de l’arbre
racine = quicksort2.construit_arbre () 5
# construction de l’image du graphe
racine.image ("graph.txt", "graph.png")
header = """\\documentclass[french,11pt]{article}\n\\usepackage[french]{babel}
\\usepackage[usenames]{color}\\usepackage{""" + \ 15
"}\n\\usepackage{".join (package) + \
"""}\\usepackage[small,normal]{caption2}\\urlstyle{sf}
\\usepackage[pdftex]{graphicx}\usepackage[T1]{fontenc}
\DefineVerbatimEnvironment{verbatimx}{Verbatim}{frame=single,
framerule=.1pt, framesep=1.5mm, fontsize=\\footnotesize,xleftmargin=0pt} 20
\\begin{document}\n"""
La partie header est longue dans cet exemple, elle inclut des packages Latex qui ne sont pas utilisés mais
qui pourraient l’être dans le cas d’un rapport plus long. Il faut bien sûr connaître quelques rudiments
de ce langage pour construire le document PDF. L’avantage de ce système est de pouvoir retravailler
manuellement le document final. Il est également indiqué lorsque le rapport mélange tableaux de chiffres
récupérés depuis différentes sources et graphiques qui peuvent être générés par Python via un module
comme matplotlib 17 .
Sous Linux, il suffit de modifier les chemins d’accès aux différentes applications. Une dernière remarque,
il existe sous Windows un éditeur convenable qui est TeXnicCenter 18 .
fin correction TD 10.5.1 u t
17. http://matplotlib.sourceforge.net/
18. http://www.toolscenter.org/
10. Exercices pratiques pour s’entraîner 238
1) La première étape consiste à construire une fonction qui permette de générer un ensemble aléatoire tel
que celui de la figure 10.7. Voici quelques fonctions utiles.
import random
n = random.gauss (0,1) # loi normale de moyenne 0, de variance 1
u = random.random () # loi uniforme [0,1]
import math
x,y = math.cos (t), math.sin(t) # cosinus, sinus
2) Pour vérifier que la fonction précédente fonctionne, il est préférable d’afficher le nuage de points avec
le module matplotlib 19 .
3) On note le nuage de points : Pi = (xi , yi ). On veut construire une matrice M = (dij ) où chaque
coefficient est égale à une distance euclidienne : dij = kPi − Pj k. Cette matrice pourra par exemple être
une liste de listes.
4) On souhaite utiliser un algorithme de clustering ou de classification non supervisée : on souhaite regrou-
per les observations proches les unes des autres à l’intérieur de plusieurs classes. Une méthode très connue
est celle des nuées dynamiques mais elle ne permet pas de construire que des classes convexes. Nous allons
ici utiliser un algorithme différent qui permet de découper un nuage de points en deux sous-classes en
espérant que chacun des deux cercles s’y retrouve.
L’algorithme de clustering est itératif. On part de la matrice M pour construire une matrice M 0 = (c0ij ).
Ensuite, on remplace M par M 0 pour recommencer jusqu’à convergence de la méthode. On note Li le
19. voir http://matplotlib.sourceforge.net/examples/api/unicode_minus.html
10. Exercices pratiques pour s’entraîner 239
vecteur constituant la ligne i de la matrice M . kLi − Lj k est la distance entre les lignes i et j. N est le
nombre de ligne. Le coefficient c0ij a pour valeur :
kLi − Lj k
c0ij = (10.1)
maxk kLi − Lk k
On peut commencer par écrire la fonction qui calcule cette nouvelle matrice. Le module numpy et sa classe
matrix pourront éviter quelques boucles 20 . Voici quelques fonctions utiles qu’il est suggéré d’essayer avant
de se lancer dans de longs développements.
import numpy as np
mat = np.matrix ( [[1,2],[3,4]] ) # crée une matrice 2*2
s = mat.shape # égale à (nombre de lignes, nombre de colonnes)
l = mat [0,:] # retourne la première ligne
c = mat [:,0] # retourne la première colonne
mat [:,0] = mat [:,1] # la première ligne est égale à la seconde
o = np.ones ( (10,10) ) # crée un matrice de 1 10x10
d = np.diag (d) # extrait la diagonale d’une matrice
dd = np.matrix (d) # transforme d en matrice
t = mat.transpose () # obtient la transposée
e = mat [0,0] # obtient de première élément
k = mat * mat # produit matriciel
m = mat * 4 # multiplie la matrice par 4
mx = np.max (mat [0,:]) # obtient le maximum de la première ligne
s = np.sum (mat [0,:]) # somme de la première ligne
l = mat.tolist () # transformer une matrice en list
Il est aussi suggérée d’exécuter l’example suivante afin de comprendre et d’utiliser certaines notations qui
facilitent d’écriture de nombreux calculs.
Enfin, pour les plus aguerris, qui veulent améliorer la rapidité d’un calcul, il faut se pencher sur la fonction
vectorize 21 :
Une dernière remarque : l’algorithme utilise une distance qui implique le calcule d’une racine carrée.
Optimiser un calcul de façon matriciel peut amener à utiliser l’une ou l’autre des deux formes suivantes :
sX sX
2
kX − Y k = (xi − yi ) = x2i + yi2 − 2xi yi (10.2)
i i
A cause des approximations numériques, il peut arriver que le résultat sous la racine carrée soit très petit
et aussi négatif. Et ce cas a la désagréable habitude de provoquer une erreur.
20. http://www.scipy.org/Numpy_Example_List
21. http://www.scipy.org/Numpy_Example_List#head-f6eefa5593a61ec3b95d514e0dc2f932b96fa115
10. Exercices pratiques pour s’entraîner 240
Pour résumer, l’objectif de cette question est donc de construire d’abord une matrice carrée M ∗ à partir
d’une matrice carrée M de même dimension où chaque coefficient c∗ij est la distance euclidienne entre les
lignes Li et Lj de la matrice M .
sX
c∗ij = kLi − Lj k = (cik − cjk )2 (10.3)
k
On obtient ensuite la matrice M 0 en normalisant les coefficients sur une même ligne :
c0ij
c0ij = (10.4)
maxk c∗ ik
Cette question a pour objectif de construire la fonction qui fait passer de M , à M ∗ puis à M 0 .
5) Il ne reste plus qu’à écrire une fonction qui réalise les itérations successives :
1. On transforme M en M 0 en utilisant la fonction précédente.
2. On remplace M par M 0 .
3. On répète les étapes 1 et 2 plusieurs fois jusqu’à obtenir une matrice dont les coefficients sont soit 0,
soit 1.
6) Sur la première ligne, si c01j est presque nul et loin de 1, alors le point j appartient à la classe 0, sinon
à la classe 1. Il faut obtenir la classe pour chaque point.
7) Il ne reste plus qu’à dessiner le résultat.
Voici un code qui permet de télécharger le cours d’une action dont on connaît le code entre les dates d1 et
d2.
Un petit exemple d’utilisation qui télécharge le cours d’une action sur la dernière année.
import datetime
import os.path
def telecharge_et_ecrit_dans_un_fichier (code) :
year = datetime.datetime (2009,1,1) - datetime.datetime (2008,1,1)
today = datetime.datetime (2009,1,1).now ()
lyear = today - year
file = code + ".txt"
if os.path.exists (file) : return # si le fichier existe déjà, ne le fait pas
res = download_quotes (a, lyear, today)
f = open (file, "w")
f.write (res)
f.close ()
10.6.2 Correction
1)
2)
3) 4) 5) 6)
7) Résultat de la classification, figure 10.8.
# coding: latin-1
import sys,random,copy,math
import matplotlib.pyplot as plt
import numpy as np
5
def random_set (nb = 100) :
"""construit un échantillon aléatoire avec deux cercles concentriques,
nb pour le premier, nb*2 pour le second"""
res = []
for i in range (0, nb) : 10
x,y = random.gauss (0,1),random.gauss (0,1)
res.append ([x,y])
for i in range (0, nb*2) :
x,y = random.gauss (0,1),random.gauss (0,1)
n = (x**2 + y**2) ** 0.5 15
if n == 0 : n == 1.0
x *= 5.0 / n
y *= 5.0 / n
x += random.gauss (0,0.5)
y += random.gauss (0,0.5) 20
res.append ([x,y])
res.sort ()
return res
if clas == None :
fig = plt.figure() 30
ax = fig.add_subplot(111)
x = [ p [0] for p in points ]
y = [ p [1] for p in points ]
ax.plot (x,y, ’o’)
plt.savefig ("im1.png") 35
else :
fig = plt.figure()
ax = fig.add_subplot(111)
x = [ p [0] for p,c in zip (points, clas) if c == 0 ]
y = [ p [1] for p,c in zip (points, clas) if c == 0 ] 40
ax.plot (x,y, ’o’)
x = [ p [0] for p,c in zip (points, clas) if c == 1 ]
y = [ p [1] for p,c in zip (points, clas) if c == 1 ]
ax.plot (x,y, ’x’)
plt.savefig ("im2.png") 45
di = np.diag (prod)
di = np.matrix (di) 55
one = np.ones ((1,lin))
ii = one.transpose () * di
jj = di.transpose () * one
dist = prod * (-2) + ii + jj
60
def sqrt (x) : return x**0.5 if x >= 0 else 0.0
func_sqrt = np.vectorize (sqrt, otypes=[float])
dist = func_sqrt (dist)
# autre essai 65
#def m (x) : return x**0.6 if x >= 0 else 0.0
#func_m = np.vectorize (m, otypes=[float])
#dist = func_m (dist)
#code dont la logique est plus explicite mais il est beaucoup plus lent 70
#for i in xrange (0, lin) :
# for j in xrange (0, lin) :
# x = (prod [i,i] + prod [j,j] - 2*prod [i,j])
#
# if x <= 0 : dist [i,j]= 0 #problème d’arrondi numérique 75
# else : dist [i,j]= x**0.5
return dist
"""algorithme"""
mat = np.matrix (points)
lin,col = mat.shape
dist = distance_ligne (mat)
for i in range (0,50) : 95
print "itération i", i, np.min (dist [0,1:]), \
np.max (dist), np.sum (dist [0,:])
dist = iteration (dist)
return li
8) A propos de la partie financière qui ne traite pas le problème des décalages entre les dates lors du
téléchargement des données mais qui le détecte.
# coding: latin-1
import sys
import random
import matplotlib.pyplot as plt
import numpy as np 5
import copy
import math
import os
from cluster import *
10
import urllib
import datetime
import sys
def get_cac40_quotes () : 15
"""récupère les cotes du CAC40 depuis un fichier texte
format : quote \t nom
"""
file = "cac40_quote.txt"
quote = open (file, "r").readlines () 20
quote = [ q.strip (" \n\r") for q in quote ]
quote = [ q.split ("\t") for q in quote if len (q) > 0 ]
assert 38 <= len (quote) <= 40
return quote
25
def get_date (s) :
"""convertit une date depuis une chaîne de caractères vers un format Python"""
y,m,d = s.split ("-")
y,m,d = int(y),int(m),int(d)
d = datetime.datetime (y,m,d) 30
return d
"""
télécharge la cotation d’une action entre deux dates 35
- code: cote
- d1: première date (format python)
- d2: seconde date (format python)
"""
root = "http://ichart.yahoo.com/table.csv?s=%s&a=%02d&b=%d&c=%d" \ 40
"&d=%02d&e=%d&f=%d&g=d&ignore=.csv" % \
(code, d1.month-1, d1.day, d1.year, d2.month-1, d2.day, d2.year)
f = urllib.urlopen (root)
qu = f.read () 45
f.close ()
lines = qu.split ("\n")
lines = [ l .strip (" \n\r") for l in lines ]
head = lines [0]
lines = lines [1:] 50
lines = [ l.split (",") for l in lines if "," in l ]
lines = [ (get_date (l[0]), l) for l in lines ]
lines = [ l [1] for l in lines if d1 <= l [0] <= d2 ]
lines = [ "\t".join (l) for l in lines ]
55
return "\n".join ([head ] + lines)
print "loading ", a, " from ", lyear , " to ", today, \
" lines ", len (res.split ("\n"))
if __name__ == "__main__" :
110
if not os.path.exists ("quotes"): os.mkdir ("quotes")
quotes = get_cac40_quotes ()
get_quotes_internet (quotes)
mat = get_matrix (quotes)
print mat.shape 115
li = algorithme_cluster (mat)
print li
for a,b in zip (quotes,li) :
print b, a [1], "\t", a [0]
120
Les trois énoncés qui suivent sont prévus pour être réalisés en deux heures sur un ordinateur. Ils ne font
référence à aucun algorithme ou méthode connue. Il suffit de connaître les types standard du Python, les
boucles, les tests et les fonctions. Le second énoncé nécessite également quelques notions sur les classes.
Correction
# coding: latin-1
# question 1
def lit_fichier (file) :
f = open (file, "r")
mot = [] 5
for l in f :
mot.append ( l.replace ("\n", "") )
f.close ()
return mot
10
mot = lit_fichier ("td_note_texte.txt")
print mot
# question 2
def est_trie (mot) : 15
for i in range (1, len (mot)) :
if mot [i-1] > mot [i] :
return False
return True
20
tri = est_trie (mot)
print "liste triée ", tri
# question 3
def cherche (mot, m) : 25
for i in range (0, len (mot)) :
if mot [i] == m :
return i
return -1
30
print "mot ACHATS ", cherche (mot, "ACHATS")
print "mot achats ", cherche (mot, "achats")
# question 4
un = cherche (mot, "UN") 35
deux = cherche (mot, "DEUX")
print "recherche normale ", un, deux
print "nombre d’itérations", un + deux
# question 7
"""
Lors d’une recherche simple, au pire, l’élément cherche sera 60
en dernière position, ce qui signifie n itérations pour le trouver.
Le coût de la recherche simple est en O(n).
"""
11. Exercices pratiques pour s’évaluer 249
# question 8 65
"""
Lors de la recherche dichotomique, à chaque itération, on divise par deux
l’ensemble dans lequel la recherche s’effectue,
au départ n, puis n/2, puis n/4 jusqu’àă ce que n/2^k soit nul
c’est-à-dire k = partie entière de ln n / ln 2 70
il y a au plus k itérations donc le coût de l’algorithme est en O (ln n).
"""
fin exo 1 u
t
Le gouvernement désire ajouter un jour férié mais il voudrait le faire à une date éloignée des jours fériés
existant. On suppose également que ce jour ne sera pas inséré entre Noël et le jour de l’an. On va donc
calculer le nombre de jours qui sépare deux jours fériés dont voici la liste pour l’année 2007 :
On rappelle que l’année 2007 n’est pas une année bissextile et qu’en conséquence, le mois de février ne
comporte que 28 jours.
1) Afin de simplifier la tâche, on cherche à attribuer un numéro de jour à chaque jour férié : l’année a 365
jours, pour le numéro du lundi de Pâques, soit 31 (mois de janvier) + 28 (février) + 31 (mars) + 9 = 89.
La première question consiste à construire une fonction qui calcule le numéro d’une date étant donné un
jour et un mois. Cette fonction prend comme entrée : (4 points)
– un numéro de jour
– un numéro de mois
– une liste de 12 nombres correspondant au nombre de jours dans chacun des douze mois de l’année
2) Si on définit la liste des jours fériés comme étant une liste de couples (jour, mois) triée par ordre
chronologique, il est facile de convertir cette liste en une liste de nombres correspondant à leur numéro
dans l’année. La fonction à écrire ici appelle la précédente et prend une liste de couples en entrée et
retourne comme résultat une liste d’entiers. (4 points)
3) A partir de cette liste d’entiers, il est facile de calculer l’écart ou le nombre de jours qui séparent deux
jours fériés. Il ne reste plus qu’à écrire une fonction qui retourne l’écart maximal entre deux jours fériés,
ceux-ci étant définis par la liste de numéros définie par la question précédente. Un affichage du résultat
permet de déterminer les deux jours fériés les plus éloignés l’un de l’autre. Quels sont-ils ? (4 points)
11. Exercices pratiques pour s’évaluer 250
Le programme précédent n’utilise pas de classe. L’objectif de ce second exercice est de le réécrire avec une
classe.
1) Une fonction du programme précédent effectue la conversion entre un couple jour-mois et un numéro
de jour. Les calculs sont faits avec le numéro mais le résultat désiré est une date : les numéros ne sont que
des intermédiaires de calculs qui ne devraient pas apparaître aussi explicitement. La première question
consiste à créer une classe Date : (2 points)
class Date :
def __init__ (self, jour, mois) :
...
2) A cette classe, on ajoute une méthode qui retourne la conversion du couple jour-mois en un numéro de
jour de l’année. (2 points)
3) On ajoute maintenant une méthode calculant le nombre de jours séparant deux dates (ou objet de type
Date et non pas numéros). Cette méthode pourra par exemple s’appeler difference. (2 points)
4) Il ne reste plus qu’à compléter le programme pour obtenir les mêmes résultats que le programme de
l’exercice 1. (2 points)
5) Avec ce programme, lors du calcul des écarts entre tous les jours fériés consécutifs, combien de fois
effectuez-vous la conversion du couple jour-mois en numéro pour le second jour férié de l’année ? Est-ce le
même nombre que pour le programme précédent (en toute logique, la réponse pour le premier programme
est 1) ? (2 points)
6) La réponse à la question précédente vous suggère-t-elle une modification de ce second programme ?
(2 points)
Correction
# coding: latin-1
####################################
# exercice 1
####################################
5
# question 1
def numero (jour, mois, duree = [31, 28, 31,30,31,30,31,31,30,31,30,31] ) :
s = 0
for i in range (0,mois-1) :
s += duree [i] 10
s += jour - 1
return s+1
# question 2
def conversion_liste (li) : 15
res = []
for jour,mois in s : res.append ( numero (jour, mois))
# pareil que
# for i in range (0, len (s)) : res.append ( numero (s [i][0], s [i][1]))
return res 20
# question 3 35
pos = ec.index ( max (ec) )
print "position de l’écart le plus grand ", pos
print "jour ", s [pos], " --> ", s [pos+1]
#################################### 40
# exercice 2
####################################
# question 4
class Date : 45
def __init__ (self, jour, mois) :
self.jour = jour
self.mois = mois
self.duree = [31, 28, 31,30,31,30,31,31,30,31,30,31]
50
# question 5
def numero (self) :
s = 0
for i in range (0,self.mois-1) :
s += self.duree [i] 55
s += self.jour - 1
return s+1
# question 6
def difference (self, autre) : 60
return self.numero () - autre.numero ()
75
# question 7
s = [ (1,1), (9,4), (1,5), (8,5), (17,5), (4,6), \
(14,7), (15,8), (1,11), (11,11), (25,12) ]
r = conversion_date (s) 80
ec = ecart_date (r)
pos = ec.index ( max (ec) )
print "position de l’ecart le plus grand ", pos
print "jour ", s [pos], " --> ", s [pos+1]
85
# question 8
"""
La conversion en Date est faite une fois pour les dates (1,1) et (25,12)
et 2 fois pour les autres en effet, la méthode difference effectue
la conversion en numéros des dates self et autre 90
la fonction ecart_date calcule date [i].difference ( date [i-1] ) et
date [i+1].difference ( date [i] )
--> la date [i] est convertie 2 fois
11. Exercices pratiques pour s’évaluer 252
"""
95
# question 9
"""
On peut par exemple stocker la conversion en numéro
dans le constructeur comme suit :
""" 100
class Date :
def __init__ (self, jour, mois) :
self.jour = jour
self.mois = mois 105
self.duree = [31, 28, 31,30,31,30,31,31,30,31,30,31]
self.num = self.numero ()
# question 5
def numero (self) : 110
s = 0
for i in range (0,self.mois-1) :
s += self.duree [i]
s += self.jour - 1
return s+1 115
# question 6
def difference (self, autre) :
return self.num - autre.num
120
r = conversion_date (s)
ec = ecart_date (r)
pos = ec.index ( max (ec) )
print "position de l’écart le plus grand ", pos 125
print "jour ", s [pos], " --> ", s [pos+1]
On pourrait encore améliorer la dernière classe Date en créant un attribut statique pour l’attribut duree
qui est identique pour toutes les instances de la classe Date.
fin exo 11.2.1 u t
# coding: latin-1
# la première ligne autorise les accents dans un programme Python
# la langue anglaise est la langue de l’informatique,
# les mots-clés de tous les langages
# sont écrits dans cette langue. 5
####################################
# exercice 1
####################################
# 10
# question 1
def lit_fichier (file) :
f = open (file, "r")
li = f.readlines () # découpage sous forme de lignes 15
f.close ()
res = []
for l in li :
s = l.replace ("\n", "")
s = s.split (" ") # le séparateur des colonnes est l’espace 20
res.append (s)
return res
# question 2
def compte_date (mat) : 30
d = { }
for m in mat :
date = m [1] # clé
if date in d : d [date] += 1
else : d [date] = 1 35
return d
40
# remarque générale : si le fichier logpdf.txt contient des lignes
# vides à la fin, il se produira une erreur à la ligne 34 (date = m [1])
# car la matrice mat contiendra des lignes avec une seule colonne et non quatre.
# Il suffit soit de supprimer les lignes vides du fichier logpdf.txt
# soit de ne pas les prendre en compte lors de la lecture de ce fichier. 45
# question 3
# La méthode sort trie la liste mais comment ?
# Il est facile de trier une liste de nombres mais une liste de couples de
# nombres ? L’exemple montre que la liste est triée selon le premier élément de 50
# chaque couple. Pour les cas où deux couples ont un premier élément en commun,
# les éléments semblent triés selon le second élément. L’exemple suivant le monte :
l = [(1, "un"), (3, "deux",), (2, "deux"), (1, "hun"), (1, "un"), (-1, "moinsun")]
l.sort (reverse = True) 55
print l # affiche [(3, ’deux’), (2, ’deux’), (1, ’un’),
# (1, ’un’), (1, ’hun’), (-1, ’moinsun’)]
# question 4
def dix_meilleures (dico) : 60
# dans cette fonction on crée une liste de couples (valeur,clé) ou
# la clé représente une date et valeur le nombre de téléchargement
# pour cette date
li = []
for d in dico : 65
cle = d
valeur = dico [cle]
li.append ( ( valeur, cle ) )
li.sort (reverse = True)
return li [0:10] 70
# les quatre premières dates correspondent aux quatre premiers TD en 2007 à l’ENSAE 75
# question 5
# la date est en colonne 1, le document en colonne 3
# on fait un copier-coller de la fonction compte_date en changeant un paramètre
def compte_document (mat) : 80
d = { }
for m in mat :
doc = m [3] # clé, 3 au lieu de 1 à la question 2
if doc in d : d [doc] += 1
else : d [doc] = 1 85
return d
# question 6
def heure (s) :
hs = s [0:2] # on extrait la partie correspondant à l’heure
return int (hs) # on retourne la conversion sous forme d’entiers 95
# question 7
# on recommence avec un copier-coller
def compte_heure (mat) :
d = { } 100
for m in mat :
h = m [2] # clé, 2 au lieu de 1 à la question 2
cle = heure (h)
if cle in d : d [cle] += 1
11. Exercices pratiques pour s’évaluer 255
h = compte_heure (mat)
dix = dix_meilleures ( h )
print dix # la première heure est (432, 17), ce qui correspond à l’heure des TD 110
for i in h :
print i, "h ", h [i]
fin exo 4 u
t
2) On souhaite écrire une fonction qui prend comme argument un montant m et une liste pieces tou-
jours triée en ordre décroissant. Cette fonction doit retourner la plus grande pièce inférieure ou égale au
montant m. (3 points)
3) En utilisant la fonction précédente, on veut décomposer un montant m en une liste de pièces dont la
somme est égale au montant. On construit pour cela une seconde fonction qui prend aussi comme argument
un montant m et une liste de pièces pieces. Cette fonction retourne une liste de pièces dont la somme est
égale au montant. Une même pièce peut apparaître plusieurs fois. L’algorithme proposé est le suivant :
1. La fonction cherche la plus grande pièce inférieure ou égale au montant.
2. Elle ajoute cette pièce au résultat puis la soustrait au montant.
3. Si la somme restante est toujours positive, on retourne à la première étape.
Cet algorithme fonctionne pour le jeu de pièces donné en exemple et décompose 49 euros en 49 = 20 +
20 + 5 + 2 + 2. (5 points)
4) Prolongez votre programme pour déterminer avec cet algorithme le plus grand montant compris entre
1 et 99 euros inclus et qui nécessite le plus grand nombre de pièces ? (2 points)
5) On ajoute la pièce 4 à la liste des pièces :
pieces = [1,2,4,5,10,20,50]
Que retourne l’algorithme précédent comme réponse à la question 3 avec cette nouvelle liste et pour le
montant 98 ? Est-ce la meilleure solution ? (2 points)
4. On peut s’aider de tout type de document. Il est possible d’utiliser les méthodes associées à la classe list qu’on peut
aussi retrouver via un moteur de recherche internet avec la requête python list.
11. Exercices pratiques pour s’évaluer 256
6) Comme l’algorithme précédent ne fournit pas la bonne solution pour tous les montants, il faut imaginer
une solution qui puisse traiter tous les jeux de pièces. On procède par récurrence. Soit P = (p1 , . . . , pn )
l’ensemble des pièces. On définit la fonction f (m, P ) comme étant le nombre de pièces nécessaire pour
décomposer le montant m. On vérifie tout d’abord que :
1. La fonction f (m, P ) doit retourner 1 si le montant m est déjà une pièce.
2. La fonction f (m, P ) vérifie la relation de récurrence suivante :
Cet algorithme permet de construire la meilleure décomposition en pièces pour tout montant. La dernière
expression signifie que si le montant m est décomposé de façon optimale avec les pièces (p1 , p2 , p3 , p4 ) alors
le montant m − p4 est aussi décomposé de façon optimale avec les mêmes pièces (p1 , p2 , p3 ).
Il ne reste plus qu’à implémenter la fonction 5 f (m, P ). (5 points)
7) Qu’obtient-on avec le jeu de pièces pieces = [1, 2, 4, 5, 10, 20, 50] ? Prolongez le programme pour déter-
miner tous les choix possibles du président parmi les pièces [3,4,6,7,8,9] ? (1 point)
8) Quel est le coût de votre algorithme ? (facultatif)
Correction
# coding: latin-1
###################################
# question 1 : retourner un tableau
################################### 5
pieces = [1,2,5,10,20,50]
pieces.reverse ()
print pieces # affiche [50, 20, 10, 5, 2, 1]
10
# il existait d’autres solutions
pieces.sort (reverse = True)
##################################################################
# question 2 : trouve la plus grande pièce inférieure à un montant
################################################################## 25
for p in pieces : 30
if p <= montant : return p
# on vérifie
5. Même si l’algorithme est présenté de façon récurrente, il peut être implémenté de façon récurrente ou non. La méthode
récurrente est toutefois déconseillée car beaucoup plus lente.
11. Exercices pratiques pour s’évaluer 257
####################################
# question 3 : décomposer un montant
####################################
45
def decomposer (montant, pieces) :
# on suppose que les pièces sont triées par ordre décroissant
res = [ ] # contiendra la liste des pièces pour le montant
while montant > 0 :
p = plus_grande_piece (montant, pieces) # on prend la plus grande pièce 50
# inférieure ou égale au montant
res.append (p) # on l’ajoute à la solution
montant -= p # on ôté p à montant :
# c’est ce qu’il reste encore à décomposer
return res 55
######################################################
# question 4 : trouver la décomposition la plus grande
###################################################### 65
##################################
# question 5 : décomposition de 98
##################################
85
pieces4 = [1,2,4,5,10,20,50]
pieces4.reverse ()
print "decomposer (98, pieces) = ", decomposer (98, pieces) # [50, 20, 20, 5, 2, 1]
print "decomposer (98, pieces4) = ", decomposer (98, pieces4) # [50, 20, 20, 5, 2, 1] 90
"""
Les deux décompositions sont identiques.
Or il existe une décomposition plus courte avec la pièce 4 :
98 = 50 + 20 + 20 + 4 + 4 = 5 pièces 95
#################################
# question 6 : algorithme optimal 105
#################################
return r
#######################
# question 7 : résultat 155
#######################
# pour trouver la décomposition la plus longue avec n’importe quel jeu de pièces
# on reprend la fonction maximum_piece et on remplace decomposer par decomposer optimale 160
print "maximum_piece (pieces) =", maximum_piece (pieces) # affiche (6, 99) 175
print "maximum_piece (pieces4) =", maximum_piece (pieces4) # affiche (5, 99)
# résultat :
"""
maximum_piece ([50, 20, 10, 5, 3, 2, 1]) = (6, 99) 190
maximum_piece ([50, 20, 10, 5, 4, 2, 1]) = (5, 99) # 4, ok
maximum_piece ([50, 20, 10, 6, 5, 2, 1]) = (6, 99)
maximum_piece ([50, 20, 10, 7, 5, 2, 1]) = (5, 99) # 7, ok
maximum_piece ([50, 20, 10, 8, 5, 2, 1]) = (5, 99) # 8, ok
maximum_piece ([50, 20, 10, 9, 5, 2, 1]) = (5, 98) # 9, ok 195
"""
############################
# question 8 : décomposition 200
############################
"""
On cherche ici le coût de la fonction decomposer_optimal en fonction du montant.
Il n’y a qu’une seule boucle qui dépend du montant, le coût de la fonction est en O(n). 205
P(1) < 0 et lim P(x) = infini lorsque x tend vers infini, 215
donc la plus grande des racines est supérieure à 1.
Le coût de la fonction decomposer_optimal récursive est en O(a^n) avec 1,96 < a < 1,97.
L’algorithme qui décompose un montant de façon optimale quelque soient le montant et le jeu de pièces
s’apparente à la programmation dynamique. C’est le même algorithme que celui qui permet de chercher le
plus court chemin dans un graphe où les nœuds serait les montants de 0 à 99 et où il y aurait autant d’arcs
que de pièces partant de chaque nœuds. Les arcs seraient tous de même poids : 1. Avec cette description,
trouver la meilleure décomposition revient à trouver le chemin incluant le moins d’arcs possible.
11. Exercices pratiques pour s’évaluer 260
fin exo 5 u
t
td_note_2009_cluedo_1.txt et td_note_2009_cluedo_2.txt.
Il est préférable de placer ces pièces jointes dans le même répertoire que le progamme lui-même. Le premier
fichier contient une matrice dont chaque ligne contient 4 informations séparées par des tabulations :
Le second fichier contient une matrice dont chaque ligne contient 3 informations séparées par des tabula-
tions :
Certaines élèves sont présents dans les deux fichiers, l’objectif de cet exercice et de construire un troisième
fichier regroupant les informations concernant les élèves présents dans les deux fichiers. Ce fichier contiendra
donc 5 colonnes.
1) Construire une fonction qui prend comme argument un nom de fichier et qui retourne le contenu du
fichier dans une matrice (une liste de listes) 8 .
2) Construire une fonction qui prend comme entrée une matrice et qui construit un dictionnaire dont :
– la clé est un tuple composé des valeurs des deux premières colonnes (nom, prénom)
– la valeur est la liste des valeurs des autres colonnes
def convertit_matrice_en_dictionnaire (matrice) :
...
3) Construire une fonction qui effectue la fusion de deux dictionnaires. Le dictionnaire résultant vérifie :
– chaque clé est présente dans les deux dictionnaires à la fois
6. Ces données sont complètement aléatoires.
7. Ces données sont aussi complètement aléatoires.
8. On pourra s’inspirer du programme présent à l’adresse http://www.xavierdupre.fr/enseignement/initiation/initiation_
via_python_all/chap7_fichiers_200_.html.
11. Exercices pratiques pour s’évaluer 261
– la valeur associée à une clé est la somme des deux listes associées à cette même clé dans chacun des
deux dictionnaires.
def fusion_dictionnaire (dico1, dico2) :
4) Construire une fonction qui convertit un dictionnaire en matrice. C’est la fonction réciproque de la
fonction de la question 2. Les deux premières colonnes sont celles de la clé, les suivantes sont celles des
valeurs.
5) Ecrire une fonction qui écrit une matrice dans un fichier texte.
6) En utilisant toutes les fonctions précédentes, écrire une fonction qui effectue la fusion de deux fichiers
et qui écrit le résultat dans un autre fichier.
7) En vous inspirant du schéma proposé dans les précédentes questions, il s’agit ici de produire un troisième
fichier qui contient tous les couples (nom, prénom) qui sont présents dans un fichier mais pas dans les
deux à la fois.
8) Serait-il compliqué d’écrire une fonction qui permette de fusionner 3 fichiers ? n fichiers ? Justifier.
A quoi ça sert : cette opération de fusion peut être effectuée sous Excel lorsque la taille des fichiers
n’excède pas 65000 lignes. Au delà, cet énoncé propose un moyen de fusionner des fichiers de plusieurs
millions de lignes.
Correction
# coding: latin-1
# Question 1
mat = list()
for s in l : 10
l = s.strip ("\n\r").split ("\t")
mat.append (l)
return mat
# Question 2 15
# Question 3
11. Exercices pratiques pour s’évaluer 262
# Question 5 40
# Question 6
fusion_fichier ( "td_note_2009_cluedo_1.txt",
"td_note_2009_cluedo_2.txt", 60
"cluedo.txt")
# Question 7
matrice = convertit_dictionnaire_en_matrice(dico)
convertit_matrice_en_fichier (matrice,fichier_resultat)
85
union_moins_intersection_fichier ( "td_note_2009_cluedo_1.txt",
"td_note_2009_cluedo_2.txt",
"cluedo2.txt")
11. Exercices pratiques pour s’évaluer 263
# Question 8 90
"""
Il suffit de fusionner les fichiers deux par deux
en procédant par récurrence. On fusionne d’abord les
deux premiers, puis on fusionne le troisième
au résultat de la première fusion... 95
"""
fin exo 6 u
t
2) On cherche à créer un nuage regroupant n sous-nuages de même variance 1 avec la fonction précédente.
Chaque sous-nuage est centré autour d’une moyenne choisie aléatoirement selon une loi de votre choix.
La fonction dépend de deux paramètres : le nombre de points dans chaque classe et le nombre de classes.
(2 points)
11. Exercices pratiques pour s’évaluer 264
3) Dessiner le nuage avec le module matplotlib pour vérifier que le résultat correspond à vos attentes. On
pourra s’appuyer sur l’extrait qui suit. (1 point)
4) L’algorithme des nuées dynamiques commence par affecter chaque point à une classe choisie au hasard.
Pour cette tâche, on pourra utiliser la fonction randint du module random. On veut créer une fonction qui
retourne une classe aléatoire pour chaque point du nuage. Elle doit prendre comme entrée le nombre de
classes souhaité. (2 points)
On propose de commencer par écrire une fonction qui retourne pour un point donné le barycentre le plus
proche. (2 points)
6) La fonction suivante retourne le barycentre le plus proche pour chaque point. (2 points)
8) L’algorithme commence par la création des classes (fonction n_sous_nuages) et l’attribution d’une classe
au hasard (fonction random_class). Il faut ensuite répéter les fonctions tous_barycentres et association_barycentre.
L’enchaînement de ces opérations est effectué par la fonction nuees_dynamiques. (2 points)
9) Dessiner le résultat permettra de vérifier que tout s’est bien passé, toujours avec un code similaire à
celui-ci. (2 points)
Le corrigé final apparaît après les commentaires qui suivent. Ils sont inspirés des réponses des élèves.
1) 2) Le centre de chaque sous-nuages a été généré selon diverses lois aléatoires, des lois normales, uniformes
réelles ou discrètes.
Quels que soient les points simulés, les réponses aux questions suivantes n’en dépendaient pas. L’algorithme
des centres mobiles s’appliquent à n’importe quel ensemble de points bien que le résultat ne soit pas toujours
pertinent.
3) Utiliser l’exemple de l’énoncé n’a pas posé de problème excepté un cas particulier :
11. Exercices pratiques pour s’évaluer 266
Dans cet exemple, la fonction n_sous_nuages est appelée une fois pour extraire les abscisses, une seconde
fois pour extraire les ordonnées. Etant donné que cette fonction retourne un résultat aléatoire, il est très
peu probable qu’elle retourne deux fois le même résultat. Par conséquence, les abscisses et les ordonnées
ne proviennent pas du même nuage : le graphique résultant ne montrera pas trois nuages séparés.
4) La fonction randint(a, b) retourne un nombre entier aléatoire compris entre a et b inclus. Il fallait donc
bien choisir a et b. Le meilleur choix était a = 0 et b = n − 1. Un autre choix assez fréquent était a = 1
et b = n comme dans l’exemple suivant :
def random_class(l,n):
l = []
for i in range(0,len(l)):
l += [ random.randint (1,n) ]
return l
Les deux réponses sont correctes. Toutefois, la solution ci-dessus implique de faire un peu plus attention
par la suite car elle complique la correspondance entre les barycentres et le numéro de la classe qu’il
représente. En effet, qu’en est-il de la classe 0 dans ce cas. Dans cet exemple, la fonction random_class
n’associe aucun point à la classe 0. On peut alors se demander à quoi correspond le premier élément
du tableau barycentre utilisé dans les fonctions des questions suivantes. Quoi qu’il en soit, la fonction
proche_barycentre retourne l’indice du barycentre le plus proche, pas le numéro de la classe à laquelle il
correspond. Selon les programmes, avec un peu de chance, les numéros des classes ont commencé à 0 après
le premier appel à la fonction proche_barycentre. Le calcul du barycentre de la première classe amène
une division par zéro à moins que ce cas ait été pris en compte.
Dans l’exemple suivant, on tire une classe aléatoire parmi n + 1 numéros. Il y a donc une classe de plus
qu’attendu mais là encore, cette erreur peut être compensée par une autre plus loin.
def random_class(l,n):
l = []
for i in range(0,len(l)):
l += [ random.randint (0,n) ]
return l
Un élève a fait une allusion à la probabilité qu’une classe soit vide : un numéro entre 0 et n − 1 n’est
jamais attribué. On peut déjà se pencher sur la probabilité que la classe 0 soit vide. Chaque point a un
N
probabilité n1 d’être associé à la classe 0. La probabilité cherchée est donc : n−1
n où N est le nombre
N
de points. On peut ainsi majorer la probabilité qu’une classe soit vide par : n n−1
n .
5) La fonction proche_barycentre a été plutôt bien traitée malgré deux erreurs fréquentes. La première
concerne la fonction puissance lors du calcul de la distance euclidienne :
d= ( (p[0]-f[0])**2+(p[1]-f[1])**2 ) ** (1/2)
Dans l’exemple précédente, 1/2 est une division entière et son résultat est nul. Comme ∀x, x0 = 1 (pour
Python du moins), toutes les distances calculées sont donc égales à 1. Il faut noter que la racine carrée
11. Exercices pratiques pour s’évaluer 267
n’était pas indispensable puisqu’on cherchait le barycentre le plus proche : seule la plus petite valeur
comptait et non la valeur elle-même.
L’autre erreur fréquente est celle-ci :
On retourne non pas l’indice du barycentre le plus proche mais la distance de ce barycentre au point
considéré.
6) Cette question a été bien traitée. Les erreurs introduites dans la fonction précédentes se sont propagées
sans provoquer d’erreur d’exécution.
7) Dans la fonction suivante, si la plupart ont pensé à ne prendre que les points de la classe numero_class,
ils ont parfois oublié de diviser par le bon nombre d’éléments. Ici, c’est la variable n qui n’est définie nulle
part. Si le programme ne provoque pas d’erreurs, c’est donc une variable globale déclarée avant.
La variable n a parfois été remplacée par len(classes) qui est aussi faux puisque cela correspond au nombre
total de points et non celui de la classe numero_class.
Il arrive que la fonction provoque une division par zéro lorsqu’une classe est vide. C’est un cas à prendre en
compte. L’algorithme peut alors évoluer dans deux directions. La première consiste à supprimer la classe.
Le second choix évite la disparition d’une classe en affectant un ou plusieurs points désignés aléatoirement
à la classe disparue. L’énoncé ne demandait à ce qu’il en soit tenu compte même si cela serait souhaitable.
La fonction tous_barycentre a été victime de deux erreurs. La première est la suivante où on construit
autant de barycentres qu’il y a de points alors qu’on souhaite autant de barycentres qu’il y a de classes :
La seconde erreur intervient lors du numéro de classes et fait écho à la fonction randon_class et ses erreurs.
Dans l’exemple suivant, la classe dont le numéro est le plus grand a pour numéro max(classes). Or dans
la boucle for i in range(0, mx) :, elle est oubliée car la fonction range va jusqu’à mx − 1 inclus. Il aurait
fallu écrire mx = max(classes) + 1. Dans le cas contraire, on perd une classe à chaque fois qu’on appelle la
fonction tous_barycentres.
Il faut noter également que la classe 0 reçoit un barycentre après la fonction tous_barycentres même si
la fonction random_class ne lui en donnait pas lorsqu’elle utilise l’instruction random.randint(1, n).
Peu d’élèves ont utilisé le module numpy. Son usage avait pour but d’éviter une boucle sur les points :
elle ne disparaît pas mais est prise en charge par les fonctions de calcul matriciel proposées par le module
numpy. Cette boucle a persisté dans la grande majorité des solutions envisagées. La difficulté réside dans
la construction d’une ou deux matrices qui mènent au calcul des barycentre par quelques manipulations
matricielles. La correction qui suit présente deux implémentations dont les seules boucles portent sur le
nombre de classes.
8) La dernière fonction de l’algorithme de classification a connu trois gros défauts. Le premier est l’oubli de
la boucle qui permet de répéter les opérations plus d’une fois. Le second défaut apparaît lorsque le résultat
de la fonction association_barycentre n’est pas utilisé. Dans l’exemple suivant, le calcul des barycentres
a toujours lieu avec la liste l qui n’est jamais modifiée : le résultat a est toujours celui de l’algorithme
après la première itération qui est répétée ici 10 fois exactement de la même manière.
La dernière erreur suit la même logique : l’instruction l = a est bien présente mais son effet est annulé par
le fait de générer aléatoirement un numéro de classe à chaque itération.
Enfin, une erreur grossière est parfois survenue : l’exemple suivant change les données du problème à
chaque itération. Le résultat a peu de chance de signifier quoi que ce soit.
9) La dernière question fut plus ou moins bien implémentée, souvent très bien pour le cas particulier de
deux classes. Le cas général où le nombre de classes est variable n’a pas été souvent traité. La correction
complète suit.
11. Exercices pratiques pour s’évaluer 269
# coding: latin-1
import random
import numpy
r = matt * w 195
n = wt * w
r /= n [0,0]
barycentre += [ [ r [0,0], r [1,0] ] ]
fin exo 7 u
t
L’abscisse et l’ordonnée doivent être des réels (float) afin d’être facilement manipulées par la suite.
(3 points) Insérer directement dans le programme la matrice dans sa forme finale ne rapporte pas de point.
def get_tour () :
stour = """Auxerre 3,537309885 47,76720047
Bastia 9,434300423 42,66175842
Bordeaux -0,643329978 44,80820084"""
...
return tour
2) Ecrire une fonction distance qui calcule la distance euclidienne entre deux villes. On supposera que la
distance à vol d’oiseau est une approximation acceptable de la distance entre deux villes. (2 points)
3) Ecrire une fonction longueur_tour qui retourne la longueur d’un circuit. Un circuit est décrit par la
matrice de la première question : on parcourt les villes les unes à la suite des autres dans l’ordre où elles
apparaissent dans la matrice. On commence à Auxerre, on va à Bastia puis Bordeaux pour terminer à
Annecy et revenir à Auxerre. (2 points)
4) Il est facile de vérifier visuellement si un chemin est absurde comme celui de la figure 11.1. La fonction
suivante vous aidera à tracer ce chemin. Il faut la compléter. (2 points)
import pylab
def graph (tour) :
x = [ t[1] for t in tour ]
y = [ t[2] for t in tour ]
....
....
pylab.plot (x,y)
for ville,x,y in tour :
pylab.text (x,y,ville)
pylab.show ()
5) La première idée pour construire un chemin plus court est de partir du chemin initial. On échange
deux villes choisies aléatoirement puis on calcule la distance du nouveau chemin. Si elle est plus courte,
on conserve la modification. Si elle est plus longue, on annule cette modification. On continue tant qu’il
n’est plus possible d’améliorer la distance. (4 points)
6) Le résultat n’est pas parfait. Parfois, les chemins se croisent comme sur la figure 11.2. Pour cela on va
essayer de retourner une partie du chemin. Il s’agit ici de construire une fonction retourne qui retourne
un chemin entre deux villes i et j. (3 points)
Avant l’exécution de cette fonction, on a :
Ou encore :
7) De la même manière qu’à la question 5, on choisit deux villes au hasard. On applique la fonction
précédente entre ces deux villes. Si la distance est plus courte, on garde ce changement, sinon, on revient
à la configuration précédente. On répète cette opération tant que la distance du chemin total diminue.
(2 points)
8) On termine par l’exécution des fonctions permutation, croisement, graph. On vérifie que le chemin
obtenu est vraisemblable même s’il n’est pas optimal.
Les deux transformations proposées pour modifier le chemin sont décrites par les fonctions permutation
et croisement. A aucun moment, on ne s’est soucié du fait que le chemin est circulaire. Est-ce nécessaire ?
Justifier. (2 points)
Correction
La dernière question suggère que l’aspect circulaire du circuit n’a pas été pris en compte par les fonctions
croisement et permutation. Pour échanger deux villes, il n’est nul besoin de tenir compte du fait que
la dernière ville est reliée à la première. En ce qui concerne la fonction croisement, il est vrai qu’on ne
considère aucune portion incluant le segment reliant la première et la dernière ville. Toutefois, lorsqu’on
retourne toutes les villes dans l’intervalle [i] j, on aboutit au même résultat que si on retournait toutes les
villes qui n’y sont pas.
# coding: latin-1
import random, numpy, math, pylab, copy
###
### réponse à la question 1 5
###
def get_tour () :
tour = """Auxerre 3,537309885 47,76720047
Bastia 9,434300423 42,66175842 10
Bordeaux -0,643329978 44,80820084
Boulogne 1,579570055 50,70875168
Caen -0,418989986 49,14748001
Le Havre 0,037500001 49,45898819
Lens 2,786649942 50,40549088 15
Lille 2,957109928 50,57350159
Lyon 4,768929958 45,70447922
Paris 2,086790085 48,65829086
11. Exercices pratiques pour s’évaluer 276
###
### réponse à la question 2
###
45
def distance (tour, i,j) :
dx = tour [i][1] - tour [j][1]
dy = tour [i][2] - tour [j][2]
return (dx**2 + dy**2) ** 0.5
50
###
### réponse à la question 3
###
###
### réponse à la question 4 65
###
###
### réponse à la question 5
### 80
if d >= best :
# si le résultat est plus long --> retour en arrière 105
# ce qui consiste à échanger à nouveau les deux villes
fix += 1
e = tour [i]
tour [i] = tour [j]
tour [j] = e 110
else :
# sinon, on garde le tableau tel quel
best = d
# et on met fix à 0 pour signifier qu’une modification a eu lieu
fix = 0 115
###
### réponse à la question 7 140
###
fix = 0
while True : 150
i = random.randint (0, len(tour)-2)
j = random.randint (i+1, len(tour)-1)
retourne (tour, i,j)
d = longueur_tour (tour)
if d >= best : 155
# retour en arrière
fix += 1
retourne (tour, i,j)
else :
fix = 0 160
best = d
if fix > 10000 : break
###
### réponse à la question 8 165
###
permutation (tour)
d = longueur_tour (tour) 190
print "permutation", d, best
if d < best :
best = d
tttt = copy.deepcopy (tour) 195
nom = 0
elif nom > 2 :
break
else :
nom += 1 200
for k in range (0,3) :
i = random.randint (0, len(tour)-2)
j = random.randint (i+1, len(tour)-1)
e = tour [i]
tour [i] = tour [j] 205
tour [j] = e
return tttt
fin exo 8 u
t
On cherche d’abord à calculer le barycentre de chaque arrondissement. On constuit la fonction def barycentre(mat)
qui retourne un dictionnaire associant un couple de points à chaque arrondissement. (4 points) Cette
question vaut 5 points si le coût de l’algorithme est optimal.
4) On cherche maintenant à connaître le plus proche restaurant en dehors de l’arrondissement. Ecrire une
fonction qui calcule la distance euclidienne entre deux points : def distance(x1, y1, x2, y2). (2 points)
5) Pour un arrondissement, écrire une fonction def plus_proche_restaurant(x, y, arr, mat) qui retourne le
restaurant le plus proche du point (x, y) en dehors de l’arrondissement arr. (4 points)
6) Ecrire une dernière fonction qui retourne sous forme de dictionnaire la densité approchée pour chaque
arrondissement : def densite_approchee(mat). Quelles sont les arrondissements le plus et le moins dense ?
Commentez les résultats et proposez des améliorations ? (4 points)
Correction
# coding: latin-1
import urllib2, math
# question 1
def lit_fichier () : 5
# le principe est le même que pour le chargement d’un fichier
# le programme lit directement les informations depuis Internet
f = urllib2.urlopen ("http://www.xavierdupre.fr/enseignement"\
"/examen_python/restaurant_paris.txt")
11. Exercices pratiques pour s’évaluer 280
s = f.read () 10
f.close ()
lines = s.split ("\n") # on découpe en lignes
# on découpe en colonnes
lines = [ _.strip ("\n\r ").split ("\t") for _ in lines if len (_) > 0 ]
lines = [ _ for _ in lines if len (_) == 3 ] # on supprime les lignes vides 15
# on convertit les coordonnées en réel
lines = [ (a [3:], float (b), float (c)) for a,b,c in lines ]
return lines
# question 2 20
def compte_restaurant (mat) :
# simple comptage, voir le chapitre 3...
compte = { }
for cp,x,y in mat :
if cp not in compte : compte [cp] = 0 25
compte [cp] += 1
return compte
# question 3
def barycentre (mat) : 30
# un barycentre est un point (X,Y)
# où X et Y sont respectivement la moyenne des X et des Y
barycentre = { }
# boucle sur la matrice
for cp,x,y in mat : 35
if cp not in barycentre : barycentre [cp] = [ 0, 0.0, 0.0 ]
a,b,c = barycentre [cp]
barycentre [cp] = [a+1, b+x, c+y]
# boucle sur les barycentres
for cp in barycentre : 40
a,b,c = barycentre [cp]
barycentre [cp] = [b/a, c/a]
# question 5 55
def plus_proche_restaurant (x,y, arr, mat) :
m,mx,my = None, None, None
for cp,a,b in mat :
if cp != arr and (m == None or distance (a,b,x,y) < m) :
mx,my = a,b 60
m = distance (a,b,x,y)
return mx,my
# question 6
def densite_approchee (mat) : 65
g = barycentre (mat)
compte = compte_restaurant (mat)
res = { }
for cp in g : 70
out = plus_proche_restaurant (g [cp][0], g [cp][1], cp, mat)
r = distance (g [cp][0], g [cp][1], out [0], out [1])
aire = math.pi * r ** 2
res [cp] = compte [cp] / aire
11. Exercices pratiques pour s’évaluer 281
75
return res
if __name__ == "__main__" :
fig = plt.figure()
ax = fig.add_subplot(111)
colors = [ ’red’, ’blue’, ’yellow’, ’orange’, ’black’, ’green’,
’purple’, ’brown’, ’gray’, ’magenta’, ’cyan’, ’pink’, ’burlywood’, 120
’chartreuse’, ’#ee0055’]
for cp in barycentre (mat) :
lx = [ m[1] for m in mat if m [0] == cp ]
ly = [ m[2] for m in mat if m [0] == cp ]
c = colors [ int(cp) % len (colors) ] 125
#if cp not in ["02", "20"] : continue
ax.scatter(lx,ly, s = 5., c=c,edgecolors = ’none’ )
plt.show ()
130
fin exo 9 u
t
11. Exercices pratiques pour s’évaluer 282
#coding:latin-1
import urllib, os, os.path, numpy
def charge_donnees (nom = "donnees_enquete_2003_television.txt") :
if os.path.exists (nom) :
# si le fichier existe (il a déjà été téléchargé une fois) 5
f = open (nom, "r")
text = f.read ()
f.close ()
else :
# si le fichier n’existe pas 10
link = "http://www.xavierdupre.fr/enseignement/td_python/" + \
"python_td_minute/data/examen/" + nom
url = urllib.urlopen (link)
text = url.read ()
# on enregistre les données pour éviter de les télécharger une seconde fois 15
f = open (nom, "w")
f.write (text)
f.close ()
donnees = charge_donnees () 25
Le fichier téléchargé 11 est stocké dans la variable matrice, il contient quatre colonnes :
Le fichier contient des lignes comme celles qui suivent. Sur la première ligne contenant des chiffres, un
individu a déclaré passer deux heures par jour devant la télévision. Sur la suivante, un autre individu a
déclaré passer six heures par semaine.
Après la première exécution, le fichier est présent sur votre disque dur à côté du programme, il est alors
facile de l’ouvrir de voir ce qu’il contient.
1) Toutes les lignes ne sont pas renseignées. Modifier la matrice pour enlever les lignes pour lesquelles
l’unité de temps (cLT2FREQ) n’est pas renseignée ou égale à zéro. (1 point)
2) Remplacer les valeurs de la quatrième colonne par une durée en heures. (1 point)
3) Calculer le nombre d’heures par jour moyen passées devant la télévision et écrire la réponse en com-
mentaire de la fonction. (1 point)
4) Calculer ce même nombre mais pondéré par la colonne POIDSF et écrire la réponse en commentaire de
11. http://www.xavierdupre.fr/enseignement/td_python/python_td_minute/data/examen/donnees_enquete_2003_
television.txt
11. Exercices pratiques pour s’évaluer 284
la fonction. (2 points)
5) Combien de gens dans l’échantillon regardent-ils la télévision plus de 24h par jour ? Quelle est la raison
la plus probable ? Ecrire la réponse en commentaire de la fonction. (1 point)
6) Calculer la médiane non pondérée. Ecrire la réponse en commentaire de la fonction. (2 points)
11. Exercices pratiques pour s’évaluer 285
11.9.2
Enoncé
L’exercice précédent utilise une fonction charge_donnees qu’on utilise également pour récupérer les données
de températures 13 .
temperature = charge_donnees("cannes_charleville_2010_max_t.txt")
Après la première exécution, le fichier est présent sur votre disque dur à côté du programme, il est alors
facile de l’ouvrir de voir ce qu’il contient. L’ensemble peut être aussi copié/collé sous Excel.
1) Ecrire une fonction qui récupère le tableau matrice et convertit tous les éléments en réels (faire attention
à la première ligne). (1 point)
2) Certaines valeurs sont manquantes et sont remplacées par la valeur -1000 choisie par convention (voir la
date du 4 mai). Ecrire une fonction qui remplace ces valeurs par la moyenne des deux valeurs adjacentes.
(3 points)
On pourra s’aider de la fonction suivante pour vérifier que les manquantes ne sont plus présentes :
3) Après avoir discuté avec mes grands-parents, nous avons décidé d’utiliser une fonction de coût égale au
carré de l’écart entre deux températures : la température qu’ils souhaitent (20 degré) et la température
qu’ils ont (celle de Charleville ou Cannes). Ecrire une fonction distance qui calcule la fonction suivante :
(1 point)
4) Supposons que nous connaissons les deux dates d’aller t1 et de retour t2 , mes grands-parents seraient
donc à :
hiver t1 été t2 hiver
Cannes Charleville Cannes
Ecrire une fonction qui calcule la somme des écarts de température au carré entre la température T
souhaitée et la température où ils sont : (3 points)
t=t
X1 t=t
X2 t=365
X
E = d (T (Cannes, t), T ) + d (T (Charleville, t), T ) + d (T (Cannes, t), T ) (11.2)
t=1 t=t1 t=t2
5) Ecrire une fonction qui détermine les meilleures valeurs t1 et t2 . Ecrire en commentaire de votre pro-
gramme le raisonnement suivi et la réponse trouvée. (3 points)
6) Quel est le coût de votre algorithme en fonction du nombre de jours ? Si, on remplace la série de tem-
pératures quotidienne par une série hebdomadaire, l’algorithme est combien de fois plus rapide ? (1 point)
Correction
# coding: latin-1
# ce fichier contient le programme fournit au début de l’examen
# http://www.xavierdupre.fr/enseignement/examen_python/python_examen_2011_2012.py
from td_note_2012_enonce import *
import numpy, pylab 5
########################################################################
# exercice 1
########################################################################
10
# question 1 (+1=1p)
donnees = charge_donnees ()
colonne = donnees [0]
matrice = numpy.array (donnees [1:], dtype=float)
15
mat = matrice
mat = mat [ mat [:,3] > 0, : ]
# question 2 (+1=2p)
mat [ mat[:,3] == 1, 3 ] = 24 20
mat [ mat[:,3] == 2, 3 ] = 24*7
mat [ mat[:,3] == 3, 3 ] = 24*30
# question 3 (+1=3p)
res = mat [:,2] / mat [:,3] 25
print res.sum() / res.shape [0] # 0.111 ~ 11,1% du temps passé devant la télévision
print res.sum() / res.shape [0] * 24 # soit 2h40min
11. Exercices pratiques pour s’évaluer 287
# question 4 (+2=5p)
m = mat[:,1] * mat[:,2] / mat[:,3] 30
print m.sum() / mat[:,1].sum() # 0.108 ~ 10,8%
# question 5 (+1=6p)
m = mat[ mat[:,2] > mat[:,3], : ]
print m # il y a deux personnes et la raison la plus probable est une erreur dans l’unité de temps 35
# question 6 (+2=8p)
res = numpy.sort (res, 0)
print res[res.shape[0]/2] # 0.083 ~ 8.3% = 2h
40
# question 7 (+2=10p)
pr = numpy.zeros ((mat.shape[0],4))
pr [:,0] = mat[:,2] / mat[:,3]
pr [:,1] = mat[:,1]
pr [:,2] = pr[:,0] * pr[:,1] 45
pr = numpy.sort (pr, 0)
total = pr[:,2].sum()
pr[0,3] = pr [0,2]
for i in xrange (1, pr.shape[0]) :
pr[i,3] = pr[i-1,3] + pr[i,2] 50
if pr[i,3]/total > 0.5 :
fin = i
break
print pr[fin,3] / pr[:fin+1,1].sum() # 0.0895 ~ 8.95%
55
########################################################################
# exercice 2
########################################################################
# question 1 (+1=1p) 60
temperature = charge_donnees("cannes_charleville_2010_max_t.txt")
#question 2 (+2=3p)
def valeur_manquante (temperature, c) :
for i in xrange (1, len (temperature)-1) : 70
if temperature [i][c] == -1000 :
temperature [i][c] = (temperature [i-1][c] + temperature [i+1][c]) / 2
valeur_manquante(temperature, 3)
valeur_manquante(temperature, 4) 75
# question 3 (+1=4p)
# question 4 (+3=7p)
11. Exercices pratiques pour s’évaluer 288
# question 5 (+3=10p)
105
def minimisation (temperature, T) :
best = 1e10
t1t2 = None
for t1 in xrange (0,len(temperature)) :
for t2 in xrange (t1+1,len(temperature)) : 110
d = somme_ecart(temperature,t1,t2,T)
if best == None or d < best :
best = d
t1t2 = t1,t2
return t1t2, best 115
# question 6
# Le coût de l’algorithme est on O(n^2) car l’optimisation est une double boucle sur les températures.
# Passer des jours aux semaines, c’est utiliser des séries 7 fois plus courtes, 125
# l’optimisation sera 7^2 fois plus rapide.
Chacun des énoncés qui suit est une série d’exercices auxquels une personne expérimentée peut répondre
en une heure. C’est souvent le temps imparti à un test informatique lors d’un entretien d’embauche
pendant lequel le candidat propose des idées plus qu’il ne rédige des solutions. Chaque énoncé est un panel
représentatif des problèmes que la programmation amène inévitablement. La difficulté de chaque exercice
est notée du plus facile au plus difficile : *, **, ***. Le sixième énoncé 12 est structuré sous forme de
problème. Il cherche à reproduire la démarche suivie lors de la conception d’un algorithme. Le barème
des énoncés est laissé à titre d’indication, il ne reflète pas toujours la difficulté de l’exercice.
Que fait le programme suivant ? Que contiendra res à la fin du programme ? (1 point)
l = [ 0,1,2,3,4,6,5,8,9,10]
res = True
for i in range (1,len (l)) :
if l[i-1] > l[i] :
res = False
Correction
Le programme vérifie que la liste l est triée, res vaut True si elle est triée, False sinon. Et dans ce cas
précis, elle vaut False car la liste n’est pas triée (4,6,5). fin exo 12.1.1 ut
Ecrire une fonction qui calcule la somme des chiffres d’un entier positif. (2 points)
Correction
Tout d’abord, obtenir la liste des chiffres d’un nombre entier n n’est pas immédiat bien qu’elle puisse
s’écrire en une ligne :
def somme (n) :
return sum ( [ int (c) for c in str (n) ] )
Cette écriture résumée cache une conversion en chaîne de caractères. Une boucle est inévitable à moins
de procéder par récurrence. Commençons par une version plus étendue et qui passe par les chaînes de
caractères. Pour cette version, il ne faut pas oublier de faire la conversion inverse, c’est-à-dire celle d’un
caractère en nombre.
12. Exercices écrits 290
Une version numérique maintenant, celle qui utilise l’opération % ou modulo pour obtenir le dernier chiffre :
Enfin, une autre solution utilisant la récurrence et sans oublier la condition d’arrêt :
def somme (n) :
if n <= 0 : return 0
else : return (n % 10) + somme ( n / 10 )
Parmi les autres solutions, certaines, exotiques, ont utilisé la fonction log en base 10 ou encore la fonction
exp. En voici une :
import math
def somme (n) :
k = int (math.log (n) / math.log (10) + 1)
s = 0
for i in range (1,k+1) :
d = 10 ** i # ou encore d = int (exp ( k * log (10) ) )
c = n / d
e = n - c * d
f = e / (d / 10)
s += f
return s
L’idée principale permet de construire une fonction retournant le résultat souhaité mais il faut avouer qu’il
est plus facile de faire une erreur dans cette dernière fonction que dans les trois précédentes.
fin exo 12.1.2 u t
Que vaut n à la fin de l’exécution du programme suivant ? Expliquez en quelques mots le raisonnement
suivi. (2 points)
n = 0
for i in range (0,10) :
if (n + i) % 3 == 0 :
n += 1
12. Exercices écrits 291
Correction
L’ensemble range(0, 10) est égale à la liste [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]. Dans ce programme, n est incrémenté
lorsque i + n est un multiple de 3. La première incrémentation a lieu lors de la première itération puisque
0 est un multiple de 3. On déroule le programme dans le tableau suivant :
i 0 1 2 3 4 5 6 7 8 9
n avant le test 0 1 1 2 2 3 3 4 4 5
i+n 0 2 3 5 6 8 9 11 12 14
n après le test 1 1 2 2 3 3 4 4 5 5
range(0, 10) contient les nombres de 0 à 10 exclu. La réponse cherchée est donc 5. fin exo 12.1.3 u
t
u1 = 1
u2 = 2
∀n ⩾ 2, un = un−1 + un−2
u13 est le nombre de manières possibles pour une grenouille de monter en haut d’une échelle de 13 barreaux
si elle peut faire des bonds de un ou deux barreaux seulement. Si cette affirmation, qui n’est pas à
démontrer, vous semble douteuse, avant d’y revenir, faites d’abord le reste de l’énoncé. (2 points)
Correction
Tout d’abord, la grenouille : elle peut faire des bonds de un ou deux barreaux à chaque fois. Donc,
lorsqu’elle est sur le barreau n, elle a pu venir du barreau n − 1 et faire un saut de 1 barreau ou venir du
barreau n − 2 et faire un bond de 2 barreaux. Par conséquent, le nombre de manières qu’a la grenouille
d’atterrir sur le barreau n, c’est la somme des manières possibles de venir jusqu’au barreau n − 1 et du
barreau n − 2.
Maintenant, comment le programmer ? Tout d’abord les solutions récursives, les plus simples à program-
mer :
def grenouille (n) :
if n == 2 : return 2
elif n == 1 : return 1
else : return grenouille (n-1) + grenouille (n-2)
print grenouille (13)
Cette solution n’est pas très rapide car si grenouille(13) va être appelée une fois, grenouille(12) une fois
aussi, mais grenouille(11) va être appelée deux fois... Cette solution implique un grand nombre de calculs
inutiles.
Après la solution récursive descendante (n décroît), la solution récursive montante (n croît) :
La méthode la plus simple est non récursive mais il ne faut pas se tromper dans les indices :
def grenouille (n) :
if n == 1 : return 1
u1 = 1
u2 = 2
for i in range (3,n+1) :
u = u1 + u2 # il est impossible de
u1 = u2 # résumer ces trois lignes
u2 = u # en deux
return u2
print grenouille (13)
Ou encore :
def grenouille (n) :
if n == 1 : return 1
u = range (0, n) # il ne faut pas oublier de créer le tableau
# avec autant de cases que nécessaire
# ici 13
u [0] = 1
u [1] = 2
for i in range (2,n) :
u [i] = u [i-1] + u [i-2]
return u [n-1]
print grenouille (12)
print d ["M"]
Que faudrait-il écrire pour que le programme marche ? Qu’affichera-t-il alors ? (1 point)
12. Exercices écrits 293
Correction
L’erreur signifie que la clé ”M” n’est pas présente dans le dictionnaire d. Elle n’est présente qu’en minuscule.
La modification proposée est la suivante :
a = "abcdefghijklmnopqrstuvwxyz"
print len (a)
d = {}
for i in range (0,len (a)) :
d [ a [ i ] ] = i
print d ["M"]
Dans les deux cas, le programme affiche la position de M dans l’alphabet qui est 12 car les indices com-
mencent à 0.
fin exo 12.1.5 u t
La fonction somme est censée faire la concaténation de toutes les listes contenues dans ens. Le résultat
retourné est effectivement celui désiré mais la fonction modifie également la liste ens, pourquoi ? (2
points)
Correction
Le problème vient du fait qu’une affectation en Python (seconde ligne de la fonction somme) ne fait pas
une copie mais crée un second identificateur pour désigner la même chose. Ici, l et tab[0] désignent la
même liste, modifier l’une modifie l’autre. Ceci explique le résultat. Pour corriger, il fallait faire une copie
explicite de tab[0] :
import copy ###### ligne ajoutée
def somme (tab) :
l = copy.copy (tab[0]) ###### ligne modifiée
for i in range (1, len (tab)) :
l += tab [i]
return l
ens = [[0,1],[2,3]]
12. Exercices écrits 294
Que se passe-t-il ? Comment corriger le programme pour qu’il marche correctement ? Qu’affichera-t-il
alors ? (2 points)
Correction
L’erreur signifie qu’on cherche à accéder à un élément de la liste mais l’indice est soit négatif, soit trop
grand. Pour comprendre pourquoi cette erreur se produit, il faut suivre les modifications de la liste li
dans la boucle for. Au départ, li vaut range(0, 10), soit :
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Lors du premier passage de la boucle, on supprime l’élément d’indice 0, la liste li est donc égale à :
[ 1, 2, 3, 4, 5, 6, 7, 8, 9]
Et elle ne contient plus que 9 éléments au lieu de 10, donc le dernier élément est celui d’indice 8. Or lors
du second passage dans la boucle for, on cherche à supprimer l’élément d’indice 9, ce qui est impossible.
L’idée la plus simple pour corriger l’erreur est de supprimer les éléments en commençant par ordre d’indices
décroissant :
li = range (0,10)
sup = [9,0] ####### ligne modifiée
for i in sup :
del li [i]
print li
Mais on pouvait tout à fait écrire aussi sup = [0, 8] même si ce n’est la réponse que je conseillerais. L’objectif
du programme n’était pas de supprimer toute la liste li.
Une dernière précision, pour supprimer l’élément d’indice i de la liste li, on peut écrire soit del li[i] soit
del li[i :i+1].
fin exo 12.1.7 u t
12. Exercices écrits 295
Ce programme est censé effectuer un tri par ordre alphabétique décroissant. Le problème intervient lors
de la permutation de l’élément l[i] avec l’élément l[mi]. Il faut donc écrire :
Le coût d’un algorithme ou d’un programme est le nombre d’opérations (additions, multiplications,
tests, ...) qu’il effectue. Il s’exprime comme un multiple d’une fonction de la dimension des données que le
programme manipule. Par exemple : O(n), O(n2 ), O(n ln n), ...
Quel est le coût de la fonction variance en fonction de la longueur de la liste tab ? N’y a-t-il pas moyen
de faire plus rapide ? (2 points)
s += t * t
return s / len (tab)
l = [ 0,1,2, 2,3,1,3,0]
print moyenne (l)
print variance (l)
Correction
Tout d’abord, le coût d’un algorithme est très souvent exprimé comme un multiple de la dimension des
données qu’il traite. Ici, la dimension est la taille du tableau tab. Par exemple, si on note n = len(tab),
alors le coût de la fonction moyenne s’écrit O(n) car cette fonction fait la somme des n éléments du tableau.
La fonction variance contient quant à elle un petit piège. Si elle contient elle aussi une boucle, chacun
des n passages dans cette boucle fait appel à la fonction moyenne. Le coût de la fonction variance est
donc O(n2 ).
Il est possible d’accélérer le programme car la fonction moyenne retourne le même résultat à chaque passage
dans la boucle. Il suffit de mémoriser son résultat dans une variable avant d’entrer dans la boucle comme
suit :
def variance (tab) :
s = 0.0
m = moyenne (tab)
for x in tab :
t = x - m
s += t * t
return s / len (tab)
12.1.10 Héritage **
Enoncé
On a besoin dans un programme de créer une classe carre et une classe rectangle. Mais on ne sait pas
quelle classe doit hériter de l’autre. Dans le premier programme, rectangle hérite de carre.
class carre :
def __init__ (self, a) :
self.a = a
def surface (self) :
12. Exercices écrits 297
return self.a ** 2
Dans le second programme, c’est la classe carre qui hérite de la classe rectangle.
class rectangle :
def __init__ (self, a,b) :
self.a = a
self.b = b
def surface (self) :
return self.a * self.b
1) Dans le second programme, est-il nécessaire de redéfinir la méthode surface dans la classe carre ?
Justifiez. (1 point)
2) Quel est le sens d’héritage qui vous paraît le plus censé, class rectangle(carre) ou class carre(rectangle) ?
Justifiez. (1 point)
3) On désire ajouter la classe losange. Est-il plus simple que rectangle hérite de la classe carre ou l’inverse
pour introduire la classe losange ? Quel ou quels attributs supplémentaires faut-il introduire dans la classe
losange ? (1 point)
Correction
1) Le principe de l’héritage est qu’une classe carre héritant de la classe rectangle hérite de ses attributs
et méthodes. L’aire d’un carré est égale à celle d’un rectangle dont les côtés sont égaux, par conséquent,
la méthode surface de la classe retourne la même valeur que celle de la classe rectangle. Il n’est donc pas
nécessaire de la redéfinir.
2) D’après la réponse de la première question, il paraît plus logique de considérer que carre hérite de
rectangle.
3) Un losange est défini par un côté et un angle ou un côté et la longueur d’une de ses diagonales, soit
dans les deux cas, deux paramètres. Dans la première question, il paraissait plus logique que la classe la
plus spécifique hérite de la classe la plus générale afin de bénéficier de ses méthodes. Pour introduire le
losange, il paraît plus logique de partir du plus spécifique pour aller au plus général afin que chaque classe
ne contienne que les informations qui lui sont nécessaires.
class carre :
def __init__ (self, a) :
self.a = a
def surface (self) :
return self.a ** 2
Le sens de l’héritage dépend de vos besoins. Si l’héritage porte principalement sur les méthodes, il est
préférable de partir du plus général pour aller au plus spécifique. La première classe sert d’interface pour
toutes ses filles. Si l’héritage porte principalement sur les attributs, il est préférable de partir du plus
spécifique au plus général. Dans le cas général, il n’y a pas d’héritage plus sensé qu’un autre mais pour un
problème donné, il y a souvent un héritage plus sensé qu’un autre.
fin exo 12.1.10 u t
x = 1.0
for i in range (0,15) :
x = x / 10
print i, "\t", 1.0 - x, "\t", x, "\t", x **(0.5)
m1 = matrice_carree_2 (1.0,1e-6,1e-6,1.0)
12. Exercices écrits 299
m2 = matrice_carree_2 (1.0,1e-9,1e-9,1.0)
print m1.determinant ()
print m2.determinant ()
D’après la précédente question, que retourne cette méthode pour la matrice M ? (à justifier) (1 point)
10−9
1 0 0
4) On décompose la matrice M = + = I + M 0.
0 1 10−9 0
On peut démontrer que si λ est une valeur propre de M 0 , alors 1+λ est une valeur propre de M . Que donne
le calcul des valeurs propres de M 0 si on utilise la méthode valeurs_propres pour ces deux matrices ? (1
point)
10−9
00 1
5) On considère maintenant la matrice M = . En décomposant la matrice M 00 de la
−10−9 1
même manière qu’à la question 4, quelles sont les valeurs propres retournées par le programme pour la
matrice M 00 ? Quelles sont ses vraies valeurs propres ? (1 point)
Correction
L’exercice a pour but de montrer que l’ordinateur ne fait que des calculs approchés et que la précision du
résultat dépend de la méthode numérique employée.
1) Le programme montre que l’ordinateur affiche 1 lorsqu’il calcule 1 − 10−17 . Cela signifie que la précision
des calculs en Python est au mieux de 10−16 .
2) Il s’agit ici de donner le résultat que calcule l’ordinateur et non le résultat théorique qui sera toujours
exact. On cherche à calculer ici le déterminant des matrices M1 et M2 définies par :
10−6 10−9
1 1
M1 = et M2 =
10−6 1 10−9 1
Pour la matrice M2 , le déterminant vaut 1. En remplaçant trace par 2 et det par 1, on obtient :
l1 = 1
l2 = 1
10−9
0
4) On change la méthode de calcul pour la matrice M2 , on écrit que M2 = I + = I + M20 .
10−9 0
Cette fois-ci le déterminant calculé par Python est bien 1e − 18. La trace de la matrice M20 est nulle, on
applique les formules suivantes à la matrice M20 pour trouver :
D’après l’énoncé, les valeurs propres de la matrice M2 sont les sommes de celles de la matrice I et de la
matrice M20 . Par conséquent, ce second calcul mène au résultat suivant :
l1 = 1-1e-9 = 0.99999999900000002828
l2 = 1+ 1e-9 = 1.000000001
5) La matrice M 00 n’est en fait pas diagonalisable, c’est-à-dire que tr (M 00 )2 −4∗det M 00 = 4−4(1+10−18 ) <
0. Or le calcul proposé par la question 3 aboutit au même résultat faux que pour la matrice M2 , les deux
valeurs propres trouvées seront égales à 1. Si on applique la décomposition de la question 4 :
−10−9
00 0
M =I+ = I + N 00
10−9 0
Le programme calcule sans erreur le discriminant négatif de la matrice N 00 qui n’est pas diagonalisable. Il
est donc impossible d’obtenir des valeurs propres réelles pour la matrice M 00 avec cette seconde méthode.
Cette question montre qu’une erreur d’approximation peut rendre une matrice diagonalisable alors qu’elle
ne l’est pas. Il est possible d’accroître la précision des calculs mais il faut faire appel à des modules
externes 1 .
fin exo 12.1.11 u t
On veut écrire une fonction qui retourne n tel que n soit le premier entier qui vérifie 2n ⩾ k. Cette fonction
prend comme paramètre d’entrée k et retourne également un entier. Elle ne devra utiliser ni logarithme ni
exponentielle. On précise qu’en langage Python, 2**n signifie 2 à la puissance n. Une boucle for ne semble
pas indiquée dans ce cas. (3 points)
Correction
L’indication qui préconisait d’utiliser autre chose qu’une boucle for ne voulait pas dire ne pas utiliser de
boucle du tout mais une boucle while. La majorité des élèves ont réussi à trouver la fonction suivante :
def fonction_log2 (k) :
n = 0
while 2**n < k :
n += 1
return n
Même s’il est possible d’utiliser malgré tout une boucle for :
def fonction_log2 (k) :
for i in range (0,1000) :
if 2**i >= k :
return i
Correction
La solution est : Et si on continue :
(1,1) (1,4)
(1,2) (2,3)
(2,1) (3,2)
(1,3) (4,1)
(2,2) (1,5)
(3,1) (2,4)
(3,3)
...
Il fallait voir deux choses importantes dans l’énoncé de l’exercice, tout d’abord, les deux lignes suivantes :
i += 1
j -= 1
Elles signifient que lorsque i augmente de 1, j diminue de 1. L’autre information est que j ne devient
jamais inférieur à 1 :
if j < 1 :
j = i+j
i = 1
Et lorsque ce cas arrive, i devient égal à 1, j devient égale à i + j, soit i + 1 puisque la condition est vérifiée
lorsque j == 1. Ce programme propose une façon de parcourir l’ensemble des nombres rationnels de la
forme ji .
fin exo 12.2.2 u t
Que vaut n à la fin du programme suivant ? Il n’est en principe pas nécessaire d’aller jusqu’à i = 10.
(2 points)
n = 3
for i in range (0,10) :
n = suite (n) % 17
print n
Correction
Il est difficile d’arriver au bout des calculs correctement lors de cet exercice. La première chose à faire est
d’identifier la relation entre n et n + 1. On rappelle que le symbole % désigne le reste d’une division entière
et que / désigne une division entière car h 2 itous les nombres manipulés dans ce programme sont entiers.
La fonction suite aboutit à f (n) = E n2 + 1 où E [x] désigne la partie entière de x. Ajouté aux trois
dernières lignes, on obtient :
2
n
un+1 = f (un )%17 = E + 1 %17
2
12. Exercices écrits 303
Même si i n’est pas utilisé dans la boucle for, celle-ci s’exécute quand même 10 fois. Pour trouver la valeur
finale de n, on calcule donc les premiers termes comme suit :
i 0 1 2 3 4
ui = n 3 5 13 0 1
n2 h i 9 25 169 0 1
n2
y=E 2 + 1 4 12 85 1 1
ui+1 = y%17 5 13 0 1 1
A partir de i = 3, la suite reste constante et égale à 1. Il n’est pas nécessaire d’aller plus loin.
fin exo 12.2.3 u
t
1) L’erreur provient de la ligne nombre [c] += 1 pour c =0 m0 , cette ligne équivaut à nombre[c] = nombre[c] + 1.
Cela signifie que nombre[c] doit exister avant l’exécution de la ligne, c’est-à-dire que le dictionnaire nombre
doit avoir une valeur associée à la clé c =0 m0 , ce qui n’est pas le cas ici.
Dans ce programme, nombre est un dictionnaire et non une liste (ou un tableau) et les dictionnaires
acceptent des indices autres que des entiers, ils acceptent comme indice ou clé toute variable de type
immuable : les nombres (entiers, réels), les caractères, les t-uples.
2) 3) La solution compte le nombre d’occurrences de chaque lettre dans le mot s.
Ou encore :
12. Exercices écrits 304
Le programme retourne :
[0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 1, 0]
4) La fonction compteur accepte d’autres types de données à condition que les deux lignes for c in s : et
nombre[c]+=1 aient un sens et elles ne sont valables que si le paramètre s contient des éléments susceptibles
de servir de clé pour le dictionnaire s, c’est-à-dire un autre dictionnaire, un tuple, une liste d’éléments de
type immuable. Autrement dit, les lignes suivantes sont correctes :
print compteur ( "mysteres" )
print compteur ( compteur ("mysteres") )
print compteur ( [0,1,1,4,-1, (6,0), 5.5, "ch"] )
print compteur ( { 1:1, 2:2, 1:[] } )
Toutefois, cette dernière ligne est valide si la fonction compteur se contente seulement de compter le nombre
d’éléments, c’est-à-dire la première solution citée aux questions 2) et 3).
fin exo 12.2.4 ut
On précise que l’instruction random.randint(0, 1) retourne un nombre aléatoire choisi dans l’ensemble {0, 1}
avec des probabilités équivalentes (ℙ (X = 0) = 12 et ℙ (X = 1) = 12 ). La fonction ligne_nulle doit
compter le nombre de lignes nulles de la matrice mat donnée comme paramètre.
def ligne_nulle (mat) :
nb = 0
for i in range (0, len (mat)) :
lig = 0
for j in range (0, len (mat [i])) :
12. Exercices écrits 305
Après avoir exécuté le programme trois fois de suite, les résultats affichés sont successivement 15, 19, 17.
Bien que l’exécution du programme ne provoque aucune erreur, le concepteur de la fonction s’interroge
quand même sur ces résultats.
1) Sachant que chaque case de la matrice reçoit aléatoirement 0 ou 1, quelle est la probabilité qu’une ligne
soit nulle ? Quelle est la probabilité d’avoir 15 lignes nulles dans la matrice sachant que cette matrice a 20
lignes ? Ces deux réponses peuvent être littérales. Donnez-vous raison à celui qui a écrit le programme (il
pense s’être trompé) ? (1 point)
2) Si vous lui donnez raison, ce qui est fort probable, où serait son erreur ? (1 point)
Correction
1) La probabilité d’avoir une ligne nulle est la probabilité d’avoir 4 zéros, c’est donc :
4
1 1
ℙ (ligne nulle) = =
2 16
Comment calculer la probabilité d’avoir 15 lignes nulles parmi 20 ? Cela revient à estimer la probabilité
de tirer 15 fois sur 20 une boule blanche lors d’un tirage à remise sachant que dans l’urne, il y a 1 boule
blanche et 15 boules noires. Le nombre de boules blanches tirées suit une loi binomiale de paramètre
1
p = 16 . On en déduit que :
15
1 5 220
15 1
ℙ (15 lignes nulles sur 20) = C20 1− ⩽ 15
16 16 10
Cette probabilité est très faible, il est donc presque impossible d’obtenir trois fois de suite un nombre de
lignes supérieur à 15. Le programme est sans aucun doute faux.
2) La construction de la matrice est manifestement correcte, c’est donc le comptage des lignes nulles qui est
faux. Cette erreur intervient lors de la ligne if lig == 0 : nb += 1. Celle-ci est incluse dans la seconde
boucle for ce qui a pour effet d’incrémenter lig dès qu’un premier zéro est rencontré sur une ligne. Au
final, la fonction retourne le nombre de lignes contenant au moins un zéro. Voici donc la correction à
apporter :
def ligne_nulle (mat) :
nb = 0
for i in range (0, len (mat)) :
lig = 0
for j in range (0, len (mat [i])) :
if mat [i][j] > 0 : lig += 1
if lig == 0 : nb += 1 # ligne décalée vers la gauche
return nb
12.2.6 Récursivité *
Enoncé
Le programme suivant provoque une erreur dont le message paraît sans fin.
12. Exercices écrits 306
class erreur :
def __init__ (self) :
self.e = erreur ()
e = erreur ()
Correction
L’erreur retournée est une erreur d’exécution et non une erreur de syntaxe. Cela veut dire que le programme
de l’exercice est syntaxiquement correct. Il n’est donc pas possible de dire que l’attribut e n’est pas défini
et de corriger le programme comme suit pour expliquer l’erreur :
class erreur :
def __init__ (self,e) :
self.e = e
En fait, la classe erreur définit un attribut qui est également de type erreur. Cet attribut va lui aussi
définir un attribut de type erreur. Ce schéma va se reproduire à l’infini puisqu’à aucun moment, le code
du programme ne prévoit la possibilité de s’arrêter. Le programme crée donc des instances de la classe
erreur à l’infini jusqu’à atteindre une certaine limite dépendant du langage Python. C’est à ce moment-là
que se produit l’erreur citée dans l’énoncé.
fin exo 12.2.6 ut
11 (base décimale) s’écrit 102 en base 3 puisque 11 = 1 ∗ 32 + 0 ∗ 31 + 2 ∗ 30 . L’objectif de cet exercice est
d’écrire une fonction qui écrit un nombre entier en base 3. Cette fonction prend comme paramètre d’entrée
un entier et retourne une chaîne de caractères. On précise que l’opérateur % calcule le reste d’une division
entière et que 11/3 → 3 car c’est une division entière dont le résultat est un entier égal au quotient de la
division. La fonction str permet de convertir un nombre en une chaîne de caractères. Il ne reste plus qu’à
compléter les deux lignes manquantes du programme suivant : (2 points)
Correction
Il y a effectivement deux lignes à corriger. r désigne le reste de la division de n par 3. Il faut le convertir
en chaîne de caractères et l’ajouter à gauche à la chaîne s. On passe au chiffre suivant en division n par 3
ce qui est rassurant puisque n va tendre vers zéro et le programme s’arrêter nécessairement au bout d’un
moment. Pour vérifier que ce programme est correct, il suffit de l’appliquer à un nombre, voire appliquer
le même algorithme mais en base 10 pour être vraiment sûr.
def base3 (n) :
s = ""
while n > 0 :
r = n % 3
s = str (r) + s
n = n / 3 # équivalent à n = (n-r) / 3
# puisque / est une division entière
return s
Un professeur désireux de tester une répartition aléatoire des notes à un examen décide de tirer au hasard
les notes de ses élèves selon une loi normale de moyenne 15 et d’écart-type 3. Il arrondit ses notes à l’entier
le plus proche en n’omettant pas de vérifier que ses notes sont bien dans l’intervalle [0, 20]. On précise que
l’instruction float(i) convertit un nombre i en nombre réel, cette conversion est utilisée pour être sûr que
le résultat final sera bien réel et non le résultat d’opérations sur des entiers. Cette conversion intervient le
plus souvent lors de divisions.
import copy
import math
import random
class Eleve :
def __init__ (self, note) :
self.note = note
e = Eleve (0)
l = []
for i in range (0,81) :
e.note = int (random.gauss (15, 3) + 0.5) # tirage aélatoire et arrondi
if e.note >= 20 : e.note = 20 # pas de note au-dessus de 20
if e.note < 0 : e.note = 0 # pas de note négative
l.append (e)
moy = 0
var = 0
for e in l :
moy += e.note
moy = float (moy) / len (l) # les notes sont entières,
# il faut convertir avant de diviser
# pour obtenir la moyenne
for e in l :
var += (e.note - moy) ** 2
var = math.sqrt ( float (var) ) / len (l)
Il songe à vérifier néanmoins que la moyenne de ses notes arrondies est bien conforme à ce qu’il a échafaudé.
moyenne 16.0
écart-type 0.0
La moyenne égale à 16 ne le perturbe guère, il se dit que l’arrondi a été plutôt généreux. Toutefois l’écart-
type nul le laisse perplexe.
1) Que signifie un écart-type nul ? Quelle est l’erreur du professeur ? (Elle est située à l’intérieur de la boucle
for i in range(0, 81) :, ce n’est pas une erreur lors du calcul de l’écart-type ni une erreur de définition de
la classe Eleve.) (1 point)
2) Proposer deux solutions pour corriger ce problème, chacune d’elles revient à remplacer la ligne l.append(e).
(2 points)
3) On sait que la variance d’une variable aléatoire X vérifie : 𝕍 (X) = 𝔼 X 2 − [𝔼 (X)]2 . Cette astuce
mathématique permet-elle de réduire le nombre de boucles du programme, si oui, comment ? (1 point)
Correction
1) Un écart-type nul signifie que toutes les notes sont identiques et égales à la moyenne. Selon le programme,
tous les élèves ont donc 16. L’erreur provient du fait que l’instruction l.append(e) ajoute à chaque fois la
même variable de type Eleve. A la fin de la boucle, la liste l contient 81 fois le même objet Eleve ou
plus exactement 81 fois la même instance de la classe Eleve. On modifie la note de cet unique objet en
écrivant : e.note = int(random.gauss(15, 3) + 0.5). 16 est donc la note attribuée au dernier élève, la dernière
note tirée aléatoirement.
2) Les deux corrections possibles consistent à créer à chaque itération une nouvelle instance de la classe
Eleve.
1. l.append(e) devient l.append(Eleve(e.note))
2. l.append(e) devient l.append(copy.copy(e))
La variable e dans la boucle est un Eleve temporaire, il est ensuite recréé ou recopié avant l’ajout dans la
liste. Ceci veut dire que l’instance ajoutée dans la liste n’est pas la même que celle utilisée dans la boucle.
Une troisième solution est envisageable même si elle introduit des modifications dans la suite du pro-
gramme, elle est logiquement correcte : l.append(e) devient l.append(e.note). La liste l n’est plus une liste
de Eleve mais une liste d’entiers. Le programme devient :
#...
e = Eleve (0)
l = []
for i in range (0,81) :
e.note = int (random.gauss (15, 3) + 0.5)
if e.note >= 20 : e.note = 20
if e.note < 0 : e.note = 0
l.append (e.note) # ligne modifiée
moy = 0
var = 0
3) La formule mathématique permet de réduire le nombre de boucles à deux. Lors de la version initiale du
programme, la première sert à créer la liste l, la seconde à calculer la moyenne, la troisième à calculer la
variance. On regroupe les deux dernières boucles en une seule.
moy = 0
var = 0
for note in l :
moy += note
var += note * note
moy = float (moy) / len (l)
var = float (var) / len (l)
var = var - moy * moy
var = math.sqrt ( float (var) )
Ecrire une fonction qui arrondit un nombre réel à 0.5 près ? Ecrire une autre fonction qui arrondit à
0.125 près ? Ecrire une fonction qui arrondit à r près où r est un réel ? Répondre uniquement à cette
dernière question suffit pour répondre aux trois questions. (1 point)
Correction
Pour arrondi à 1 près, il suffit de prendre la partie entière de x + 0.5, arrondir x à 0.5 près revient à arrondi
2x à 1 près.
def arrondi_05 (x) :
return float (int (x * 2 + 0.5)) / 2
Le programme suivant affiche toutes les listes de trois entiers, chaque entier étant compris entre 0 et 9.
for a in range (0, 10) :
for b in range (0, 10) :
for c in range (0, 10) :
print [a,b,c]
1) Proposez une solution avec une boucle while et deux tests if. (1 point)
2) Proposez une solution avec deux boucles while. (1 point)
12. Exercices écrits 310
Correction
1) Une seule boucle contrôle les indices a, b, c. Quand un indice atteint sa limite, on incrémente le suivant
et on remet l’indice à 0.
a,b,c = 0,0,0
while c < 10 :
print [a,b,c]
a += 1
if a == 10 :
b += 1
a = 0
if b == 10 :
c += 1
b = 1
2) L’avantage de cette dernière solution est qu’elle ne dépend pas du nombre d’indices. C’est cette solution
qu’il faut préconiser pour écrire une fonction dont le code est adapté quelque soit la valeur de n.
l = [0,0,0]
while l [-1] < 10 :
print l
l [0] += 1
i = 0
while i < len (l)-1 and l [i] == 10 :
l [i] = 0
l [i+1] += 1
i += 1
Ce problème était mal posé : il n’est pas difficile d’introduire des tests ou des boucles redondantes ou
d’enlever une des conditions d’une boucle while pour la remplacer un test relié à une sortie de la boucle.
fin exo 12.3.2 u
t
La fonction fibo retourne la valeur de la suite de Fibonacci pour tout entier n. Quel est son coût en
fonction de O(n) ? (1 point)
def fibo (n) :
if n <= 2 : return 2
else : return fibo (n-1) + fibo (n-2)
Correction
Lorsqu’on cherche à calculer fibo(n), on calcule fibo(n − 1) et fibo(n − 2) : le coût du calcul fibo(n) est
égal à la somme des coûts des calculs de fibo(n − 1) et fibo(n − 2) plus une addition et un test. Le coût de
la fonction fibo(n) est plus facile à définir par récurrence. Le coût cn du calcul de fibo(n) vérifie donc :
c0 = c1 = c2 = 1 (12.1)
cn = cn−1 + cn−2 + 2 (12.2)
Le terme 1 dans (12.1) correspond au premier test. Le terme 2 dans (12.2) correspond au test et à l’addition.
Le coût du calcul de fibo(n) est égal à une constante près à une suite de Fibonacci. Le code suivant permet
de le vérifier en introduisant une variable globale. On modifie également la fonction fibo de façon à pouvoir
changer u0 et u1 .
12. Exercices écrits 311
Dans cet exemple, on s’aperçoit que la suite est égale à son coût. Pour le démontrer d’une façon plus
théorique, on s’intéresse aux suites de la forme suivante dont la récurrence est développée ci-dessus à
droite :
u0 = u1 = u2 = p
un = un−1 + un−2 + d (12.3)
Par une astuce de calcul, on peut réduire l’écriture de la suite (un ) à une somme linéaire de deux suites
(Un ) et (Vn ). La suite (Un ) est définie par U0 = 1, U1 = 1, Un = Un−1 + Un−2 et la suite (Vn ) définie par
V0 = 0, V1 = 0, Vn = Vn−1 + Vn−2 + 1. On en déduit que :
un = Un p + Vn d
Si on arrive à montrer que Un = Vn + 1, cela montrera que la fonction fibo citée dans la correction est
bien égale à son coût. Or :
La suite Vn∗ = Vn + 1 suit bien la récurrence de la suite Un . On vérifie que les premières valeurs sont
identiques et cela montre que Un = Vn + 1. On en déduit que :
un = Un p + (Un − 1)d = Un (p + d) − d
√
1+ 5
Pour conclure, une suite de Fibonacci vérifie la récurrence un = un−1 + un−2 . Si on pose λ1 = 2 et
√
1− 5
λ2 = 2 . Cette suite peut s’écrire sous la forme un = Aλn1 + Bλn2 . Le second terme étant négligeable
par rapport au premier, le coût de la fonction fibo est donc en O (λn1 ). En d’autres termes, si on calcule
le coût du calcul récursif d’une suite de Fibonacci, ce dernier est équivalent à la suite elle-même.
fin exo 12.3.3 u
t
l = [0,1,2,3,4,5]
g = l
12. Exercices écrits 312
2) Et celui-ci : (1 point)
l = [0,1,2,3,4,5]
g = [0,1,2,3,4,5]
for i in range (0, len (l)-1) :
g [i] = g [i+1]
print l
print g
4) A votre avis, quel est l’objectif du programme et que suggérez-vous d’écrire ? (1 point)
5) Voyez-vous un moyen d’écrire plus simplement la seconde ligne g = [0, 1, 2, 3, 4, 5] tout en laissant in-
changé le résultat ? (1 point)
Correction
1) L’instruction g = l implique que ces deux variables désignent la même liste. La boucle décale les nombres
vers la gauche.
[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5]
2) L’instruction g = [0, 1, 2, 3, 4, 5] implique que ces deux variables ne désignent plus la même liste. L’ins-
truction print l affiche le contenu du début.
[0, 1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 5]
3) La boucle s’intéresse cette fois-ci au déplacement du premier élément en première position. Le program-
meur a pris soin d’utiliser le modulo, n%n = 0 mais le premier élément de la liste g est devenu le second.
Le résultat est donc :
[0, 1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 1]
4) Le programme souhaite décaler les éléments d’une liste vers la gauche, le premier élément devenant le
dernier.
l = [0,1,2,3,4,5]
g = [0,1,2,3,4,5]
for i in range (0, len (l)) :
g [i] = l [(i+1)%len (l)] # ligne modifiée, g devient l
print l
print g
5) La seconde ligne impose de répéter le contenu de la liste. Il existe une fonction qui permet de le faire :
12. Exercices écrits 313
import copy
l = [0,1,2,3,4,5]
g = copy.copy (l) # on pourrait aussi écrire g = list (l)
# ou encore g = [ i for i in l ]
for i in range (0, len (l)) :
g [i] = l [(i+1)%len (l)]
print l
print g
1) Un individu a écrit la fonction suivante, il inclut un seul commentaire et il faut deviner ce qu’elle fait.
Après exécution, le programme affiche 4. (1 point)
def mystere (l) :
"""cette fonction s’applique à des listes de nombres"""
l.sort ()
nb = 0
for i in range (1,len (l)) :
if l [i-1] != l [i] : nb += 1
return nb+1
l = [4,3,1,2,3,4]
print mystere (l) # affiche 4
2) Après avoir écrit l’instruction print l, on s’aperçoit que la liste l a été modifiée par la fonction mystere.
Qu’affiche cette instruction ? (1 point)
3) Comment corriger le programme pour que la liste ne soit pas modifiée ? (1 point)
Correction
1) La fonction travaille sur une liste de nombres triés. La fonction parcourt cette liste et compte le nombre
de fois que la liste triée contient deux éléments successifs différents. La fonction retourne donc le nombre
d’éléments distincts d’une liste.
2) La fonction trie la liste, elle ressort donc triée de la fonction car le passage des listes à une fonction
s’effectue par adresse. Le résultat de l’instruction print l est donc :
[1, 2, 3, 3, 4, 4]
12.3.6 Héritage **
Enoncé
h = Homme ("Hector")
f = Femme ("Gertrude")
print h
print f
Programme A Programme B
1) Les deux programmes précédents affichent-ils les mêmes choses ? Qu’affichent-ils ? (1 point)
2) On souhaite ajouter une personne hermaphrodite, comment modifier chacun des deux programmes pour
prendre en compte ce cas ? (1 point)
3) Quel programme conseilleriez-vous à quelqu’un qui doit manipuler cent millions de personnes et qui a
peur de manquer de mémoire ? Justifiez. (1 point)
Correction
M. Hector
Melle Gertrude
2)
12. Exercices écrits 315
h = Homme ("Hector")
f = Femme ("Gertrude")
g = Hermaphrodite ("Marie-Jean")
print h
print f
print g
Programme A Programme B
3) Les deux programmes définissent une personne avec un prénom et un en-tête. Dans le second programme,
l’en-tête est un attribut de la classe et cette classe permet de modéliser les hommes et les femmes. Dans
le premier programme, l’en-tête est défini par le type de la classe utilisée. Il y a moins d’information
mémorisée, en contre partie, il n’est pas possible d’avoir d’autres en-têtes que M. et Melle. Pour manipuler
100 millions de personnes, il vaut mieux utiliser le premier programme avec deux classes, il sera moins
gourmant en mémoire.
fin exo 12.3.6 ut
On considère la fonction suivante qui prend comme entrée une liste d’entiers. Elle ne retourne pas de
résultat car elle modifie la liste. Appeler cette fonction modifie la liste donnée comme paramètre. Elle est
composée de quatre groupes d’instructions.
def tri_entiers(l):
"""cette fonction s’applique à une liste d’entiers"""
# groupe 1
m = l [0]
M = l [0]
for k in range(1,len(l)):
if l [k] < m : m = l [k]
if l [k] > M : M = l [k]
12. Exercices écrits 316
# groupe 2
p = [0 for i in range (m,M+1) ]
for i in range (0, len (l)) :
p [ l [i] - m ] += 1
# groupe 3
R = [0 for i in range (m,M+1) ]
R [0] = p [0]
for k in range (1, len (p)) :
R [k] = R [k-1] + p [k]
# groupe 4
pos = 0
for i in range (1, len (l)) :
while R [pos] < i : pos += 1
l [i-1] = pos + m
l [len (l)-1] = M
5) Le groupe 1 inclut une boucle de n itérations où n est le nombre d’éléments de la liste l. Les groupes 2 et 3
incluent une boucle de M − n itérations. Le groupe 4 inclut deux boucles, la première inclut n itérations,
la seconde implique une variable pos qui passe de m à M . Le coût de ce bloc est en O(n + M − n). C’est
ce dernier bloc le plus long.
fin exo 12.3.7 u t
12. Exercices écrits 317
Chaque année, une société reçoit des cadeaux de ses clients et son patron souhaite organiser une tombola
pour attribuer ces cadeaux de sorte que les plus bas salaires aient plus de chance de recevoir un cadeau.
Il a donc choisi la méthode suivante :
– Chaque employé considère son salaire net par mois et le divise par 1000, il obtient un nombre entier ni .
– Dans une urne, on place des boules numérotées que l’on tire avec remise.
– Le premier employé dont la boule est sortie autant de fois que ni gagne le lot.
La société a distribué plus de 100 cadeaux en quelques années mais jamais le patron n’en a gagné un seul,
son salaire n’est pourtant que cinq fois celui de sa sécrétaire. Son système est-il équitable ? Il fait donc une
simulation sur 1000 cadeaux en Python que voici. Sa société emploie quatre personnes dont les salaires
sont [2000, 3000, 5000, 10000].
import random
Il n’a aucune chance de gagner à moins que son programme soit faux. Ne s’en sortant plus, il décide
d’engager un expert car il trouve particulièrement injuste que sa secrétaire ait encore gagné cette dernière
bouteille de vin.
1) Avez-vous suffisamment confiance en vos connaissances en Python et en probabilités pour déclarer le
programme correct ou incorrect ? Deux ou trois mots d’explications seraient les bienvenus. (1 point)
2) Le patron, après une folle nuit de réflexion, décida de se débarrasser de son expert perdu dans des
probabilités compliquées et modifia son programme pour obtenir le suivant :
import random
Quelle est la modification dans les règles qu’il a apportée ? Vous paraissent-elles justes ? (1 point)
Correction
1) Le programme est correct, ses résultats aussi. Prenons le cas simple où il n’y a que le patron et sa
sécrétaire. Le patron gagne 10000 euros et sa secrétaire seulement 1000 euros. On reformule le problème.
On construit ensuite une suite de 10 nombres choisis uniformément au hasard dans l’ensemble {0, 1}. Le
patron gagne si les 10 nombres sont les siens. Ce jeu est équivalent à celui décrit dans l’énoncé à ceci près
qu’on continue le tirage même si quelqu’un gagne. Et avec cette nouvelle présentation, on peut conclure
qu’il y a exactement 210 tirages possibles et qu’il n’y a qu’un seul tirage gagnant pour le patron. La
probabilité de gagner est seulement de 2−10 et non 10 1
comme le patron le souhaitait.
Si on revient au jeu à quatre personnes et les salaires de l’énoncé, la probabilité de gagner du patron est
cette fois de 4−10 , celle de sa sécrétaire est au moins supérieure à 16 1
qui correspond à sa probabilité de
gagner en deux tirages. Si le patron ne gagne pas, c’est que sa probabilité de gagner est beaucoup trop
petite par rapport au nombre de tirages. Il en faudrait au moins 4−10 pour avoir une probabilité non
négligeable que le patron gagne au moins une fois.
2) 90 ∗ 5 = 450 qui n’est pas très loin de 429 ou encore 178/2 ∗ 5 = 445. La secrétaire paraît avoir 5 fois
plus de chances de gagner que son patron et 5/2 fois plus que la personne payée 5000 euros.
La fonction tirage reçoit un nouvel élément nb qui est un compteur. Le programme ne remet plus à zéro
ce compteur entre deux tirages. Cela signifie que si la boule du patron est sortie trois fois alors que la
secrétaire a gagné. Lors du prochain tirage, le compteur du patron partira de 3 et celui de sa secrétaire
de 0.
Si on ne remet jamais les compteurs à zéro, au bout de 1000 tirages (et non 1000 cadeaux), la boule du
patron est sortie environ autant de fois que celle de la secrétaire, soit 250 fois puisqu’il y a quatre employés.
La secrétaire aura gagné 250 250
2 = 125 fois, le patron aura gagné 10 = 25 soit 5 fois moins .
2
2. Je cite ici la plus belle des réponses retournée par un élève : Les patrons qui organisent des tombolas sont toujours
mariés à leur secrétaire donc la question ne se pose pas.
12. Exercices écrits 319
Correction
10
12
Ou encore :
Ou encore :
2) Proposez une solution pour que le résultat de la fonction soit 0.5 lors d’une instruction print division... ?
(1 point)
Correction
1) 1/2 est égal à zéro en Python car c’est une division de deux entiers et le résultat est égal à la partie
entière. La seconde division est une division entre un réel et un entier, le résultat est réel. Le programme
affiche :
0
0.45
2) Voici deux solutions print division(1.0) ou print division(float(1)). Il est également possible de
convertir n à l’intérieur de la fonction division. fin exo 12.4.3 u t
On considère deux listes d’entiers. La première est inférieure à la seconde si l’une des deux conditions
suivantes est vérifiée :
12. Exercices écrits 321
– les n premiers nombres sont égaux mais la première liste ne contient que n éléments tandis que la seconde est
plus longue
– les n premiers nombres sont égaux mais que le n + 1ème de la première liste est inférieur au n + 1ème de la seconde
liste
Par conséquent, si l est la longueur de la liste la plus courte, comparer ces deux listes d’entiers revient à
parcourir tous les indices depuis 0 jusqu’à l exclu et à s’arrêter sur la première différence qui détermine le
résultat. S’il n’y pas de différence, alors la liste la plus courte est la première. Il faut écrire une fonction
compare_liste(p, q) qui implémente cet algorithme.
(3 points)
Correction
Cet algorithme de comparaison est en fait celui utilisé pour comparer deux chaînes de caractères.
On pourrait également écrire cette fonction avec la fonction cmp qui permet de comparer deux éléments
quels qu’ils soient.
def compare_liste (p,q) :
i = 0
while i < len (p) and i < len (q) :
c = cmp (p [i], q [i])
if c != 0 : return c # on peut décider
i += 1 # on ne peut pas décider
# fin de la boucle, il faut décider à partir des longueurs des listes
return cmp (len (p), len (q))
Pourquoi ? (1 point)
Correction
Le programme affiche les nombres par groupes de deux nombres consécutifs. Une itération de la boucle
commence si la liste contient un élément suivant et non deux. Le programme est donc contraint à l’erreur
car lors de la dernière itération, la liste contient un dixième nombre mais non un onzième. Le programme
affiche le dixième élément (9) puis provoque une erreur list index out of range.
fin exo 12.4.5 u
t
On cherche à calculer la somme des termes d’une suite géométriques de raison 12 . On définit r = 12 , on
cherche donc à calculer ∞ i
P
i=0 r qui une somme convergente mais infinie. Le programme suivant permet
d’en calculer une valeur approchée. Il retourne, outre le résultat, le nombre d’itérations qui ont permis
d’estimer le résultat.
def suite_geometrique_1 (r) :
x = 1.0
y = 0.0
n = 0
while x > 0 :
y += x
x *= r
n += 1
return y,n
Un informaticien plus expérimenté a écrit le programme suivant qui retourne le même résultat mais avec
un nombre d’itérations beaucoup plus petit.
def suite_geometrique_2 (r) :
x = 1.0
y = 0.0
n = 0
yold = y + 1
while abs (yold - y) > 0 :
yold = y
y += x
x *= r
n += 1
return y,n
Expliquez pourquoi le second programme est plus rapide tout en retournant le même résultat. (2 points)
Repère numérique : 2−55 ∼ 2, 8.10−17 .
12. Exercices écrits 323
Correction
Tout d’abord le second programme est plus rapide car il effectue moins d’itérations, 55 au lieu de 1075.
Maintenant, il s’agit de savoir pourquoi le second programme retourne le même résultat que le premier mais
plus rapidement. L’ordinateur ne peut pas calculer numériquement une somme infinie, il s’agit toujours
d’une valeur approchée. L’approximation dépend de la précision des calculs, environ 14 chiffres pour
Python. Dans le premier programme, on s’arrête lorsque rn devient nul, autrement dit, on s’arrête lorsque
x est si petit que Python ne peut plus le représenter autrement que par 0, c’est-à-dire qu’il n’est pas
possible de représenter un nombre dans l’intervalle 0, 2−1055 .
Toutefois, il n’est pas indispensable d’aller aussi loin car l’ordinateur n’est de toute façon pas capable
d’ajouter un nombre aussi petit à un nombre plus grand que 1. Par exemple, 1+1017 = 1, 000 000 000 000 000 01.
Comme la précision des calculs n’est que de 15 chiffres, pour Python, 1 + 1017 = 1. Le second programme
s’inspire de cette remarque : le calcul s’arrête lorsque le résultat de la somme n’évolue plus car il additionne
des nombres trop petits à un nombre trop grand. L’idée est donc de comparer la somme d’une itération à
l’autre et de s’arrêter lorsqu’elle n’évolue plus.
Pn i
Ce raisonnement n’est pas toujours applicable. Il est valide dans ce cas car P la série sn = i=0 r est
croissante et positive. Il est valide pour une série convergente de la forme sn = ni=0 ui et une suite un de
module décroissant.
fin exo 12.4.6 u t
Un chercheur cherche à vérifier qu’une suite de 0 et de 1 est aléatoire. Pour cela, il souhaite compter le
nombre de séquences de n nombres successifs. Par exemple, pour la suite 01100111 et n = 3, les triplets
sont 011, 110, 100, 001, 011, 111. Le triplet 011 apparaît deux fois, les autres une fois. Si la suite est
aléatoire, les occurrences de chaque triplet sont en nombres équivalents.
Le chercheur souhaite également faire varier n et calculer les fréquences des triplets, quadruplets, quin-
tuplets, ... Pour compter ses occurrences, il hésite entre deux structures, la première à base de listes (à
déconseiller) :
k -= 1
return r
h = hyper_cube_dico (3)
print h # affiche {(0, 1, 1): 0, (1, 1, 0): 0, (1, 0, 0): 0, (0, 0, 1): 0,
# (1, 0, 1): 0, (0, 0, 0): 0, (0, 1, 0): 0, (1, 1, 1): 0}
Sur quelle structure se porte votre choix (a priori celle avec dictionnaire), compléter la fonction occurrence.
(4 points)
Correction
Tout d’abord, la structure matricielle est à déconseiller fortement même si un exemple d’utilisation en sera
donné. La solution avec dictionnaire est assez simple :
def occurrence (l,n) :
d = hyper_cube_dico (n)
for i in range (0, len (l)-n) :
cle = tuple (l [i:i+n])
d [cle] += 1
return d
La seule différence apparaît lorsqu’un n-uplet n’apparaît pas dans la liste. Avec la fonction hyper_cube_dico,
ce n-uplet recevra la fréquence 0, sans cette fonction, ce n-uplet ne sera pas présent dans le dictionnaire d.
Le même programme avec la structure matricielle est plus une curiosité qu’un cas utile.
def occurrence (l,n) :
d = hyper_cube_liste (n, [0,0]) # * remarque voir plus bas
for i in range (0, len (l)-n) :
cle = l [i:i+n]
t = d #
for k in range (0,n-1) : # point clé de la fonction :
t = t [ cle [k] ] # accès à un élément
t [cle [ n-1] ] += 1
return d
Si on remplace la ligne marquée d’une étoile par d = hyper_cube_list(n), le programme retourne une
erreur :
12. Exercices écrits 325
Cette erreur est assez incompréhensible puisque la modification a consisté à appeler une fonction avec un
paramètre de moins qui était de toutes façons égal à la valeur par défaut associée au paramètre. Pour
comprendre cette erreur, il faut exécuter le programme suivant :
def fonction (l = [0,0]) :
l [0] += 1
return l
L’explication provient du fait que la valeur par défaut est une liste qui n’est pas recréée à chaque appel
mais c’est la même liste à chaque fois que la fonction est appelée sans paramètre. Pour remédier à cela, il
faudrait écrire :
import copy
def fonction (l = [0,0]) :
l = copy.copy (l)
l [0] += 1
return l
x = 1
f() f()
g() g()
Le premier programme produit l’erreur qui Qu’affiche le second pro-
suit. Pourquoi ? gramme ?
Traceback (most recent call last):
File "p1.py", line 11, in <module>
g()
File "p1.py", line 9, in g
print x
NameError: global name ’x’ is not defined
2) Le premier des deux programmes suivant s’exécute sans erreur, qu’affiche-t-il ? (1 point)
import copy
a = [ None, 0 ] a = [ None, 0 ]
b = [ None, 1 ] b = [ None, 1 ]
a [0] = b a [0] = copy.copy (b)
b [0] = a b [0] = a
print a [0][0] [0][0] [0][0] [1] print a [0][0] [0][0] [0][0] [1]
3) On ne sait rien du second programme qui utilise le module copy. S’exécute-t-il sans erreur ? Si la réponse
est positive, qu’affiche-t-il ? Si la réponse est négative, pourquoi ? (1 point)
Correction
1) L’erreur se produit dans la fonction g car il n’existe aucune variable globale ou locale portant le nom x,
l’instruction print x ne peut donc être exécutée. La variable x définie dans la fonction f est une variable
locale et son existence est limité à la fonction f.
A contrario, le second programme définit une variable globale x = 1 avant l’appel à la fonction g. Le
programme affiche donc :
0
1
2) L’instruction print a[0][0][0][0][0][0][1] peut surprendre mais elle est tout-à-fait correcte dans le premier
programme. Lorsqu’il s’agit d’une liste, une affectation ne copie pas la liste mais donne à cette liste un
second identifiant :
a = [ None, 0 ]
b = [ None, 1 ]
a [0] = b # b et a [0] désignent la même liste
b [0] = a # b [0] et a désignent la même liste
a ⇐⇒ [b, 0]
b ⇐⇒ [a, 0]
12. Exercices écrits 327
a[0][0][0][0][0][0][1]
= b[0][0][0][0][0][1]
= a[0][0][0][0][1]
= a[0][0][1]
= a[1]
= 0
3) Dans le second programme, l’instruction copy.copy(b) créée une copie c de la liste b. On peut résumer
les listes a et b par l’écriture suivante :
Par conséquent :
a[0][0][0][0][0][0][1]
= c[0][0][0][0][0][1]
= N one[0][0][0][0][1]
⇐= erreur
44
A sa première exécution, il affiche : x = None
Ce n’est pas tout-à-fait le résultat attendu, le terme None ne devrait pas apparaître, que faut-il faire pour
le corriger ? (1 point)
2) On s’aperçoit également que le programme provoque l’erreur suivante environ une fois sur dix :
Traceback (most recent call last):
File "examen2009.py", line 10, in <module>
print somme_alea ( range (0,100), 10)
File "examen2009.py", line 8, in somme_alea
s += tableau [h]
IndexError: list index out of range
12. Exercices écrits 328
Pourquoi ? Expliquer également pourquoi le programme ne provoque cette erreur qu’une fois sur dix
environ ? Une réponse statistique est attendue. (2 points)
3) Enfin, le résultat attendu est réel alors que le résultat du programme est toujours entier, pourquoi ?
(1 point)
Correction
1) Il suffit de remplacer l’instruction print s/n par rapport return s/n. Le programme affichera :
44
2) L’erreur nous apprend que l’instruction s+ = tableau[n] provoque une erreur parce que la variable h
contient un entier soit négatif, soit supérieur ou égal à len(tableau). C’est ce second cas qui se produit
parfois. La fonction random.randint retourne un entier aléatoire compris entre 0 et len(tableau) inclus. Il
faudrait changer l’instruction précédente en :
L’erreur ne se produit pas toujours car le programme tire un nombre aléatoire qui peut être égal à
len(tableau). En fait, il faut calculer la probabilité que le programme a de tirer le nombre len(tableau) que
l’on notera n dans la suite. Le programme fait la moyenne de 10 nombres aléatoires tirés au hasard mais
uniformément dans l’ensemble {0, . . . , n}. La probabilité que le programme fonctionne est la probabilité p
de ne jamais tomber sur le nombre n en 10 tirages :
10 10
n−1 1
p= = 1− (12.4)
n n
2) On peut faire plus rapide en remplaçant les lignes en pointillés dans le programme qui suit. (1 point)
3) Quel est le coût du nouveau programme toujours en fonction de la longueur n de la suite suite et de la
largeur de la moyenne mobile m ? (1 point)
Correction
1) La fonction moyenne_mobile inclut deux boucles imbriquée. La première est de longueur n − 2m, la
seconde est de longueur 2m. Le coût de la fonction est donc en O((n − 2m)2m) = O(2mn − 4m2 ). On
peut supposer que m est plus petit devant n et négliger le second terme. Le coût recherché est : O(mn).
2) Lorsqu’on calcule la valeur de la somme s pour les itérations i et i + 1, on ajoute presque les mêmes
termes (on cote s = suite pour simplifier) :
Pour passer d’une somme à la suivante, il suffit d’ajouter un terme et d’en soustraire un. Il faut également
calculer la première somme lorsque i = m. Ceci donne :
3) La fonction contient toujours deux boucles imbriquées (la fonction sum cache une boucle) mais la seconde
boucle n’est exécutée qu’une seule fois. Le coût de la fonction n’est plus que O(n+m). Comme n est souvent
beaucoup plus grand que m, on peut négliger le second terme : O(n).
fin exo 12.5.3 u t
12. Exercices écrits 330
Correction
1) Pour comprendre l’objectif de la fonction, il suffit d’enlever les deux lignes suivantes :
La fonction devient :
Cette fonction correspond à un tri par ordre décroissant. Les deux lignes supprimées ne font qu’éviter
l’exécution des itérations pour les éléments impairs. La fonction trie par ordre décroissant les éléments
pairs d’un tableau tout en laissant inchangés les éléments impairs. Par exemple :
a = [ 0, 1, 4, 3, 5, 2 ]
print a # affiche [ 0, 1, 4, 3, 5, 2 ]
b = fonction_mystere (a)
print b # affiche [ 4, 1, 2, 3, 5, 0 ]
Figure 12.2 : Figure représentant deux lignes de métro qui se croisent en C et qui se rejoignent en terminus.
Chaque station est représentée par un point, chaque arc indique que deux stations sont reliées par la même ligne de
métro. Tous les arcs sont à double sens sauf trois car dans ce cas, le métro ne suit pas le même chemin dans les
deux sens.
La figure 12.2 représente deux lignes de métro. On représente ce graphe par une matrice carrée M = (mij )
dans laquelle il y a autant de lignes que de stations. Chaque élément mij représente la durée de transport
entre les stations i et j dans le sens i −→ j. mij est positif si la liaison existe, infini (ou très grand) si elle
n’existe pas.
1) La matrice M est-elle symétrique ? Pourquoi ? (1 point)
2) On détermine le chemin le plus court entre les stations A et terminus grâce à un algorithme (Bellman,
Dikjstra, ...). La solution indique qu’il faut changer de ligne à la station C. Toutefois, cette solution ne
prend pas en compte la durée du changement de ligne. C’est pourquoi on souhaite modifier la matrice M
en y insérant la durée du changement de ligne à la station C de façon à pouvoir utiliser le même algorithme
pour déterminer le plus court chemin. Comment faire ? La réponse peut être soit rédigée, soit graphique
en s’inspirant de la figure 12.2 sans nécessairement la reproduire en entier. (2 points)
Correction
1) La matrice n’est pas symétrique sur la ligne 1, il existe trois arcs orientés : le passage du métro ne
s’effectue que dans un seul sens. Si on nomme i, j, k les trois stations concernées, les coefficients mij et
mji sont différents. L’un des deux sera infini puisque la connexion n’existe pas.
2) Il faut nécessairement ajouter des lignes ou des colonnes dans la matrice M puisqu’il faut ajouter une
nouvelle durée au précédent problème. Pour introduire les durées de changements, on considère que les
nœuds du graphe représente un couple (station, ligne). De cette façon, un coefficient mij de la matrice
représente :
– une durée de transport si les nœuds i et j sont deux stations différents de la même ligne
– une durée de changement si les nœuds i et j sont en fait la même station mais sur deux lignes différentes.
La réponse graphique est représentée par la figure 12.3. Il faut noter également que cette solution permet
de prendre également en compte un couple de stations reliées par deux lignes différentes ce qui n’était pas
possible avec la matrice décrite dans l’énoncé. C’est le cas par exemple des stations Concorde et Madeleine
qui sont reliées par les lignes 8 et 12 du métro parisien.
12. Exercices écrits 332
Figure 12.3 : Chaque nœud du graphe représente une station pour une ligne donnée. Cela signifie que si une station
est présente sur deux lignes, elle est représentée par deux nœuds. Un arc peut donc relier deux stations différents
de la même ligne ou deux stations identiques de deux lignes différentes. Dans ce second cas, c’est la durée d’un
changement de ligne.
Quelques élèves ont ajouté trois stations au lieu d’une seule, la station C étant représentée par quatre
stations, ces quatre stations étant reliées par des arcs de longueurs nulles ou égales au temps de changement
de ligne. C’est une solution même si elle est inutilement compliquée.
Un élève a proposé une solution qui n’implique pas l’ajout d’une station. Cette solution est envisageable
dans le cas de deux lignes de métro mais elle peut devenir compliquée à mettre en place dans le cas de
plusieurs lignes avec plusieurs stations consécutives sur la même ligne (voir figure 12.4).
Figure 12.4 : Toutes les connexions menant à la station C sont maintenant à sens unique : elles ne permettent
que d’arriver en C mais pas d’en repartir. Pour aller à une autre station voisine de C, il faut passer par l’un des
6 arcs ajoutés qui incluent ou non un temps de transfert selon que ces stations appartiennent à la même ligne.
Cette solution n’est pas aussi simple que la première et peut rapidement devenir compliquée si une même station
appartient à trois, quatre, lignes.
La solution impliquait nécessairement la création d’arcs (ou de connexion) entre différentes stations. Il ne
suffisait pas d’ajouter le temps de changement à des coefficients de la matrice de transition, cette opération
a pour conséquence de modifier les distances d’au moins un voyage sur une même ligne.
fin exo 12.5.5 u t
12.5.6 Héritage
Enoncé
self.b = b
def iteration (self, i) : # contenu d’une itération
pass
def boucle (self) : # opère la boucle
for i in range (self.a, self.b) :
self.iteration (i)
On l’utilise pour faire une somme : que fait le programme suivant ? Quel est le résultat numérique affiché ?
(1 point)
class Gauss (Boucle) :
def __init__ (self, b) :
Boucle.__init__ (self, 1, b+1)
self.x = 0
g = Gauss (10)
g.boucle ()
print g.x
2) De façon la plus concise possible, écrire la classe Carre qui hérite de la classe Gauss et qui fait la somme
des n premiers entiers au carré. (1 point)
3) On souhaite écrire la classe Boucle2 qui effectue une double boucle : une boucle imbriquée dans une
boucle. Le barême de cette question est le suivant :
– 1 point si le code de la classe Boucle2 contient deux boucles (for ou while).
– 2 points si le code de la classe Boucle2 ne contient qu’une boucle (for ou while).
Correction
1) Le programme effectue la somme des entiers compris entre 1 et 10 inclus : 10∗11/2 = 55. Le programme
affiche 55.
2) Le plus court est de repartir de la classe Gauss qui redéfinit le constructeur dont on a besoin.
g = Carre (10)
g.boucle ()
print g.x
3) La version incluant deux boucles est la plus simple à présenter. On modifie la classe Boucle de façon à
avoir deux boucles. La classe Gauss2 est un exemple d’utilisation de la classe Boucle2.
La méthode iteration inclut maintenant deux paramètres i et j et il paraît difficile de réutiliser la méthode
iteration de la classe boucle. Un moyen consiste à ajouter l’attribut i à la classe Boucle2. Il faut également
modifier la classe Gauss2.
On peut maintenant supprimer une boucle dans la méthode boucle de la classe Boucle2. La classe Gauss2
reste quant à elle inchangée.
La solution suivante est sans doute la plus simple puisqu’on n’utilise pas de subterfuge tel que self.j = j.
On surcharge cette fois la méthode iteration et non plus boucle. Mais il faut créer une seconde méthode
iteration2 acceptant i, j comme paramètres 3 .
3. Le langage C++ permettrait d’appeler la méthode iteration(self, i, j) car ce langage différencie les méthodes
acceptant des nombres différents de paramètres.
12. Exercices écrits 335
Il existait une autre méthode pour faire disparaître une boucle sans doute plus simple que ces manipulations
qui utilise une façon simple de manipuler les indices. C’est le même mécanisme que celui utilisé pour
représenter une matrice avec une seule liste et non une liste de listes. Ce n’était pas la réponse souhaitée
mais elle n’introduit qu’une seule boucle également.
l = []
for i in range (0, 4) :
c = []
for j in range (0, i) : c += [ j ] # ou c.append (j)
l += [ c ] # ou l.append (c)
for c in l : print c
l = [1,4,1,5,9]
d = { }
for u in l :
if u in d : d [u] += 1
else : d [u] = 1
print d
Correction
1)
[]
[0]
[0, 1]
[0, 1, 2]
2)
{1: 2, 4: 1, 5: 1, 9: 1}
12.6.2
Enoncé
Les exemples suivant contiennent des erreurs, il faut en expliquer la raison et proposer une correction qui
respecte l’intention de son concepteur.
1) (1 point)
n = 10 File "ecrit.py", line 3, in <module>
l = [i for i in (0,n)] l[9]
l[9] IndexError: list index out of range
2) (1 point)
d = {’un’: 1, ’deux’: 4} File "ecrit.py", line 2, in <module>
print deux print deux
NameError: name ’deux’ is not defined
3) (1 point)
l = [ "mot", "second" ] File "ecrit.py", line 2, in <module>
print l(0) print l(0)
TypeError: ’list’ object is not callable
4) (1 point)
l = [4, 2, 1, 3] File "ecrit.py", line 2, in <module>
ll = l.sort() print ll[0]
print ll[0] TypeError: ’NoneType’ object is unsubscriptable
Correction
1)
12. Exercices écrits 337
n = 10
l = [i for i in (0,n)]
print l # affiche [0, 10]
L’instruction l[9] implique que le 10ième élément existe et ce n’est pas le cas. Il faudrait écrire :
2) L’instruction print deux ne contient ni guillemet ni apostrophe : deux est considéré comme une variable
et celle-ci n’existe pas. Il faudrait écrire print ”deux” ou print d[”deux”].
3) L’instruction l(0) suppose que l est une fonction et c’est en fait une liste. Il faut remplacer les parenthèses
par des crochets : l[0].
4) L’instruction l.sort() trie la liste l. Elle la modifie et ne retourne aucun résultat, c’est à dire None. Pour
corriger le programme, il faut faire une copie de la liste l en ll puis la trier.
l = [4, 2, 1, 3]
ll = list (l)
ll.sort()
print ll[0]
Dans le cas où on ne cherche que le plus petit élément, on peut aussi écrire :
l = [4, 2, 1, 3]
print min(l)
12.6.3
Enoncé
Une liste l contient des nombres et on souhaite les permuter dans un ordre aléatoire. On dispose pour cela
de deux fonctions :
Exemple :
l = [4,5,3,7,4]
l.sort ()
print l # affiche [3, 4, 4, 5, 7]
Dans le cas d’une liste de couples (ou 2-uple), chaque élément de la liste est trié d’abord selon la première
coordonnée puis, en cas d’égalité, selon la seconde coordonnée. Cela donne :
Les quelques lignes qui suivent illustrent le résultat souhaité. Il reste à imaginer la fonction qui permute
de façon aléatoire la liste l. (3 points)
Correction
Pour répondre au problème, on s’inspire d’une méthode qui trie une liste tout en récupérant la position
de chaque élément. Pour ce faire, on construit une liste de couples (élément, position).
Pour effectuer une permutation, on construit une liste de couples (position aléatoire, élement). En triant
sur une position aléatoire, on dérange la liste des éléments de façon aléatoire. Il suffit alors de récupérer
la seconde colonne de cette liste de couples.
Ce n’était pas la seule réponse possible mais les autres solutions ne nécessitaient pas de tri comme celle-ci :
Une permutation peut se décomposer en une somme d’au plus n transpositions. Une transposition est une
permutation qui échange uniquement deux éléments. La fonction permutation choisit donc n transpositions
aléatoires, si on considère que l’identité est aussi une transposition. Une autre possibilité était :
12. Exercices écrits 339
La plupart des élèves ont oublié qu’il était possible de supprimer un élément d’une liste et ont reconstruit
une nouvelle liste d’où l’élément choisi aléatoirement était absent. Une erreur fréquente était de confondre
éléments de la liste et positions, ce qui amenait parfois à écrire :
Cette dernière version a deux problèmes. Le premier est que si on tire un élément déjà tiré, on n’ajoute
aucun élément et la liste qu’on retourne est plus courte que la liste à permuter. Le second problème découle
du premier : ce cas est toujours vrai si la liste à permuter contient deux éléments identiques. Les positions
sont uniques mais pas forcément les éléments à trier.
Au vu des réponses des élèves, celles qui échangeaient les positions des éléments semblaient plus intuitives
que celles s’appuyant sur un tri. Son coût est d’ailleurs plus élevé puisqu’il est égal à celui du tri :
O(n ln n). Son avantage est de pouvoir construire une permutation d’une liste démesurément grande voire
de l’effectuer sur plusieurs ordinateurs simultanément. Supposons que la liste à permuter ne tienne pas
dans la mémoire de l’ordinateur. La première étape qui consiste à donner un nombre aléatoire à un élément
peut très bien se faire en ne conservant en mémoire que l’élément courant. Le tri lui aussi, s’il est effectué
par fusion, pourra se faire par morceaux. En revanche, les méthodes qui utlisent les transpositions requiert
la liste en mémoire dans sa globalité puisqu’il faut accéder à n’importe quel élément à tout moment. Dans
12. Exercices écrits 340
le premier cas (tri), on dit que l’accès est séquentiel. Dans le second cas (transposition), il est aléatoire.
Un dernier avantage : la méthode avec tri utilise des fonctions standard qui réduisent son écriture et son
temps de conception par voie de conséquence.
fin exo 12.6.3 u t
12.6.4
Enoncé
N = 1000 personnes sont classées en quatre catégories. On souhaite convertir un numéro de classe (1, 2,
3, ou 4) en un nom de catégorie plus explicite. On considère la fonction suivante :
1) Connaissant le nombre de gens ni dans chaque catégorie pour ces N = 1000 personnes (n1 +n2 +n3 +n4 =
N ), quel est le nombre de comparaisons 4 effectuées lors des N appels à la fonction nom_csp en fonction
des nombres (ni ) ? (1 point)
2) On suppose que les (ni )i∈{1,2,3,4} sont tous différents, le nombre de comparaisons effectuées lors des
N appels à la fonction nom_csp dépend-il de l’ordre des lignes ? Si oui, y a-t-il un ordre optimal qui
minimise le nombre de comparaisons pour effectuer ces N conversions ? (2 points)
3) On propose une autre fonction :
1) On peut résumer le coût de la fonction nom_csp en fonction de csp par le tableau suivant :
csp 1 2 3 4
nombre de tests 1 2 3 3
4. Une comparaison correspond ici à un test, soit d’égalité ==, mais aussi d’infériorité <= par la suite.
12. Exercices écrits 341
2) Si les (ni )i sont tous différents, le nombre de tests dépend évidemment de l’ordre des lignes de la
fonction nom_csp. Pour minimiser le nombre de tests, il suffit de les ordonner en plaçant la catégorie la
plus importante en premier, la moins importante en dernier : il faut trier les (ni )i par ordre décroissant.
On suppose que :
ni1 > ni2 > ni3 > ni4
Supposons que cet ordre correspondant à : cat C. > cat A. > cat B. > cat D. Dans ce cas, on devrait
écrire :
Cette assertion se démontre mathématiquement par l’absurde. On suppose que la configuration optimale
n’est pas celle qui respecte l’ordre décroissant des (ni )i . Dans cette configuration, il existe k, l tels que :
nik < nil . On vérifie aisément qu’en les permutant, on obtient une configuration meilleure que la précédente.
C’est contradictoire avec l’hypothèse de départ.
3) On construit le même tableau que celui de la première question pour cette nouvelle fonction :
csp 1 2 3 4
nombre de tests 2 2 2 2
4) La réponse est : cela dépend. Dans le premier cas, le coût de la fonction est : n1 + (2 + 3 + 3)n2 =
n1 + 8 N −n
3 . La seconde fonction à pour coût : 2N . On choisit la première version si :
1
N − n1
n1 + 8 < 2N
3
8 8
n1 1 − < N 2−
3 3
5 2
− n1 < − N
3 3
2
n1 > N
5
La seconde fonction est le plus souvent la bonne manière de faire. Elle a également l’avantage d’être aussi
performante quel que soit le poids de chaque catégorie. C’est celle qu’utilise les dictionnaires. Mais dans
les cas presque dégénérés, la première méthode est plus rapide à condition que ce soit toujours les mêmes
catégories qui soient les plus fréquentes. Il est même possible d’envisager un mélange des deux : on traite
d’abord les quelques catégories très fréquentes avec la première fonction, on traite les autres avec la seconde
fonction.
fin exo 12.6.4 u t
12. Exercices écrits 342
12.6.5
Enoncé
Cet exercice ne nécessite aucune connaissance particulière sur les graphes bien que la figure suivante décrive
un graphe orienté : chaque flèche représente un chemin possible entre deux nœuds ou points de passage,
on ne peut les parcourir que dans le sens de la flèche. On veut répondre à la question : combien y a-t-il
de chemins possibles pour aller du nœud n11 au nœud n35 sachant qu’on ne peut que descendre ou aller
à droite ?
n11 / n12 / n13 / n14 / n15
n21 / n22 / n23 / n24 / n25
n31 / n32 / n33 / n34 / n35
1) Pour arriver au nœud n23 , on peut soit venir du nœud n22 soit du nœud n13 . On note N (nij ) le nombre
de chemins possibles pour arriver au nœud nij en partant du nœud n11 . Il n’y a que deux façons d’arriver
au nœud n23 , on en déduit que :
N (n23 ) = N (n22 ) + N (n13 )
def calculeN (I,J) : # I et J sont des entiers comme par exemple I=3 et J=5
return nombre
[1, 1, 1, 0, 0]
[1, 2, 3, 3, 3]
[1, 3, 6, 9, 12]
Le programme est incorrect. Indiquez où se trouve l’erreur ou les erreurs. Quelle est finalement la valeur
du résultat pour le nœud n35 ? (1 point)
2) On observe que le résultat de la fonction calculeN une fois corrigée vérifie :
i+j−2 (i + j − 2)!
N (nij ) = =
i−1 (i − 1)! (j − 1)!
12. Exercices écrits 343
n31 / n32 / n33 / n34 / n35
Correction
1) Les deux zéros sur la première ligne de résultat sont surprenant. Ils sont dûs au fait que J > I lorsqu’on
écrit les deux lignes suivantes :
On corrige ces deux zéros et les deux dernières colonnes pour obtenir :
[1, 1, 1, 1, 1]
[1, 2, 3, 4, 5]
[1, 3, 6, 10, 15]
2) A chaque nœud, il est possible soit de descendre, soit d’aller à droite. Pour aller au nœud (i, j), il faut
descendre i fois et aller j fois à droite et peu importe l’ordre. Le nombre de chemins correspond au nombre
de combinaisons possibles entre i "descendre" et j "à droite". Ceci explique la formule citée dans l’énoncé.
3) Il n’existe pas de solution unique. Il suffit de remarquer qu’il existe maintenant 2 façons d’aller au nœud
n12 ainsi qu’aux nœuds n13 , n14 , n15 . Il n’est toujours pas possible de revenir en arrière. On peut donc
modifier le programme comme suit :
return nombre
On obtient :
12. Exercices écrits 344
[1, 2, 2, 2, 2]
[1, 3, 5, 7, 9]
[1, 4, 9, 16, 25]
fin exo ?? u
t
On suppose qu’on dispose ici d’une matrice remplie de 0 et de 1. Les cases contenant les valeurs 1 repré-
sentent un motif "creux" ou contour qu’on souhaite remplir de 1. Les deux schémas suivants illustrent la
matrice avant le remplissage et le résultat qu’on souhaite obtenir après remplissage.
· ·
·· ·· ·· · · ·· ····
·······
·········
·· ·· ·· ·· ·· · ·· ············
···············
··················
·····················
·······················
·· ·· · · ············· ············
·· ·· ·· ···· ······· ·· ··· · ·· · ·· · · ·············
·············
··············
··············
··············
············
···········
··········
··········
·········
·· ·· · ··· ·· · · ·· · ·· ············
·········· ··
······ ·········
·········
· · · ···· ··· ····· ··
·· ·· · ·· ·· ·· · · ·· · ·
········
······
······
·······
··········
··············
·················
·················
·······
·······
·······
·······
·· ·· · · ·· · · ·········
········· ··········
········· ······
······· ·······
·· ·· · · ·· · · ·· ·· · ·· ·· · · ·· ·· · · · · ··········
···········
·········· ·······
······
···· ·······
·······
······· ·······
·······
·······
· · · · · · · · ·· · ·· · ···· · · · · · ·· ·· ········
·······
·····
······
········ ···········
··········
··········
·······
·······
·······
·······
······
······
······
· ···· ·· ·· · · ···· ·· · · ·· · ·· ···· ··········
···········
··· ··········
··············
·················
·················· ·······
······
······
·· · · · · · ·· ··············
·· · ·· · ··· ·
·········
········
······· ····················
·······
········
······
······
·········
··········
·· ·· · ·· ·· ·
·· · ·· · ··· ····
······
·····
······ ······
·····
····
············
···············
···············
···············
·· ··· · ··· ·
·· · ·· ···· · ·······
·······
········
········
·· ··············
· ·············
··············
··············
·············
·· ·· · ·· ········ ·············
·· · · ···· · ·· ··· ·· ··· ·········
·········
·········
·············
············
············
············
····················
· ·· ·· ·· ·· ··················
················
·············
·· ······ ···········
·········
·····
·
L’algorithme est assez simple : on suppose qu’on connaît une case de l’intérieur du contour. On lui donne
la valeur 1. On s’intéresse ensuite à ses quatre voisins (haut, bas, gauche, droite) jusqu’à ce qu’on soit
bloqué par le contour. Cela donne la fonction suivante :
Malheureusement, le programme ne se termine jamais. Il reste bloqué dans une boucle infinie. Lorsqu’on
affiche la taille de dico et les variables x, y 5 , on observe :
...
118 41 13
118 40 13
118 34 8
118 34 7
118 41 13
118 40 13
118 34 8
118 34 7
118 41 13
...
1) La séquence de chiffres montre que la boucle est une boucle infinie puisqu’elle reproduit sans cesse le
même cycle. A chaque passage dans la boucle, la fonction retire un élément du dictionnaire puis en ajoute
au plus quatre. Mais ceci ne veut pas dire obligatoirement que la boucle est une boucle infinie : on ajoute
des éléments à un dictionnaire et ils pourraient déjà être présents. Toutefois, comme le montre la sortie
du programme, c’est bien une boucle infinie. L’algorithme de remplissage oublie de tester en fait si une
case est déjà remplie ou non. Il continue indéfiniment sans se soucier du contour.
2) Il n’existe pas de solution unique, voici la plus courte
Si on désire un résultat moins précis mais du même ordre de grandeur, il suffisait d’ajouter la ligne
return len(dico) à la fin.
fin exo 12.7.1 u t
12.7.2
Enoncé
On s’intéresse dans cet exercice au problème du sac-à-dos : il s’agit de remplir le plus possible un sac-à-dos
qui ne peut supporter qu’un certain poids avec des objets dont le poids est connu. Par exemple, on dispose
d’un sac-à-dos de 15 kilos qu’on doit remplir avec des objets de 2,4,7,10 kilos. La meilleure solution consiste
à prendre les objets pesant 4 et 10 kilos.
1)
Le premier algorithme envisagé consiste à parcourir les objets par ordre de poids décroissant. Si un objet
peut être ajouté au sac-à-dos sans dépasser la limite alors il est ajouté et on passe à l’objet suivant.
Compléter le programme qui suit pour obtenir cet algorithme. (3 points)
2) Que retourne la fonction lorsque le poids du sac est de 10 kilos et qu’il n’y a qu’un seul objet de 12
kilos ? (1 point)
3) Trouver un exemple (une liste d’objets et un poids maximal) pour lequel cet algorithme ne retourne
pas la solution optimale. (2 points)
4)
On propose une autre résolution par récurrence. On note la liste des objets (w1 , ..., wn ) et P le poids
maximal à ne pas dépasser. On désigne S(w1 , ..., wn , P ) la solution optimale pour le poids P .
Autrement dit, la solution optimale d’un problème à n objets est la meilleure des deux solutions suivantes :
1. La meilleure solution à n − 1 objets et le même sac : le premier objet n’est pas sélectionné.
2. La meilleure solution à n − 1 objets et le sac diminué du poids du premier objet : le premier objet
est sélectionné.
On a cherché à implémenter cette solution, on aboutit au programme suivant :
t1 = sum(s1)
t2 = sum(s2) + objets [0] # ligne C
obj = [2,4,7,10]
sac = 15
print "solution ",resolution (obj, sac) # ligne A
Le programme déclenche l’exception suivante (les numéros de lignes ont été remplacées par des lettres
correspondant aux commentaires insérés ci-dessous) :
solution
Traceback (most recent call last):
File "examen2011.py", line A, in <module>
print "solution ",resolution (obj, sac)
File "examen2011.py", line B, in resolution
s2 = resolution (reduit, sac - objets [0])
File "examen2011.py", line C, in resolution
t2 = sum(s2) + objets [0]
TypeError: ’NoneType’ object is not iterable
Pourquoi ? (2 points)
5) Comment corriger la fonction pour qu’elle retourne le bon résultat ? (3 points)
6) On cherche à estimer le nombre de fois que la fonction resolution est appelée. Trouvez un équivalent
lorsque n est grand. (2 points)
Correction
1)
Il n’était pas évident de trouver l’erreur, elle pouvait être à trois endroits possibles :
1. objets[0] : impossible d’obtenir le premier élément d’un tableau. Peut-être que le tableau est vide
ou que objets n’est pas un tableau.
2. + : l’addition peut échouer si on cherche à ajouter des éléments qui ne peuvent pas être additionnés.
3. sum(s2) : pour une raison quelconque, le programme ne peut pas faire la somme de la liste s2.
La première cause est impossible sinon la liste reduit = objets[1 :] exécutée avant aurait déjà retournée
une erreur. La seconde ne l’est pas non plus car le message aurait explicitement mentionnée l’addition.
Reste la troisième option : sum(s2). Le message stipule qu’un objet est égale à None, c’est donc s2. Et None
est n’est pas un tableau ou une liste, il n’est pas itérable et ne peut être utilisé par la fonction sum.
Le programme s’arrête donc parce que la variable s2 contient None.
5) Si la variable s2 contient None, cela signifie que la fonction resolution a retourné None lors de son appel
à la ligne B :
Comme cette fonction ne contient aucune instruction explicite return None, c’est donc que la fonction se
termine sans retourner de résultat. Or elle se termine par les deux lignes suivantes :
Cela signifie qu’aucune de ces deux conditions n’est vérifiée et que la liste objets contient plus d’un élément
(voir la début de la fonction). Un autre cas se produit donc et pour le traiter, il suffit d’ajouter :
6) Simplifions l’expression de la fonction resolution qu’on appelle f . Soit U1n une liste de n objets indicée
de 1 à n. P est le poids du sac.
f (U2n , P ) si ....
n
f (U1 , P ) = f (U2n , P − u1 ) si .... (12.7)
∅ sinon
Cette reformulation fait apparaître une fonction récursive qui s’appelle deux fois dans des conditions
différentes. Le coût pour une liste de longueur n suit la suite (cn ) :
On suppose qu’on peut trier la liste l = [...] avec la méthode sort. Une fois le tableau trié, comment obtenir
la position du plus petit élément dans le tableau initial ?
Correction
L’idée est de ne pas trier la liste mais une liste de couples incluant chaque élément avec sa position. On
suppose que la liste l existe. La méthode sort utilisera le premier élément de chaque couple pour tier la
liste de couples.
l2 = [ ( l [i], i ) for i in range (0, len (l)) ]
l2.sort ()
print l2 [0][1] # affiche la position du plus petit élément
# dans le tableau initial
Est-ce que ce résultat change si on appelle la fonction ensemble_lettre avec un autre mot ?
2) Le programme précédent n’est vraisemblablement pas fidèle aux intentions de son auteur. Celui-ci avait
pour objectif de déterminer l’ensemble des lettres différentes de la chaîne de caractères passée en entrée.
Que faut-il faire pour le corriger ? Que sera le résultat de l’instruction ensemble_lettre(”baaa”) en tenant
compte de la modification suggérée ?
Correction
1) L’instruction if c in ens : signifie que le caractère c est ajouté seulement s’il est déjà présent dans la
liste s qui est vide au début de la fonction. Elle sera donc toujours vide à la fin de l’exécution de la fonction
et sera vide quelque soit le mot s fourni en entrée comme paramètre. Le résultat ne dépend donc pas du
mot.
2) Il suffit de changer if c in ens : en if c not in ens : pour donner :
def ensemble_lettre (s) :
ens = []
for i in range (0, len (s)) :
c = s [i]
if c not in ens :
ens.append (c)
return ens
12. Exercices écrits 350
Lors du premier passage dans la boucle for, le premier ajouté dans la liste l est k[len(k)] qui n’existent
pas puisque les indices vont de 0 à len(k). Voici la correction :
k = [10,14,15,-1,6]
l = []
for i in range (0,len (k)) :
l.append ( k [ len (k) - i-1 ] ) # -1 a été ajouté
Quel est le programme le plus rapide et pourquoi ? Quels sont les coûts des deux algorithmes ?
2) Combien faut-il d’itérations pour que la fonction retourne un résultat ?
√
3) On introduit la fonction racine_carree qui calcule k. Celle-ci est issue de la résolution de l’équation
f (x) = 0 avec f (x) = x2 − k à l’aide de la méthode de Newton 6 .
def racine_carree (k) :
x0 = float (k)+1
x = float (k)
while abs (x-x0) > 1e-10 :
x0 = x
x = (k-x*x) / (2 * x) + x
return x
√ √
Cette fonction retourne un résultat pour 2 en 6 itérations. On décompose ensuite ln 2 = ln ( 2)2 =
√ √
2 ln 2 = 2 ln 1 + 2 − 1 . En utilisant cette astuce et la fonction racine2, à combien estimez-vous
grossièrement le nombre d’itérations nécessaires pour calculer ln 2 : 10, 40 ou 100 ? Justifiez. On rappelle
que 210n ∼ 103n .
4) Que proposez-vous pour calculer ln 100 ?
Correction
1) Le second programme est le plus rapide. A chaque boucle du premier programme, la fonction puiss
calcule xn avec n multiplications, ce calcul est remplacé par une seule multiplication dans le second
programme.
Les deux programmes exécuteront exactement le même nombre de fois la boucle while. Soit n ce nombre
d’itérations, le coût du premier programme est 1 + 2 + 3 + 4 + ... + n ∼ O(n2 ) car la fonction puiss fait
i passages dans sa boucle for pour l’itération i. Le coût du second programme est en O(n).
n k
(−1)k+1 xk , la condition d’arrêt de la fonction log_suite correspond à :
P
2) Si on pose sn =
k=1
xk
|sn − sn−1 | > 10−10 ⇐⇒ > 10−10
k
Cette partie est un résumé de la première, elle reprend sans détailler les éléments essentiels du langage
Python et ne tient pas compte des extensions telles que les interfaces graphiques. Elle suffit à décrire le
langage Python à quelqu’un qui ne le connaît pas mais connaît déjà un autre langage de programmation.
Elle est également utile lorsqu’on conçoit ses premiers programmes et limite la recherche à une vingtaine
de pages au lieu d’une centaine.
Le second chapitre s’attarde sur des algorithmes de tri qu’il est préférable de connaître. Ils existent dans
tous les langages et sont utilisés dans presque tous les programmes. Il est difficile d’écrire un programme
ne contenant aucun tri comme il est difficile d’écrire sans utiliser la lettre e. Il est également parfois plus
rapide de reprogrammer un tri que de chercher une fonction qui correspond à vos besoins comme le fait de
trier conjointement plusieurs listes. Les deux sujets qui suivent sont rédigés de telle sorte qu’ils peuvent
aussi être lu comme des énoncés d’exercices.
Certaines pages concernent le calcul du coût d’un algorithme 7 . Ces considérations mathématiques n’in-
terviennent que rarement, principalement lorsque l’exigence en terme de rapidité d’exécution n’est pas
accessible simplement par l’utilisation d’une machine plus puissante ou d’un langage de programmation
plus rapide. Cette exigence passe par une amélioration de l’algorithme. Il existe par exemple des fonc-
tions dont il n’existe pas d’expression du coût à l’aide des opérations standard (addition, multiplication,
puissance). C’est le cas de la fonction d’Ackermann.
n+1 si m = 0
A(m, n) = A(m − 1, 1) si m > 0 et n = 0 (12.9)
A(m − 1, A(mn, n − 1)) sinon
7. Le coût d’un algorithme est le nombre d’opérations élémentaires (addition, multiplication, affectation, ...) nécessaires
à sa réalisation.
Chapitre 13
Instructions fréquentes en Python
13.1 Le langage
Quelques rappels sur le langage :
1. Il n’y a pas de séparateur d’instructions, il faut écrire une instruction par ligne et décaler les lignes
dans une boucle, un test, une fonction, une classe. Pour écrire une instruction sur plusieurs lignes,
il faut utiliser le caractères \ sur toutes les lignes de l’instruction sauf la dernière. Il ne doit rien y
avoir derrière ce caractère, ni espace, ni commentaires.
2. On peut mettre autant d’espaces qu’on veut. Il peut y en avoir aucun sauf derrière une instruction
for, if, ...
3. Les commentaires dans un programme commencent par le symbole # et vont jusqu’à la fin de la
ligne.
4. L’instruction print permet d’afficher n’importe quelle information. Elle affiche le résultat de la
méthode __str__ pour les classes.
5. L’instruction help affiche l’aide associée à une variable, une fonction, une classe, une méthode, un
module. Pour une fonction, une classe, une méthode du programme, cette aide correspond à une
chaîne de caractères encadrée par trois ". Ce message d’aide peut s’étaler sur plusieurs lignes.
def fonction () :
"""fonction de
démonstration"""
return 0
help (fonction) # affiche fonction de
# démonstration
En cas de doute sur une partie de code, un calcul, une priorité entre opérateurs, le résultat
d’une fonction, il est possible d’utiliser la fonction print pour afficher une valeur intermédiaire
pendant un calcul. Il ne faut pas non plus hésiter à vérifier sur un petit exemple dans un
petit programme que des lignes douteuses font exactement ce pour quoi elles ont été écrites.
Il est souvent utile de chercher sur Internet des exemples de programmes pour corriger une syntaxe in-
correcte, utiliser Google en ajoutant une requête commençant par le mot Python. Pour les erreurs, il est
parfois intéressant de recopier intégralement le message d’erreur sous Google, les réponses obtenues sont
souvent assez claires.
va = <valeur>
Le type de < valeur > détermine le type de la variable va. Si une variable de même portée portait déjà ce
nom-là, son contenu est écrasé (perdu aussi). L’instruction type(x) retourne le type de la variable x. Un
identificateur ne peut être utilisé qu’une seule fois, qu’il désigne une variable, une fonction,
une classe ou un module.
t = () # tuple vide
t = (2, "e") # tuple de deux éléments
print t[0] # affiche le premier éléments
L’affectation d’une valeur de type immuable à une variable est une copie. On peut appliquer sur les types
numériques les opérations usuelles (+ * - / % ** += *= -= /= %= **=) 1 . On rappelle que a+ = 10 est
équivalent à a = a + 10, ceci signifie que la valeur de a avant le calcul n’a plus besoin d’exister. Le et logique
et le ou logique sont notés and et or. Les priorités sont celles usuellement utilisées en mathématique, en
cas de doute, il faut utiliser des parenthèses.
Les opérateurs de comparaison (< > == <= >=) s’appliquent sur tous les types numériques ainsi que
sur les chaînes de caractères. Rappel : les minuscules sont classées après les majuscules.
Fréquente source de bug : une division entière a pour résultat le quotient et non un nombre
décimal. Autrement dit : 1/2 = 0 et non 0.5.
Pour convertir une information d’un type à un autre, il suffit d’utiliser le nom de ce type suivi de la valeur
à convertir entre parenthèses : b = float(”2.145”) équivaut à la conversion d’une chaîne de caractères en
réel.
L’addition d’un t-uple et d’une valeur retourne un t-uple incluant cette valeur à la fin (plus long d’un
élément). L’addition de deux t-uples concatène les deux t-uples. L’addition de deux chaînes de caractères
retourne leur concaténation.
Pour savoir si un élément x fait partie d’un t-uple t, il faut utiliser la syntaxe x in t dont la réciproque est
x not in t.
La fonction len retourne la longueur d’un tuple ou d’une chaîne de caractères. Les éléments ou les caractères
d’un tuple ou d’une chaîne de caractères t sont indicés de 0 à len(t) − 1 inclus.
1. ** est le symbole pour puissance : 3 ∗ ∗4 = 34
13. Instructions fréquentes en Python 357
Pour les chaînes de caractères, on utilise fréquemment les méthodes de la table 13.1, exemple :
st = "langage python"
st = st.upper () # mise en lettres majuscules
i = st.find ("PYTHON") # on cherche "PYTHON" dans st
print i # affiche 8
print st.count ("PYTHON") # affiche 1
print st.count ("PYTHON", 9) # affiche 0
count(sub[, start[, end]]) Retourne le nombre d’occurences de la chaîne de caractères sub, les para-
mètres par défaut start et end permettent de réduire la recherche entre
les caractères d’indice start et end exclu. Par défaut, start est nul tandis
que end correspond à la fin de la chaîne de caractères.
find(sub[, start[, end]]) Recherche une chaîne de caractères sub, les paramètres par défaut start
et end ont la même signification que ceux de la fonction count. Cette
fonction retourne -1 si la recherche n’a pas abouti.
isalpha() Retourne True si tous les caractères sont des lettres, False sinon.
isdigit() Retourne True si tous les caractères sont des chiffres, False sinon.
replace(old, new[, count]) Retourne une copie de la chaîne de caractères en remplaçant toutes les
occurrences de la chaîne sub par new. Si le paramètre optionnel count est
renseigné, alors seules les count premières occurrences seront remplacées.
split([sep[, maxsplit]]) Découpe la chaîne de caractères en se servant de la chaîne split comme
délimiteur. Si le paramètre maxsplit est renseigné, au plus maxsplit cou-
pures seront effectuées.
upper() Remplace les minuscules par des majuscules.
lower() Remplace les majuscules par des minuscules.
Table 13.1 : Quelques fonctions s’appliquant aux chaînes de caractères, l’aide associée au langage Python fournira
la liste complète. Certains des paramètres sont encadrés par des crochets, ceci signifie qu’ils sont facultatifs (voir
également page 31).
L’affichage de réels nécessite parfois de tronquer la partie décimale ce qui est fait grâce à la syntaxe suivante
(voir également page 31) :
x = 0.123456789
print "%1.2f" % x # donne 0.12
s = "%2.2e %s" % (3.14, "est une approximation de pi")
print s
a = [1,2]
b = a
La seconde ligne ne fait pas une copie de la première liste, elle ne fait que créer un second nom pour
nommer la même liste. Pour copier une liste ou un dictionnaire, il faut utiliser :
13. Instructions fréquentes en Python 358
a = [1,2]
import copy
b = copy.copy (a)
a = [1,2]
import copy
b = copy.deepcopy (a)
Cette remarque s’applique à tout type modifiable, liste, dictionnaire ou tout autre classe. La suppression
d’une variable n’implique pas la suppression de toutes les variables se référant à une seule et même instance
de classe.
13.2.2.1 Liste
Une liste est une sorte de tableau qui permet de mémoriser un ensemble d’éléments de types variés. C’est
une sorte de t-uple modifiable. La table 13.2 regroupe les opérations qu’une liste supporte et la table 13.3
les méthodes dont elle dispose.
Les listes peuvent aussi être définies à partir d’une écriture abrégée :
13.2.2.2 Dictionnaire
Un dictionnaire est un tableau pour lequel les indices ou clés ne sont pas uniquement des entiers mais tout
type non modifiable (le plus souvent un entier, un réel, une chaîne de caractères, un t-uple).
x = { "cle1":"valeur1", "cle2":"valeur2" }
print x ["cle1"] # affiche valeur1
x [(0,1)] = "clé tuple" # ajoute une nouvelle valeur dont la clé est (0,1)
y = { } # crée un dictionnaire vide
z = dict () # crée aussi un dictionnaire vide
La table 13.4 regroupe les opérations qu’un dictionnaire supporte. La table 13.5 regroupe les méthodes
d’un dictionnaire.
13. Instructions fréquentes en Python 359
13.2.2.3 Tableaux
Ce type ne fait pas partie du langage Python standard mais il est couramment utilisé.
import numpy
a = numpy.array ( [0,1] )
Il permet de convertir des listes en une structure plus appropriée au calcul. En contrepartie, il n’est pas
aussi rapide d’ajouter ou supprimer des éléments.
if x < 5 :
print x
...
if x < 5 :
print x
...
else :
print 5-x
...
S’il n’y a qu’une seule instruction, elle peut s’écrire en bout de ligne :
if x < 5 : print x
else : print 5-x
13.3.2 Boucles
Il y a deux types de boucles, la boucle for parcourt un ensemble, la boucle while continue tant qu’une
condition est vraie. Comme pour les tests, une boucle est suivie du syumbol :, les lignes incluses dans
cette boucle sont indentées à moins qu’il n’y en ait qu’une seule, auquel cas elle peut être écrite après le
symbole : sur la même ligne.
13. Instructions fréquentes en Python 361
while condition :
# lignes décalées
# contenu de la boucle
Pour toutes les boucles, l’instruction break permet de sortir de la boucle, l’instruction continue passe
directement à l’itération suivante sans exécuter les instructions qui suivent l’instruction continue.
13. Instructions fréquentes en Python 362
13.4 Fonctions
Les fonctions ou sous-programmes permettent de faire la même chose sans avoir à recopier le code infor-
matique plusieurs fois dans le programme. Elle accepte aucun ou plusieurs paramètres, elle peut retourner
aucun ou plusieurs résultats. Leur déclaration suit le schéma suivant :
L’instruction return n’est pas obligatoire mais si elle est présente à un ou plusieurs endroits, aucune autre
instruction de la fonction ne sera exécutée après l’exécution de la première instruction return rencontrée
lors de l’exécution de la fonction. Les fonctions peuvent être récursives et inclure des paramètres par
défaut : ces paramètres reçoivent une valeur même si celle-ci n’est pas précisée lors de l’appel.
Les paramètres par défaut doivent tous être mis en fin de déclaration, l’exemple suivant n’est pas correct :
En ce qui concerne les paramètres, les paramètres de type non modifiable sont passés par valeur (une
modification à l’intérieur de la fonction n’a pas de répercution à l’extérieur).
Les paramètres de type modifiable sont passés par référence (une modification à l’intérieur de la fonction
a des répercutions à l’extérieur).
13.5 Classes
Les classes sont un moyen de définir de nouveaux types modifiables de variables. Peu de programmes
ne les utilisent pas. Une classe est un ensemble d’attributs (ou variables) et de méthodes (ou fonctions).
13. Instructions fréquentes en Python 363
Un programme utilisant les classes est orienté objet. Il est possible de faire les mêmes choses avec ou
sans classes mais leur utilisation rend d’ordinaire les grands programmes plus facile à comprendre et à
construire.
class ma_classe :
def __init__ (self, att1, att2, att3) :
self.att1 = att1
self.att2 = att2
self.att3 = att3
self.att4 = att1 * att2 * att3
Lors de la déclaration de la variable a, le langage Python exécute la méthode __init__ aussi appelée
constructeur. Elle permet de définir les attributs de la classe directement à partir des paramètres ou comme
le résultat d’un calcul ou d’une fonction. Le constructeur comme toutes les autres méthodes possède comme
premier paramètre self qui permet d’accéder aux attributs et aux méthodes de la classe. Le programme
suivant est équivalent au premier.
class ma_classe :
def __init__ (self, att1, att2, att3) :
self.att1 = att1
self.att2 = att2
self.att3 = att3
self.att4 = self.calcule4 ()
class ma_classe :
def __init__ (self, att1, att2, att3) :
13. Instructions fréquentes en Python 364
self.att1 = att1
self.att2 = att2
self.att3 = att3
self.att4 = att1 * att2 * att3
a = ma_classe (1,2,3)
print a.att1 # affiche 1
print a.__dict__ ["att1"] # affiche aussi 1, ligne équivalente à la précédente
13.5.3 Opérateurs
Les opérateurs sont des méthodes qui permettent une manipulation plus simple des objets. Leur nom est
fixé par convention par le langage Python, ils commencent et terminent par __.
class ma_classe :
def __init__ (self, att1, att2, att3) :
self.att1 = att1
self.att2 = att2
self.att3 = att3
self.att4 = att1 * att2 * att3
a = ma_classe (1,2,3)
b = ma_classe (4,5,6)
c = a + b # n’a de sens que si l’opérateur __add__ a été redéfini
Il existe un opérateur spécifique pour chaque opération, cet opérateur permet de donner un sens à une
addition, une soustraction, ..., de deux instances d’une classe. L’opérateur __str__ retourne une chaîne
de caractères et est appelé par l’instruction print. L’opérateur __cmp__ retourne un entier permettant à
des instances de la classe d’être comparées et triées par une liste.
class ma_classe :
def __init__ (self, att1, att2, att3) :
self.att1 = att1
self.att2 = att2
self.att3 = att3
self.att4 = att1 * att2 * att3
a = ma_classe (1,2,3)
b = a
b.att1 = -16
print a.att1 # affiche -16
print b.att1 # affiche -16
class ma_classe :
def __init__ (self, att1, att2, att3) :
self.att1 = att1
13. Instructions fréquentes en Python 365
self.att2 = att2
self.att3 = att3
self.att4 = att1 * att2 * att3
a = ma_classe (1,2,3)
import copy
b = copy.copy (a)
b.att1 = -16
print a.att1 # affiche 1
print b.att1 # affiche -16
Lorsque une classe inclut une variable de type classe, il faut utiliser la fonction deepcopy et non copy.
13.5.5 Héritage
L’héritage est l’intérêt majeur des classes et de la programmation orientée objet. Lorsqu’une classe hérite
d’une autre, elle hérite de ses attributs et de ses méthodes. Le simple fait d’hériter crée donc une classe
équivalente.
class ma_classe :
def __init__ (self, att1, att2, att3) :
self.att1 = att1
self.att2 = att2
self.att3 = att3
self.att4 = att1 * att2 * att3
class ma_classe :
def __init__ (self, att1) :
self.att1 = att1
self.att2 = self.calcul ()
a = ma_classe (2)
b = ma_classe2 (2)
print a.att2 # affiche 4 = 2 * 2
print b.att2 # affiche 8 = (2*2) * 2
13.6 Fichiers
L’écriture et la lecture dans un fichier s’effectuent toujours de la même manière. On ouvre le fichier en mode
écriture ou lecture, on écrit ou on lit, puis on ferme le fichier, le laissant disponible pour une utilisation
13. Instructions fréquentes en Python 366
ultérieure. Ce paragraphe ne présente pas l’écriture ou la lecture dans un format binaire car celle-ci est
peu utilisée dans ce langage.
f = open ("nom-fichier", "w") # ouverture en mode écriture "w" ou écriture ajout "a"
f.close () # fermeture
\n passage à la ligne
\t insertion d’une tabulation, indique un passage à la colonne suivante dans le logiciel Excel
Pour lire ce fichier, il est nécessaire de scinder chaque ligne en une liste de chaînes de caractères. On utilise
pour cela la méthode split des chaînes de caractères.
for s in l :
s2 = s.replace ("\n", "") # on supprime le code de fin de ligne \n
s2 = s2.replace ("\r", "") # on supprime le code de fin de ligne \r (Windows uniquement)
case = s2.split (";")
if len (case) >= 3 :
print case [1], " ", case [0], " a écrit ", case [2]
13.7 Modules
Le concept de module permet de répartir différentes parties d’un programme sur plusieurs fichiers. Il
existe deux types de modules : ceux disponibles sur Internet (programmés par d’autres) et ceux que l’on
programme soi-même. Les premiers sont souvent fournis avec un programme d’installation automatique
ou dans le cas où ils sont manquants, des instructions permettant de l’installer. Les seconds sont écrits
dans le même répertoire que le fichier principal. On enregistre le module suivant sous le nom geometrie.py.
class point :
def __init__ (self,x,y) :
self.x, self.y = x,y
Pour utiliser une fonction ou une classe du module geometrie.py, on utilise une des syntaxes suivantes :
1. Première syntaxe :
import geometrie
print geometrie.carre (1.5)
p = geometrie.point (1,2)
2. Deuxième syntaxe :
import geometrie as GEO # on donne un pseudonyme au module geometrie
print GEO.carre (1.5)
p = GEO.point (1,2)
3. Troisième syntaxe : le module est utilisé très souvent, même un pseudonyme est trop long, il faut
néanmoins s’assurer que les modules importés de cette même manière n’incluent pas des fonctions
ou classes portant des noms identiques. Dans ce cas, c’est toujours le dernier qui gagne.
13. Instructions fréquentes en Python 368
Dans le cas des modules installés, les trois syntaxes d’utilisation sont aussi valables. On voit aussi souvent
apparaître dans un module la condition :
if __name__ == "__main__" :
# quelques instructions ici
Ces instructions ne sont exécutées que si le module est utilisé en tant que programme principal. Lorsque
ce fichier est importé, elles ne sont jamais exécutées. Cela permet d’écrire des instructions qui permettent
de vérifier si le module ne contient pas d’erreurs. Une fois cette étape effectuée, il ne sert à rien de la
répéter à chaque fois que le module est importé. C’est pourquoi elles ne sont exécutées que si la condition
if __name__ == ”__main__” : est vérifiée, c’est-à-dire si le module est le programme principal et non
un module.
13.8 Exceptions
Le petit programme suivant déclenche une erreur parce qu’il effectue une division par zéro.
Le mécanisme des exceptions permet au programme de "rattraper" les erreurs, de détecter qu’une erreur
s’est produite et d’agir en conséquence afin que le programme ne s’arrête pas :
On protège la partie du code à l’aide des mots-clés try et except. Entre ces deux instructions, s’il se
produit une erreur, le programme passe immédiatement à ce qui suit l’instruction except. On peut même
récupérer le message d’erreur correspondant :
try :
print inverse (2)
print inverse (0)
except Exception, exc:
print "exception de type ", exc.__class__
# affiche exception de type exceptions.ZeroDivisionError
print "message ", exc
# affiche le message associé à l’exception
On peut aussi décider que le programme agira différemment selon l’erreur produite. Dans l’exemple suivant,
le programme teste d’abord si l’erreur est de type ZeroDivisionError auquel cas il affiche le message division
par zéro. Pour un autre type d’erreur, il regarde s’il y a d’autres instructions except qui s’y rapportent.
S’il y en a une, il exécute les lignes qui la suivent, sinon, le programme s’arrête et déclenche une erreur.
Les instructions try et except peuvent apparaître dans le programme principal, dans une boucle, un test,
une fonction, s’imbriquer les unes dans les autres. Il est possible de déclencher soi-même une exception
avec l’instruction raise et ou de définir ses propres exceptions en créant une classe héritant d’une classe
d’exception. L’exemple suivant regroupe tous ces cas.
def __str__(self) :
return """exception AucunChiffre, lancée depuis la fonction """ + self.f + \
" avec le paramètre " + self.s
try :
s = "123a"
i = conversion (s)
print s, " = ", i
except AucunChiffre, exc :
print AucunChiffre.__doc__, " : ", exc
print "fonction : ", exc.f
13. Instructions fréquentes en Python 370
Une chaîne de caractères est un tableau de caractères : pour accéder à un caractère, on procède comme
pour une liste.
s = "abcdefghijklmnopqrstuvwxyz"
print s [4] # affiche "e"
print s [4:6] # affiche "ef"
Le code précédent ne fonctionne pas car il n’y a pas de guillemets autour de un, deux, trois, quatre. Le
langage considère alors ces quatre mots comme des variables : un identificateur qui désigne une information.
Mais comme ces variables n’existent pas, ces identifiants ne sont reliés à aucun contenu et l’interpréteur
Python ne comprend pas.
Un mot entouré de guillemets définit un contenu. Sans guillemet, il définit une variable qui permet de
manipuler un contenu tout simplement en donnant la possibilité au programmeur de le nommer. Autrement
dit, pour manipuler une chaîne de caractères, il faut affecter ce contenu à une variable. Les guillemets
n’apparaissent plus par la suite car on doit utiliser la variable pour la manipuler.
13.9.2 Boucles
13.9.2.1 range ou pas range
Les deux programmes suivant sont équivalents. La seule différence réside dans l’écriture dans la boucle
for qui utilise dans le premier cas la fonction range et dans l’autre non.
Lorsqu’on utilise la fonction range, on dispose lors de la boucle de deux informations, l’indice i et l’élément
l[i]. Si l’indice n’est pas utile, il est possible de simplifier la boucle comme suit.
En général, on se sert de la boucle qui utilise la fonction range dans deux cas :
13. Instructions fréquentes en Python 371
1. On souhaite faire des opérations sur les éléments qui précèdent ou suivent l’élément en question, ce
qui nécessite de connaître l’indice.
2. On parcourt deux listes de même taille à la fois : l’indice désigne la position de deux éléments, un
dans chaque liste.
13.9.2.2 Initialisation
Une boucle est souvent utilisée pour faire une somme, calculer un maximum : garder un unique résultat en
parcourant une liste. Une boucle de ce type est toujours précédée d’une étape d’initialisation qui consiste
à donner une valeur au résultat : celle qu’il aurait si la liste était vide.
13.9.3 Fonctions
13.9.3.1 Différence entre print et return
A la fin d’un calcul, afin de voir son résultat, on utilise souvent l’instruction print. On peut se demander
alors si à la fin de chaque fonction, il ne faudrait pas utiliser l’instruction print. A quoi servirait alors
l’instruction return ? On suppose qu’un calcul est en fait le résultat de trois calculs à la suite :
a = calcul1 (3)
b = calcul2 (a)
c = calcul3 (b) # c résultat souhaité et affiché
Chaque terme calculx cache une fonction or seul le résultat de la dernière nous intéresse et doit être
affiché. Pour les deux premières, la seule chose importante est que leur résultat soit transmis à la fonction
suivante et ceci ne peut se faire que grâce à l’instruction return. L’instruction print insérée dans le code
de la fonction calcul1 ou calcul2 permettra d’afficher le résultat mais ne le transmettra pas : l’instruction
return est donc indispensable, print facultative.
En revanche, dans la dernière fonction calcul3, il est possible de se passer de return et de se contenter
uniquement d’un print. Cependant, il est conseillé d’utiliser quand même return au cas où le résultat de
la fonction calcul3 serait utilisé par une autre fonction, calcul4 par exemple.
Chapitre 14
Outils d’aide au développement
Ce chapitre présente différents outils qui accompagnent la création d’une application dans quelque langage
de programmation que ce soit. Les premiers outils présentés sont des modules du langage Python qui
permettent de fiabiliser un programme malgré de nombreuses modifications ou de mesurer le temps passé
dans chaque fonction.
Les autres outils sont des applications facilitant le développement etle partage d’information. Le premier,
Tortoise SVN, est un logiciel de suivi de version : il permet de conserver l’historique de vos modifications.
Le second, HTML Help Workshop, permet de construire des fichiers d’aide au format chm. Le troisième,
InnoSetup, permet de construire un installateur, un programme qui installera votre application sur une
autre machine. Le dernier outil, MoinMoin est ce qu’on appelle un Wiki, il permet facilement de partager
l’information au sein d’une entreprise en offrant à chacun la possibilité de modifier des pages internet et
d’y effectuer des recherches.
Remarque 14.1 : instructions d’installation et de configuration
Les instructions qui permettent d’installer et de configurer les applications Apache, SVN, MoinMoin, ...
sont extraites des sites internet de chaque applications. Elles sont valides pour les numéros de version cités
dans ce document mais il est possible qu’elles évoluent dans le futur. Pour des versions plus récentes, il
est probable que ces installations se modifient 1 .
def run () :
files = [".\\genchm.py", ".\\genhelp.py", "os", "sys"]
res = genhelp.genhelp (files)
print res # [’genchm.html’, ’genhelp.html’, ’os.html’, ’sys.html’]
genchm (res, "GenHelp")
if __name__ == "__main__" :
# écrit des informations relatives à l’exécution de la fonction
1. Les versions testées lors de l’écriture de ce document sont Apache 2.2.8, SVN 1.4.6, MoinMoin 1.6.3, InnoSetup 5.2.3,
Tortoise SVN 1.4.8, HTML Help Workshop 4.74, MySQL 5.0.51.
2. Certains site comme celui-ci http://sebsauvage.net/python/snyppets/index.html sont plus utiles lors de la conception, ils
recensent de nombreux usages du langages toujours illustrés d’un exemple.
14. Outils d’aide au développement 373
Ces informations nous révèle que la fonction la plus souvent appelée replace ne consomme presque pas de
temps d’exécution. La fonction generate_help_file dure près de trois quarts de seconde à chaque appel.
La fonction run est la plus longue car elle cumule le temps passé dans toutes les autres.
En juillet, on s’aperçoit qu’il faut modifier fonction C, cette modification implique la modification de
fonction A. Quel est l’impact sur fonction B qui est une vieille fonction mais dont le comportement ne doit
14. Outils d’aide au développement 374
absolument pas changer ? Ce cas de figure se produit fréquemment lors de la conception d’une application
et il n’est pas toujours facile de prévoir les conséquences d’une modifications d’une fonction lorsque celle-ci
est appelée d’un peu partout dans le code.
Une des solutions proposées par le langage Pythonest le module doctest. Il permet de simplifier le travail
de vérification pour des fonctions qui retourne un résultat. Plus précisément, il est possible de spécifier
une liste d’appels à une fonction et de comparer le résultat avec un résultat attendu ou souhaité.
L’exemple suivant utilise une fonction d’addition de deux listes. Elle retourne une liste de même taille
contenant la somme membre à membre des deux listes. On souhaite vérifier que cette fonction retourne les
bonnes valeurs pour certaines valeurs d’entrées connues. Dans ce cas, on souhaite s’assurer que la fonction
retourne bien :
l1 l2 résultat
[] [] []
[1, 2] [3, −1] [4, 1]
La première étape est d’ajouter ces résultats dans le commentaire associé à la fonction. On fait précéder
l’appel de >>> et on écrit le résultat sur la ligne suivante.
Il suffit ensuite d’ajouter une fonction appelant ces tests pour le module en question :
def _test():
import doctest
print doctest.testmod()
Si le test est validé, il ne se passe rien lorsqu’on appelle la fonction _test, sinon le message suivant
apparaît :
Dans ce cas précis, une erreur se déclenche car un espace de trop a été ajouté au résultat attendu. Il suffit
d’appeler régulièrement ou à chaque modification du programme la fonction t est de tous les modules d’un
programme pour s’assurer que les résultats sont toujours ceux attendus. On peut mettre plusieurs tests
bout à bout, le programme complet donne :
# coding: latin-1
def addition (l1, l2):
"""cette fonction additionne deux listes
>>> addition ( [], [])
[] 5
>>> addition ( [1,2], [3,-1])
[4, 1]
"""
14. Outils d’aide au développement 375
res = []
for i in range (0, len (l1)) : 10
res.append ( l1 [i] + l2 [i] )
return res
def _test():
import doctest 15
doctest.testmod()
if __name__ == "__main__":
_test()
Il reste à gérer le cas où la fonction retourne une exception. Il suffit d’inclure le texte suivant dans le
commentaires 3 . Les deux premières lignes sont identiques à chaque fois, la dernière est l’exception lancée
par la fonction.
# coding: latin-1
def addition (l1, l2):
"""cette fonction additionne deux listes
>>> addition ( [], [])
[] 5
>>> addition ( [1,2], [3,-1])
[4, 1]
>>> addition ( [1], [3,-1])
Traceback (most recent call last):
... 10
Exception: listes de tailles différentes
"""
if len (l1) != len (l2) :
raise Exception ("listes de tailles différentes")
res = [] 15
for i in range (0, len (l1)) :
res.append ( l1 [i] + l2 [i] )
return res
def _test(): 20
import doctest
doctest.testmod()
if __name__ == "__main__":
_test() 25
Le module doctest reste cependant très limité puisqu’il ne permet que de tester le résultat d’une fonction
et non un paramètre modifié comme une liste. C’est une manière de faire simple et vite pour des fonctions
courtes et retournant des résultats principalement numériques. Ce module permet néanmoins d’introduire
le concept de tests automatiques d’une application ou tests unitaires développés par le paragraphe suivant.
utilisateurs finaux 4 Le module unittest facilite la mise en place de ces tests mais ils pourraient tout-à-fait
être implémentés sans l’utiliser.
L’objectif reste identique à celui visé par le module doctest, il est simplement plus ambitieux. Cette fois-ci,
le code correspondant aux tests sera séparé du code de l’application elle-même. Les tests inclus dont de
deux types :
1. vérifier que la fonction fait bien ce pour quoi elle a été créé y compris dans les cas limites, on vérifiera
par exemple qu’une fonction de tri retourne bien un tableau trié, qu’une fonction retourne bien le
bon code d’erreur dans un cas précis
2. vérifier que la correction apportée suite à la découverte d’un bug continue bien de corriger ce bug
Ce paragraphe n’abordera pas en détail toutes les possibilités du module unittest, ils ne sont pas indis-
pensables au travail d’un ingénieur à moins que son travail n’évolue vers la création d’une application qui
dépasse son propre usage ou d’une application destinée à durer. Le programme dont on souhaite vérifier
la fiabilité est celui du paragraphe précédent :
# coding: latin-1
def addition (l1, l2):
"""cette fonction additionne deux listes"""
if len (l1) != len (l2) :
raise Exception ("listes de tailles différentes") 5
res = []
for i in range (0, len (l1)) :
res.append ( l1 [i] + l2 [i] )
return res
On écrit maintenant un autre fichier contenant les trois tests implémentés avec le module doctest. Deux
concernaient des résultats retournés par la fonction addition et le dernier un cas générant une exception.
Pour définir un test, le principe est le suivant :
1. créer une classe héritant de unittest.TestCase
2. ajouter une méthode dont le nom commence par test
3. dans le corps de cette méthode, écrire le code du test, ce dernier est validé si une série de conditions
est vérifiée, pour chaque condition, on ajoutera l’instruction assert condition
Le test est passé avec succès si les conditions précédées du mot-clé assert sont vraies. Si l’une d’elle est
fausse, le test a échoué et signifie que le programme est buggé. Pour lancer les tests, il faut appeler la
fonction unittest.main(). Cela donne le programme suivant :
# coding: latin-1
import unittest
from testunit1 import *
if __name__ == "__main__" :
# on lance les tests
unittest.main ()
Après avoir modifiée la fonction addition de façon à faire échouer le second test test_addition, le message
retourné par Python est :
======================================================================
ERROR: test_addition : test de [1,2] + [3,-1] != [4,1]
----------------------------------------------------------------------
Traceback (most recent call last):
File "testunit2.py", line 16, in test_addition
assert l [0] == 4 and l [1] == 1
IndexError: list index out of range
Les premières lignes reprennent le commentaires associées à la méthode de test. Les lignes suivantes
permettent de comprendre où l’erreur est apparue et quelle condition a fait échouer le test. La fonction
addition a égalément été intentionnellement modifiée pour ne pas générer d’exception lorsque les tailles
de listes sont différentes. Le message suivant apparaît :
======================================================================
FAIL: on vérifie que l’addition de deux listes de tailles différentes génère une exception
----------------------------------------------------------------------
Traceback (most recent call last):
File "testunit2.py", line 27, in test_exception
assert str (e.__class__ .__name__) != "AssertionError"
AssertionError
Le module unittest indique également le temps passé à effectuer ces tests et le nombre de tests lancés.
Lorsqu’il existe une multitude de tests, il est sans doute préférable de les répartir sur plusieurs fichiers à
la fois. Il faut pouvoir les lancer depuis un seul fichier qui passera tous les tests unitaires en séries. C’est
l’objectif du programme suivant qui récupère tous les fichiers test∗ .py du répertoire, les importe puis crée
des suites de tests unitaires pour les méthodes test... des classes intitulées Test....
# coding: latin-1
import unittest
import os
def get_test_file () : 5
"""retourne la liste de tous les fichiers *.py commençant par test_"""
li = os.listdir (".")
li = [ l for l in li if "test_" in l and ".py" in l and \
".pyc" not in l and ".pyd" not in l]
return li 10
return allsuite
def main () : 35
"""crée puis lance les suites de textes définis dans
des programmes test_*.py"""
li = get_test_file ()
suite = import_files (li)
runner = unittest.TextTestRunner() 40
for s in suite :
print "running test for ", s [1]
runner.run (s [0])
if __name__ == "__main__" : 45
main ()
# coding: cp1252
import glob
import shutil
def copie_repertoire (rep1, rep2) :
"""copie tous les fichiers d’un répertoire rep1 vers un autre rep2"""
li = glob.glob (rep1 + "/*.*")
for l in li :
to = l.replace (rep1, rep2) # nom du fichier copié (on remplace rep1 par rep2)
shutil.copy (l, to)
copie_repertoire ("c:/original", "c:/backup")
14. Outils d’aide au développement 379
Cette tâche est en plus exécuté sur deux répertoires et on ne voudrait pas avoir deux programmes différents
alors que la tâche est la même. On souhaite pouvoir lancer le programme Python et lui spécifier les deux
répertoires, c’est-à-dire être capable de lancer le programme comme suit (voir également figure 14.1) :
Pour lancer un programme Python en ligne de commande, il faut écrire dans une fenêtre de commande ou
un programme d’extension bat les instructions suivantes séparées par des espaces :
Il faut maintenant récupérer ces paramètres au sein du programme Python grâce au module sys qui contient
une variable argv contenant la liste des paramètres de la ligne de commande. S’il y a deux paramètres,
cette liste argv contiendra trois éléments :
1. nom du programme
2. paramètre 1
3. paramètre 2
Le programme suivante utilise ce mécanisme avec le programme synchro contenant la fonction copie_repertoire :
# coding: latin-1
import glob
import shutil
def copie_repertoire (rep1, rep2) :
"""copie tous les fichiers d’un répertoire rep1 vers un autre rep2""" 5
li = glob.glob (rep1 + "/*.*")
for l in li :
to = l.replace (rep1, rep2) # nom du fichier copié
# (on remplace rep1 par rep2)
shutil.copy (l, to) 10
import sys
# sys.argv [0] --> nom du programme (ici, synchro.py)
rep1 = sys.argv [1] # récupération du premier paramètre
rep2 = sys.argv [2] # récupération du second paramètre 15
copie_repertoire (rep1, rep2)
Pour des besoins plus complexes, le module getopt propose des fonctions plus élaborées pour traiter les
paramètres de la ligne de commande.
14. Outils d’aide au développement 380
Un autre moyen est l’utilisation du module subprocess. Il permet d’attendre ou non la fin de l’exécution
de la ligne de commande.
import subprocess
args = ["scite.exe", "-open:fichier.txt"]
proc = subprocess.Popen(args)
retcode = proc.wait() # on attend la fin de l’exécution
if retcode!=0:
# ici, traiter les erreurs
Certains programmes en ligne de commande écrivent des informations dans la fenêtre d’exécution. C’est le
cas par exemple d’une instruction print dans un programme Python. lorsqu’on exécute un programme en
ligne de commande, ces informations sont perdues à moins de les récupérées. La première solution consiste
à ajouter >> output.txt en fin de ligne de commande de sorte que ces informations seront stockées dans
le fichier output.txt.
import subprocess
args = ["scite.exe", "-open:fichier.txt", ">>output.txt"]
proc = subprocess.Popen(args)
La seconde option consiste à rediriger la sortie vers le programme appelant toujours avec la fonction Popen.
(à venir)
installation
L’installation commence par l’exécution d’un installateur Windows 7 . Celle-ci ne pose pas de problème par-
ticulier. Une fois celle-ci terminée, de nouveaux menus apparaissent dans l’explorateur Windows lorsqu’on
clique sur le bouton droit de la souris sur un répertoire (voir figure 14.3). La création d’un repository
consiste tout d’abord à créer un répertoire que SVN utilisera comme répertoire principal. C’est un ré-
pertoire que seul SVN modifiera, le modifier soi-même n’est pas conseillé même si certains fichiers de
configuration peuvent l’être mais cela sort du cadre de ce livre.repository
Figure 14.3 : Après l’installation de SVN, de nouveaux menus apparaissent lors d’un clic droit de la souris
au-dessus d’un répertoire. La seconde image intervient lorsqu’on cherche à construire un repository, SVN demande
quel type d’archivage il doit choisir. Il est préférable de choisir le premier.
L’étape suivante consiste à créer un répertoire dans le repository. Il faut encore cliquer sur le clic droit de
la souris à l’endroit où a été créé le repository et choisir RepoBrowser. Ensuite, un nouveau clic droit de la
souris dans la fenêtre qui s’ouvre permet de créer un nouveau répertoire (choisir Add Folder ). Ce premier
répertoire porte souvent le nom de racine ou root ou encore trunk.création d’un répertoire
14.2.1.2 Utilisation
Le principe de SVN est de faire correspondre un répertoire du disque dur au premier répertoire du
repository. Ce sera le répertoire de travail. Le nom de l’opération est CheckOut (voir figure 14.5).CheckOut
SVN crée un répertoire si celui-ci n’existe pas et y insère également un répertoire caché .svn..svn Il contient
des informations interne à SVN. Il ne faut ni le supprimer ni le modifier.
Ajouter un fichier au répertoire racine du disque dur n’a pas d’effet sur le repository. Pour l’y inclure, il
faut utiliser l’opération Commit (voir la figure 14.6).Commit Il faut cocher la case associée au fichier créé,
ajouter un commentaire et cliquer sur Ok.
Un icône vert apparaît adossé au fichier créé (voir figure 14.7), il signifie que le fichier est identique dans le
repository et dans le répertoire associé. Sa modification entraîne l’apparition d’un icône rouge qui signifie
7. L’installation à usage personnel est simple, l’utilisation pour un usage multiple permet une utilisation personnelle mais
nécessite des étapes de configurations plus importantes et l’installation de trois logiciels (voir paragraphe 14.2.2.1).
14. Outils d’aide au développement 382
Figure 14.5 : Création d’une correspondance entre un répertoire du disque dur et un répertoire du repository.
que la copie du disque dur a été modifiée. La répétition de l’opération Commit permet de basculer les
modifications dans le repository et de faire apparaître l’icône vert.
Un nouveau clic droit sur le fichier modifié et la sélection du sous-menu Log permet d’afficher l’ensemble
des modifications intervenues sur ce fichier (voir figure 14.8).Log Un double-clic sur le fichier en question
permet de comparer deux versions consécutives, de voir les lignes ajoutées, supprimées et modifiées. Cette
fonctionnalité ne concerne que les fichiers textes.
Les autres menus ne seront pas détaillés, leur exploration est laissée au soin du lecteur. Toutefois, l’un
d’entre deux permet de récupérer une vieille version d’un fichier, antérieure aux dernières modifications.
De la même manière, si un fichier est supprimée par mégarde, celui-ci, à jamais présent dans le repository,
peut être récupéré à tout moment.
14.2.1.3 Configuration
Un clic droit sur le répertoire repository permet d’accéder à la partie Settings.Settings Le seul paramètre
décrit dans ce paragraphe est celui intitulé Global ignore patterns de la section General (voir figure 14.9).
14. Outils d’aide au développement 383
Il permet que certains fichiers ne soient pas pris en compte par SVN. Par exemple, lorsqu’on programme
en Python, de nombreux fichiers d’extension pyc sont générés. Il ne sert à rien de les conserver dans le
repository puisque ceux-ci seront de toutes façons recréés par Python si ceux-ci ont été supprimés ou si
le fichier source à leur origine a été modifié : ce sont des fichiers temporaires. Pour éviter que SVN n’en
tienne compte, il suffit d’ajouter l’expression *.pyc dans le champ Global ignore patterns.
Même si ce n’est pas souvent utile, il est possible de travailler avec plusieurs copies du repository. Chaque
copie doit être construite avec la fonctionnalité CheckOut. Une fois ces deux copies créés, il faut pouvoir
propager les modifcations d’une copie à l’autre. La fonctionnalité Commit appliquée à la copie modifiée
permet de transmettre des modifications au repository qui contient l’unique version officielle. Ensuite,
la fonctionnalité UpdateUpdate permet de mettre à jour en chargeant depuis le repository les dernières
modifications. Ce principe est illustré par le schéma de la figure 14.10.
Certains problèmes peuvent survenir lorsqu’on travaille sur les mêmes fichiers depuis les deux copies. Il faut
toujours rappatrier les dernières modifications enregistrées dans le repository : un Commit est toujours
précédé d’un Update. SVN est capable dans la plupart des cas de gérer les conflits. Seuls restent les cas
où les mêmes lignes du même fichier sont modifiées. Lors d’un Update, on peut récupérer une modification
qui rentre en conflit sur la version en cours. Il faut alors résoudre ce conflit puis indiquer à SVN que le
conflit est résolu (par l’intermédiaire des sous-menu accessibles avec la souris). SVN n’autorisera pas de
Commit sur un fichier pour lequel il subsiste un conflit irrésolu.Resolve
14. Outils d’aide au développement 384
Figure 14.9 : Configuration de SVN, il est possible de faire en sorte que SVN ne tienne pas compte de certains
fichiers dans le champ Global ignore patterns. S’il y a plusieurs filtres, ils doivent être séparés par des espaces.
Figure 14.10 : Lorsqu’on travaille avec plusieurs copies, la fonctionnalité Update fait les modifications depuis
le repository jusqu’à une copie (celle sur laquelle on applique l’opération Update. La fonctionnalité Commit est le
chemin inverse : on transmet les modifications depuis une copie vers le repository.
L’installation est beaucoup plus simple puisqu’il suffit d’installer Tortoire SVN (voir paragraphe 14.2.1.1,
page 381). Il suffira de faire une opération checkout depuis le repository http://localhost/svn/ http://127.
0.0.1/svn/ où 127.0.0.1 est l’adresse IP de la machine où est installée le repository.
Remarque 14.3 : connexion sur Apache impossible depuis une autre machine du réseau
Il est possible que l’accès à la machine où est installé Apache soit impossible depuis une autre machine. Il y
a deux causes possibles, la première est que les deux machines ne peuvent pas communiquer ensemble. Pour
vérifier cela, il suffit d’ouvrir une fenêtre de commande Windows et d’écrire depuis la machine distante :
ping 127.0.0.1
On suppose que 127.0.0.1 est l’adresse IP de la machine où figure Apache. Si le résultat est négatif, il faut
d’abord résoudre ce problème là. La seconde cause est le firewall (ou pare-feu) qui empêche les connexions
entrantes. Il est possible que ce soit le firewall de Windows qui soit utilisé, il est possible de le désactiver
depuis le panneau de contrôle ou le laisser activer mais autoriser une exception concernant le port qu’écoute
le serveur Apache.
Remarque 14.4 : Apache écoute un autre port que le port 80
Au cas où Apache écoute un autre port que le port 80, il faut le préciser lorsqu’on se connecte depuis une
machine distante. Si le port écouté est le port 81, il faudra écrire dans la barre d’adresse :
14. Outils d’aide au développement 386
http://127.0.0.1:81/
14.2.2.3 Backup
backup
Il peut être intéressant de sauvergarder régulièrement le repository au cas où la machine qui le contiendrait
tombe en panne. Cette option ne sera pas détaillée ici mais elle est disponible avec Apache qui autorise
l’exécution de tâches à intervalles réguliers.
SVN est plus souvent installé pour être utilisé à l’intérieur d’une entreprise et n’est accessible que depuis son
réseau local. Néanmoins, il n’est pas exclu de travailler depuis des postes distants via Internet. Comme pour
une utilisation interne, cela suppose qu’un ordinateur servira de repository, la différence viendra de l’URL
permettant de se connecter au seveur SVN qui pointera vers l’extérieur. Toutefois, cette configuration
soulève des problèmes de sécurité. Il faut que le serveur soit bien configuré pour ne laisser l’accès aux
fichiers partagés qu’à ceux qui y sont autorisés.
14.2.2.5 Limitations
Travailleur à plus d’une dizaine de personnes sur les mêmes fichiers peut parfois poser des problèmes.
Comme un Update précède toujours un Commit, il arrive, en période de travail intense, qu’un autre
programmeur réussisse à faire passer ses modifications entre la succession Update-Commit d’un autre
programmeur.
La gestion des fichiers SVN est dite centralisée car il existe une seule version officielle de tous les fichiers.
Cette configuration n’est pas toujours appropriée à la gestion du code source de projets open source
auxquels participent un grand nombre de programmeurs. La configuration employée est dite décentralisée,
ceci signifie qu’il n’existe plus de version officielle mais que chacun possède une version unique et identifiée.
Il n’y a plus d’opération Commit ou Update mais simplement l’opération Merge qui consiste à fusionner
les fichiers de deux versions. Avec ce schéma, le problème évoqué au paragraphe précédent n’existe plus.
Un module pysvn 9 permet de récupérer des informations sur un repository et de faire des modifications
par l’intermédiaire du langage Python. Grâce à cela, on pourrait plus facilement piloter SVN et faciliter
l’exécution de tâches récurrentes comme la mise à jour chaque nuit de tous les machines de chaque utilisa-
teur. Il faut vérifier lors de l’installation que le module pysvan installé est cohérent à la fois avec la version
de Python et la version de SVN installée.
Pour l’inclure dans un programme Python, il faut reproduire un appel en ligne de commande grâce à la
fonction system du module os :
import os
os.system (r"c:\python25\python c:\python25\lib\pydoc.py .\mon_programme.py")
Le nom d’un module standard doit apparaître sans extension, le nom d’un fichier peut apparaître sans
extension s’il se trouve dans le répertoire depuis lequel est lancé l’appel à pydoc, sinon, il faut mettre son
chemin complet avec au moins un symbol \. Le programme suivant crée automatique l’aide associée à une
liste de fichiers ou de modules, le résultat est illustré par la figure 14.11.
# coding: latin-1
"""genhelp.py : génération automatique de l’aide dans le répertoire d’exécution"""
import os
import sys
python_path = r"c:\python25\python" # constante 5
pydoc_path = r"c:\python25\lib\pydoc.py" # constante
class ClassExemple :
"""classe vide, exemple d’aide"""
def __init__ (self) : 10
"""constructeur"""
pass
def methode (self) :
"""unique méthode"""
return 1 15
if __name__ == "__main__" :
import sys 60
import os
[OPTIONS]
Compatibility=1.1
Full-text search=Yes
Contents file=help.hhc # fichier table des matières
Default Window=main 5
Default topic=index.html # page par défaut, première page à apparaître
Index file=help.hhk # fichier index
Language=0x40C French
Binary TOC=YES
Create CHI file=No 10
Title="GenHelp" # titre
[WINDOWS] # définition de la page principale, elle reprend des éléments cités ci-dessus
main="GenHelp", "help.hhc", "help.hhk" , "index.html", "index.html",,,,,0x23520,,0x387e,,,,,,,,0
15
[FILES] # ici commence la liste des fichiers à insérer dans le projet
genchm.html
genhelp.html
14. Outils d’aide au développement 390
os.html
sys.html 20
index.html
Les fichiers hhc et hhk sont définis avec la même syntaxe HTML et leur structure est une arborescence.
Ces deux fichiers commencent par l’en-tête suivant :
</BODY></HTML>
Chaque élément est composé d’un intitulé et d’une page HTML correspondante qui est facultative, ces
deux informations vont être insérés dans le code HTML qui suit :
<LI><OBJECT type="text/sitemap">
<param name="Name" value="intitule">
<param name="Local" value="page HTML correspondante">
<param name="ImageNumber" value="11"></OBJECT>
Les niveaux et sous-niveaux sont définis par l’instruction < UL > qui permet d’ouvrir une section de niveau
inférieure et < /UL > de la fermer. Appliqué à un exemple d’indexe, cela donne :
La dernière étape est de construire un programme Python qui construit automatiquement les trois fichiers
nécessaires à HTML Help Workshop. L’exemple suivant est assez simple, il pourrait être étoffé notamment
en ajoutant à l’indexe le nom des classes et de leur méthodes ce qu’il ne fait pas.
14. Outils d’aide au développement 391
# coding: latin-1
"""genchm.py : génération automatique du fichier d’aide chm"""
import genhelp
import os
htmlworkshop_path = "\"c:\\Program Files\\HTML Help Workshop\\hhc.exe\"" 5
def g (s) :
"""ajoute des guillements autour d’une chaîne de caractères"""
return "\"" + s + "\""
10
def genhhp (files, premierepage, titre, \
hhp = "help.hhp", hhc = "help.hhc", hhk = "help.hhk") :
"""génère le fichier hpp définissant le fichier d’aide chm,
files est la liste des fichiers HTML
premierepage est la page à afficher en premier 15
titre est le titre de l’aide"""
proj = """[OPTIONS]
Compatibility=1.1
Full-text search=Yes
Contents file=""" + hhc + """ 20
Default Window=main
Default topic=""" + premierepage + """
Index file=""" + hhk + """
Language=0x40C French
Binary TOC=YES 25
Create CHI file=No
Title=""" + g (titre) + """
[WINDOWS]
main=""" + g (titre) + """, """ + g (hhc) + """, """ + g (hhk) + """ , """ + \ 30
g (premierepage) + """, """ + g (premierepage) + """,,,,,0x23520,,0x387e,,,,,,,,0
[FILES]
"""
for f in files : 35
proj += f + "\n"
if __name__ == "__main__" :
files = [".\\genchm.py", ".\\genhelp.py", "os", "sys"]
res = genhelp.genhelp (files) 115
print res # [’genchm.html’, ’genhelp.html’, ’os.html’, ’sys.html’]
genchm (res, "GenHelp")
14.3.3 Prolongements
La documentation générée par le module pydoc n’est pas très agréable à lire. D’autres outils permettent
d’améliorer la qualité des pages automatiquement générées et de générer d’autres formats que des pages
HTML. Le premier est epydoc 11 qui fonctionne de façon similaire à pydoc mais déjà plus agréable. Le
plus répandu est Doxygen 12 car il traite la plupart des langages informatique. Son utilisation fait appel à
d’autres outils tels que Graphviz 13 , Latex 14 , ou encore le module doxypy 15 .
Toutes ces actions sont définies par un fichier texte d’extension iss. Celui-ci commence par un en-tête
définissant les options générales.
11. http://epydoc.sourceforge.net/
12. http://www.stack.nl/~dimitri/doxygen/
13. http://www.graphviz.org/, cet outil sert à représenter des graphes.
14. Latex n’est pas un outil en soi, c’est un langage permettant de créer des documents à usage le plus souvent scientifique.
Miktex fournit un compilateur sur Windows (http://miktex.org/).
15. http://code.foosel.org/doxypy
16. http://www.jrsoftware.org/isinfo.php
14. Outils d’aide au développement 394
Ensuite, il faut définir les quatre étapes définies précédemment. Les deux premières seront regroupées en
une seule section [Files] qui indiquera en même temps l’emplacement d’un fichier chez le concepteur et sa
destination chez l’utilisateur.
[Files]
Source: "chemin_concepteur"; DestDir: "chemin_utilisateur" ;
Source: "..\hal_dll.dll"; DestDir: "{app}\hal_python" ; autre exemple
La partie exécution permet d’exécuter certaines opérations comme des opérations sur les fichiers en lignes
de commande, la compilation de fichiers, l’installation d’un module Python ou encore l’installation d’autres
applications. Par exemple, les lignes suivantes permettent de lancer l’installation du langage Python puis
de le supprimer (suppression du fichier python-2.5.2.msi).
[Run]
Filename: "{win}\system32\msiexec.exe"; Parameters: "/i ""{app}\python-2.5.2.msi"" /qr ALLUSERS=1";
Filename: "{cmd}"; Parameters: "/c del python-2.5.2.msi"; WorkingDir: "{app}";
La section [Icons] permet d’ajouter un sous-menu dans le menu Démarrer / Tous les programmes. Le nom
de ce sous-menu est précisé dans la section [Setup].
[Icons]
; liens vers diverses applications
Name: "{group}\PyScripter"; Filename: "{app}\PyScripter.exe"; WorkingDir: "{app}"
Name: "{group}\PyScripter Help"; Filename: "{app}\pyscripter.chm"; WorkingDir: "{app}"
Name: "{group}\Help"; Filename: "{app}\hal_python.chm"; WorkingDir: "{app}"
Name: "{group}\smallest sample with PyScripter"; Filename: "{app}\small_sample.bat"; WorkingDir: "{app}"
[InternetShortcut]
URL=http://www.xavierdupre.fr/hal_python/hal_python_help_html/index.html
La section [Tasks] permet de créer un icône sur le bureau relié au sous-menu définit auparavant.
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}";
Flags: checkedonce
14. Outils d’aide au développement 395
Cette section permet de modifier les clés de registres de Windows. Cela permet de mémoriser le numéro
de version ou encore le chemin utilisé lors d’une installation précédente.
[Registry]
Root: HKLM; Subkey: "SOFTWARE\HalPython\InstallPath"; ValueType: string; ValueName: "";
ValueData: "{app}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "SOFTWARE\HalPython\Version"; ValueType: string;
ValueName: ""; ValueData: "1.5.1162"; Flags: uninsdeletekey
Enfin, si certaines opérations à effectuer sont trop complexes pour être décrites par ces sections, il est
possible de coder des fonctions, d’ajouter des boîtes de dialogues permettant de choisir des options. Cette
dernière possibilité ne sera pas décrite ici.
14.4.2 Examples
14.4.2.1 Exemple 1 : installateur pour un module Python
L’exemple suivant définit un installateur pour un module Python construit avec Boost Python. Il inclut
également l’installateur de Python 2.5 ainsi qu’un éditeur de texte. De ce fait, cet unique installateur inclut
tout ce qu’il faut pour pouvoir se servir du module. Le résultat sera un sous-menu dans le menu Démarrer
/ Tous les programmes incluant différents liens dont un ouvrant l’éditeur de texte sur un exemple de
programme Python utilisant le module installé. Certaines fonctions ont été ajoutés afin que le langage
Python 2.5 ne soit pas installé si celui-ci l’a déjà été.
Tout texte après un point virgule est considéré comme un commentaire, les instructions des sections [Run],
[Tasks], [Registry] sont écrites sur deux lignes pour des raisons de présentation mais doivent l’être sur une
seule.
[Code]
; clés de registre 40
; mémorise le répertoire d’installation
; et le numéro de version
[Registry]
Root: HKLM; Subkey: "SOFTWARE\HalPython\InstallPath"; ValueType: string; ValueName: "";
ValueData: "{app}"; Flags: uninsdeletekey 45
Root: HKLM; Subkey: "SOFTWARE\HalPython\Version"; ValueType: string; ValueName: "";
ValueData: "1.5.1162"; Flags: uninsdeletekey
; fichiers
[Files] 55
; le module en question
Source: "..\hal_dll.dll"; DestDir: "{app}\hal_python";
Source: "..\hal_dll_ext.dll"; DestDir: "{app}\hal_python";
Source: "..\hal_dll_model.dll"; DestDir: "{app}\hal_python";
Source: "..\hal_python.dll"; DestDir: "{app}\hal_python"; 60
Source: "..\wxwindows_old.dll"; DestDir: "{app}\hal_python";
Source: "..\boost_python.dll"; DestDir: "{app}\hal_python";
Source: "..\_graphviz_draw.exe"; DestDir: "{app}\hal_python";
Source: "..\_hal_script.exe"; DestDir: "{app}\hal_python";
Source: "..\hal_python.py"; DestDir: "{app}\hal_python"; 65
Source: "..\install\__init__.py"; DestDir: "{app}\hal_python";
Source: "..\install\setup.py"; DestDir: "{app}";
Source: "..\_test\temp\hal_python.chm"; DestDir: "{app}";
[Run]
; installe Python 2.5 si GetPythonPathExec retourne un résultat non vide
; passe à l’instruction suivante sinon
Filename: "{code:GetPythonPathExec}"; Parameters: "/i ""{app}\python-2.5.2.msi"" /qr ALLUSERS=1"; 90
StatusMsg: "Installing Python 2.5..."; Flags: skipifdoesntexist
14. Outils d’aide au développement 397
PyScripter.exe sample.py
L’installateur suivant assure la mise à jour du module. Il évite ainsi d’inclure dans le setup de gros fichiers
comme l’installateur de Pythonou l’éditeur de texte. Ce fichier de description est presque identique au
précédent, les parties ne concernant pas le module ont été supprimées et une fonction InitalizeSetup a
été ajoutée. Cette dernière est appelée au démarrage de l’installateur, elle vérifie que tous les éléments sont
nécessaires au bon fonctionnament du module. En particulier, si Python 2.5 n’est pas installé, l’installateur
affiche un message puis abandonne.
[Code]
function GetPythonPath(Param: String): String;
begin
Result := ’’; 20
Result := ExpandConstant(’{reg:HKLM\Software\Python\PythonCore\2.5\InstallPath,|}’);
if Result <> ’’ then
Result := Result + ’\pythonw.exe’;
end;
25
function GetHalPythonPath(Param: String): String;
begin
Result := ’’;
Result := ExpandConstant (’{reg:HKLM\Software\HalPython\InstallPath,|}’) ;
end; 30
the complete Setup with Python 2.5 included instead of updating.’, mbError, MB_OK) ;
Result := False ; 40
end else if GetHalPythonPath (’’) = ’’ then begin
MsgBox(’HalPython for Python 2.5 has not been installed.
You should download the complete Setup with Python 2.5 included instead of updating.’,
mbError, MB_OK) ;
Result := False ; 45
end
end;
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; 50
Flags: checkedonce
[Files]
Source: "..\hal_dll.dll"; DestDir: "{app}\hal_python";
Source: "..\hal_dll_ext.dll"; DestDir: "{app}\hal_python"; 55
Source: "..\hal_dll_model.dll"; DestDir: "{app}\hal_python";
Source: "..\hal_python.dll"; DestDir: "{app}\hal_python";
Source: "..\wxwindows_old.dll"; DestDir: "{app}\hal_python";
Source: "..\boost_python.dll"; DestDir: "{app}\hal_python";
Source: "..\_graphviz_draw.exe"; DestDir: "{app}\hal_python"; 60
Source: "..\_hal_script.exe"; DestDir: "{app}\hal_python";
Source: "..\hal_python.py"; DestDir: "{app}\hal_python";
Source: "..\install\__init__.py"; DestDir: "{app}\hal_python";
Source: "..\install\setup.py"; DestDir: "{app}";
Source: "hal_python.url"; DestDir: "{app}"; 65
Source: "small_sample.bat"; DestDir: "{app}";
Source: "sample.py"; DestDir: "{app}";
Source: "..\_test\temp\hal_python.chm"; DestDir: "{app}";
[Registry] 70
Root: HKLM; Subkey: "SOFTWARE\HalPython\InstallPath"; ValueType: string; ValueName: "";
ValueData: "{app}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "SOFTWARE\HalPython\Version"; ValueType: string; ValueName: "";
ValueData: "1.5.1162"; Flags: uninsdeletekey
75
[Icons]
Name: "{group}\PyScripter"; Filename: "{app}\PyScripter.exe"; WorkingDir: "{app}"
Name: "{group}\PyScripter Help"; Filename: "{app}\pyscripter.chm"; WorkingDir: "{app}"
Name: "{group}\Help"; Filename: "{app}\hal_python.chm"; WorkingDir: "{app}"
Name: "{group}\smallest sample with PyScripter"; Filename: "{app}\small_sample.bat"; WorkingDir: "{app}" 80
Name: "{group}\Website"; Filename: "{app}\hal_python.url"
Name: "{group}\uninstall"; Filename: "{uninstallexe}"; WorkingDir: "{app}"
[Run]
Filename: "{code:GetPythonPath}"; Parameters:"setup.py install"; WorkingDir: "{app}"; 85
StatusMsg: "Installing HalPython for Python 2.5..."
Le code suivant permet d’insérer un raccourci dans le sous-menu inséré dans Démarrer / Tous les pro-
grammes. La première partie appelle la fonction GetLnkSciTE qui crée un raccourci scite.exe sample.y. La
seconde partie consiste à insérer une ligne permettant d’ajouter le raccourci au sous-menu.
[Code]
function GetLnkSciTE(Param:String): String;
begin
Result := CreateShellLink(ExpandConstant(’{app}\SciTE sample.lnk’),
’Opens SciTE with a sample’,
ExpandConstant(’{app}\wscite\scite.exe’),
14. Outils d’aide au développement 399
ExpandConstant(’"{app}\sample.py"’),
ExpandConstant(’{app}’),
’’,
0,
SW_SHOWNORMAL);
end;
[Icons]
Name: "{group}\smallest sample with SciTE"; Filename: "{code:GetLnkSciTE}"; WorkingDir: "{app}"
Il n’est pas évident d’apprendre encore un nouveau langage tel que celui utilisé par InnoSetup. Pour des
tâches plus compliquées rien n’empêche d’exécuter un programme Python récupérant les paramètres dont
il a besoin sur sa ligne de commande. L’exemple suivant permet de remplacer dans un fichier installé une
chaîne de caractères par une autre. Le programme prend trois paramètres sur sa ligne de commande : le
fichier à modifier, la chaîne de caractères à remplacer et la chaînes qui la remplace.
if __name__ == "__main__" :
import sys
file = sys.argv [1] # fichier à modifier
s1 = sys.argv [2] # chaîne à remplacer
s2 = sys.argv [3] # nouvelle chaîne
replace_string (file, s1, s2)
[Code]
; retourne le chemin du programme pythonw.exe
function GetPythonPathW(Param: String): String;
begin
Result := ’’;
Result := ExpandConstant(’{reg:HKLM\Software\Python\PythonCore\2.5\InstallPath,|}’);
if Result <> ’’ then
Result := Result + ’\pythonw’;
end;
[Run]
; exécution de la ligne de commande
Filename: "{code:GetPythonPath}"; Parameters:"{code:GetReplaceConf}"; WorkingDir: "{app}";
14. Outils d’aide au développement 400
14.5.1 Installation
installation
La première étape consiste à installer un serveur comme le serveur Apache, cette installation est identique
à celle du paragraphe 14.9 (page 427). L’étape de configuration d’Apache requise pour MoinMoin pourra
s’ajouter à celle effectuée pour SVN.
17. http://wikipython.flibuste.net/
18. CGI : Common Gateway Interface
14. Outils d’aide au développement 401
La seconde étape consiste à installer MoinMoin 19 . Une fois téléchargé puis décompressé le fichier MoinMoin1.6.3 tar.gz 20 ,
il reste à l’installer comme un module Python en tapant les instructions suivantes en ligne de commande :
C:
cd c:\TEMP\moin-1.6.3
C:\Python25\python setup.py install --record=install.log
La dernière étape de l’installation consiste à créer un répertoire dans lequel seront placés toutes les in-
formations relatives au Wiki. Cela signifie qu’il est possible de créer plusieurs Wiki. Les instructions qui
suivent sont à exécuter en ligne de commande, elles supposent que le Wiki devra être créer dans le réper-
toire c : \Moin et que Python a été installé dans le répertoire c : \Python25. Dans le cas contraire, il suffit
de les adapter.
cd C:\
md Moin
md Moin\mywiki
md Moin\mywiki\data
md Moin\mywiki\underlay
cd c:\Python25\share\moin
xcopy data C:\Moin\mywiki\data /E
xcopy underlay C:\Moin\mywiki\underlay /E
copy config\*.* C:\Moin\mywiki\*.*
copy server\*.* C:\Moin\mywiki\*.*
Il reste à configurer Apache et MoinMoin pour que les deux applications communiquent. Par la suite, on
supposera que le Wiki est créé dans le répertoire c : \Moin et que Python a été installé dans le répertoire
c : \Python25. Dans le cas contraire, il suffit d’adapter ces lignes.
#!/usr/env/bin python
par la ligne :
#!c:/Python25/python.exe
Cette ligne spécifie om l’interpréteur Python est disponible sur la machine du serveur.
Remarque 14.7 : pour aller plus loin
Il est possible, si vous êtes courageux, d’optimiser MoinMoin en utilisant d’autres types de CGI 22 , no-
tamment les FastCGI dont l’exécution est plus rapide qu’un CGI classique. Dans ce cas, il faudra penser
à répéter cette manœuvre pour les fichiers du type moin.∗.
Ce fichier définit l’apparence et les droits d’accès de MoinMoin. Après l’installation, ce fichier contient
de nombreuses lignes commentées concernant les options principales. Tout d’abord, deux options qui
permettent à MoinMoin de définir l’emplacement du Wiki :
data_dir = r’C:\Moin\mywiki\data’
data_underlay_dir = r’C:\Moin\mywiki\underlay’
Ensuite, il est préférable qu’une personne non identifiée ne puisse pas défaut ni attacher une pièce jointe,
ni détruire ou renommer une page, pour cela il faut ajouter la ligne :
On définit ensuite un "superuser", le seul ayant tous les droits sur MoinMoin, le nom d’admim 23 lui est
donné (ce n’est pas une obligation) :
superuser = [u"admin", ]
Les droits utilisateurs sont définis par défaut par la ligne suivante. Par défaut, le "superuser" peut tout
faire :
acl_rights_before = u"admin:read,write,delete,revert,admin"
sitename = u’NomSite’
page_front_page = u"FirstPage"
language_default = ’fr’
Le Wiki est maintenant en état de fonctionner et vous devriez avoir une page correspondant à celle de la
figure 14.13.
22. Le rôle des CGI est brièvement expliqué au paragraphe 14.5.
23. pour administrator
14. Outils d’aide au développement 403
Figure 14.13 : Première page du Wiki une fois configuré. Son contenu est vide car ce dernier n’a pas encore été
précisé. C’est aussi ce qui s’affiche lorsqu’une page n’existe pas encore mais que le Wiki y fait référence.
Il est utile de restreindre maintenant les droits de chaque utilisateurs. La première chose à faire est de
créer un utilisateurs administrateurs. Pour cela il faut cliquer sur Connexion pour atteindre la page de la
figure 14.14.
Figure 14.14 : Page de connexion, il faut rentrer un login et un mot de passe ou définir un nouvel utilisateur en
cliquant sur UserPreferences si vous n’avez pas encore de compte. Il est déconseillé d’utiliser des accents, ils peuvent
provoquer certains comportements erratiques.
Une fois qu’un utilisateur administrateur est créé, il faut insérer la ligne suivante dans le fichier wikiconfig.py.
Elle permet de spécifier que seul l’administrateur (ici dupre) a tous les droits, tout autre utilisateur n’a
que le droit de lire les pages sans pouvoir les modifier. Au cas om cet utilisateur essaierait, il tomberait
sur la page de la figure 14.15.
MoinMoin offre la possibilité de créer des groupes d’utilisateurs, de se connecter à un annuaire de type
14. Outils d’aide au développement 404
Figure 14.15 : Page qui apparaît lorsqu’un utilisateur essaye de modifier une page mais qu’il n’en a pas le droit.
LDAP ou ActiveDirectory. Le site de MoinMoin est assez complet et décrit tout ce qu’il est possible de faire
avec ce Wiki 24 Les étapes de configuration présentées dans ce livre sont assez sommaires et conviennent
pour une utilisation au sein d’une entreprise de quelques dizaines d’employés tout au plus et sur un réseau
interne. Pour des réseaux de plus en grandes envergures, il est conseillé de consulter le site de MoinMoin,
voire d’opter pour un Wiki différent.
14.5.3.4 Apparence
Il est possible de modifier l’apparence du site Wiki, la variable theme_default permet de définir l’aspect
général. Il existe trois thèmes prédéfinis 0 classic0 , 0 modern0 et 0 rightsidebar0 .
theme_default = ’modern’
Il est aussi possible de développer son propre thème 25 mais cela requiert un peu de temps ou aller télé-
charger un thème existant 26 en prenant toutefois soin de vérifier que ce thème fonctionne avec la version
de MoinMoin installée.
Le Wiki est maintenant configuré, il reste encore savoir comment écrire quelques pages à commencer par
la première qui est à pour le moment l’allure de celle de la figure 14.13. Il suffit de cliquer sur Create an
empty page pour afficher l’écran de la figure 14.16. Le Wiki ne manipule que du texte, toute image doit
être traitée comme une pièce jointe. La syntaxe Wiki, simple, permet tout de même d’afficher des titres,
des tableaux, des listes, une table des matières 27 . La figure 14.17 donne un court exemple de qu’il est
possible de faire avec MoinMoin.
24. Tout est y est écrit en anglais exclusivement.
25. voir à l’adresse http://moinmo.in/MoinDev/ThemeDevelopment
26. voir à l’adresse http://moinmoin.wikiwikiweb.de/ThemeMarket
27. voir un résumé en français de la syntaxe à l’adresse http://moinmo.in/HelpOnMoinWikiSyntax.
14. Outils d’aide au développement 405
Figure 14.16 : Page qui apparaît lorsqu’un utilisateur essaye de modifie une page.
<<TableOfContents(2)>>
= Titre 1 =
http://www.xavierdupre.fr/mywiki/XavierDupre
== Titre 1 niveau 2 ==
= Titre 2 =
une ligne
----
Figure 14.17 : D’un côté, la syntaxe MoinMoin, de l’autre le rendu dans un navigateur.
On peut considérer deux types de liens à l’intérieur d’un Wiki : les liens externes qui sont une simple
adresse internet et les liens internes qui font référence à une page du Wiki. Pour ajouter un lien externe
à une page, il suffit de l’incoroporer au texte de la page ou entre double crochets si on veut que ce lien
apparaisse avec un autre texte.
http://www.xavierdupre.fr/mywiki/XavierDupre
[[http://www.xavierdupre.fr/mywiki/XavierDupre|wiki de Xavier Dupré]]
Les liens internes sont des références à des pages déjà écrites ou des pages à créer si le Wiki détecte que
ce lien interne n’existe pas. Un lien interne est un mot contenant deux ou plusieurs majuscule comme
LienInterne ou une phrase entre crochets comme [[lien interne]].
14. Outils d’aide au développement 406
L’ajout d’une pièce jointe s’effectue en cliquant sur l’intitulé Pièce jointe. Il suffit ensuite d’ajouter le texte
attachment : nom_de_la_piece_jointe pour y donner accès à tous les lecteurs de la page.
Il est possible d’attribuer des droits spécifiques à une page. Le paramètre acl_rights_before de wikiconfig.py
(voir paragraphe 14.5.3.2) définit les droits par défaut de chaque page du Wiki. Il est néanmoins possible
de restreindre l’accès à certaines pages du Wiki. Par exemple, par défaut, tout le monde peut lire chaque
page mais on voudrait que chaque utilisateurs est un espace réservé inaccessible à quiconque autre que lui.
Dans ce cas, on ajoutera au début du texte de cette page le texte suivant :
#acl dupre:read,write
Dans ce cas, personne d’autre que l’utilisateur dupre ne pourra avoir accès ni en lecture ni en écriture à la
page où est insérée cette ligne. Cette ligne écrase les droits par défaut. Le texte de la page peut ensuite
être écrit comme n’importe quelle autre page.
configuration CGI MoinMoin fonctionne à base de script CGI : ce sont des pages HTML entièrement
produites par des scripts écrits en Python. Exécuté par le ssrveur Apache, tout instruction print d’un script
CGI sera automatiquement envoyé au navigateur. Les lignes qui suivent donne un exemple d’introduction
à cette technique. Il commence par la configuration du serveur Apache auquel on indique un nouveau
répertoire dans lequel seront placés des script CGI écrits en Python.
Dans ce répertoire, on placera des pages HTML et des scripts CGI. Pour éviter de longues adresses URL,
on définit deux alias différents mais dont la cible est le même répertoire python_cgi. Apache n’accepte
pas que deux directives Alias et ScriptAlias portent le même nom.
#!c:/Python25/python.exe
# ligne 1 : emplacement de python (obligatoire)
# ligne 2 : la page ne contiendra que du texte
print "Content-Type: text/plain\n\n"
# premier message au navigateur
print "premier message\n"
Il faut maintenant créer une page HTML qui pointe vers ce script :
<html><body>
<h1> Lien vers un script CGI écrit en Python </h1>
<a href="/pycgi/hello.cgi">hello.cgi</a>
</body></html>
14. Outils d’aide au développement 407
http://localhost/pycgih/index.html
La page affichée fait apparaître un titre suivi d’un unique lien vers le script CGI. Cliquer dessus affiche le
message souhaité.
Remarque 14.10 : erreur, première ligne #!c : /Python25/python.exe
Il ne faut pas oublier cette première ligne qui indique à Apache où trouver l’interpréteur Python qui
lui permettra de comprendre le script. Dans le cas contraire, la page aboutit à une erreur. Si cette erreur
subsiste toujours même après l’ajout de cette ligne, cela signifie que l’interpréteur Python n’est pas installé
à cet endroit où que le script a produit une erreur. Dans ce cas, il peut être utile de remplacer l’extension
cgi par py puis d’exécuter le script CGI comme s’il était un programe Python.
plain
Le paragraphe précédent a présenté un script CGI permettant d’afficher du texte générée par un script
Python. Pour mettre en forme ce texte, il faut utiliser la syntaxe HTML et préciser depuis le script que
le format de sortie du script est bien du HTML. Pour cela, il faut modifier une ligne du script CGI, html
remplace plain :
Par exemple :
#!c:/Python25/python.exe
# coding: cp1252
print "Content-Type: text/html\n\n"
# premier message au navigateur,
# les passages à la ligne \n sont sans effet à l’affichage
print "<html><body>\n"
print "<b>premier message en gras</b>\n"
print "</body></html>"
Lorsqu’une erreur survient, le texte affiché correspond au traitement effectué par le script CGI jusqu’à ce
que l’erreur se soit produite. Pour pouvoir remonter jusqu’à la cause de l’erreur, il est possible d’attraper
l’exception engendré par le script et d’afficher les informations relatives à cette erreur. Le script suivant
génère volontairement une erreur en exécutant une instruction inconnue stop. L’exception est alors attrapée
puis on utilise le module traceback pour afficher la ligne et le type d’erreur généré.
#!c:/Python25/python.exe
# coding: cp1252
print "Content-Type: text/html\n\n"
print "<html><body>\n"
try:
print "<b>premier message en gras</b>\n"
print "<br>déclenchement d’une erreur (instruction inconnue)<br>"
14. Outils d’aide au développement 408
stop
except:
import sys
import traceback
print "<pre>"
traceback.print_exc(file=sys.stdout)
print "</pre>"
print "</body></html>"
Il peut être utile de décrire très succintement la syntaxe HTML. Ce langage est un langage à balise,
c’est-à-dire que toute information est insérée au sein d’une syntaxe qui ressemble à :
Les balises peuvent être écrites avec des minuscules ou des majuscules, le langage HTML ne fait pas la
différence. Toute page HTML s’écrit de la forme :
<html>
<head>
.... entête de la page, n’est pas affiché, il est possible d’y définir
.... des feuilles de styles pour un affichage amélioré
.... ou la définition de nouvelles balises
.... la ligne
<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
.... permet de définir un jeu de caractères incluant les accents français
</head>
<body>
.... contenu de la page
</body>
</html>
Le contenu de la page doit figurer entre les balises < body > et < /body >. La table 14.1 regroupe les balises
de style les plus utilisées et la table 14.2 des balises courantes de présentations. Les balises citées dans ces
tables permettent de présenter des résultats au format HTML. Pour des présentations plus sophistiquées,
une recherche sur Internet donnera la plupart du temps une réponse 28 . Le langage étant en perpétuelle
évolution, les possibilités offertes ne cessent de s’étendre.
Les programmes Python sont capables d’accepter des paramètres lorsqu’ils sont exécutés en ligne de
commande. Ce mécanisme existe également en ce qui concernent les pages HTML. Cela consiste à faire
un URL d’un point d’interrogation puis d’une suite de couple param = valeur séparé par le symbole &.
28. voir également http://fr.wikipedia.org/wiki/HTML
14. Outils d’aide au développement 409
balises effet
<! − − −−> commentaires, ces informations ne sont pas affi-
chées par le navigateur
< h1 >< /h1 > titre de premier niveau
< h2 >< /h2 > titre de second niveau
... ...
< br > aller à la ligne
< hr > tracer une ligne horizontale
< b >< /b > gras
< i >< /i > italique
< ins >< /ins > souligné
< del >< /del > barré
Table 14.1 : Balises HTML relatives à la mise en forme de caractères.
balises effet
< ahref = ”url” > texte < /a > lien, url désigne l’hyperlien, le texte à afficher
par le navigateur est situé entre les balises
< aname = ”ancre” >< /a > poser une ancre
< ahref = ”#ancre” > texte < /a > lien vers une ancre à l’intérieur d’une page
< imgsrc = ”imagename” >< /img > lien vers une image, les attributs width et height
pour modifier la taille de l’image lorsqu’elle est
affichée.
< p >< /p > paragraphe
< table >< /table > insertion d’un tableau, l’attribut border
permet de préciser l’épaisseur des bords.
< tableborder = ”1” >
< tr >< /tr > insertion d’une ligne dans un tableau
< td >< /td > insertion d’une case dans un tableau
< td >< /td > insertion d’une case dans un tableau
< ul >< /ul > création d’une liste
< li > insertion d’un intitulé au sein d’une liste
Table 14.2 : Balises HTML relatives à la mise en page. Le nom du fichier suit le mot src dans la syntaxe,
< imgsrc = ”...” >< /img >, src est appelé un attribut. les valeurs associées aux attributs sont toujours entourées
de guillemets. Lorsqu’il n’y a rien entre deux balises, l’écriture peut être écourtée en < imgsrc = ”...”/ >.
http://URL?param1=valeur1¶m2=valeur2&...
#!c:/Python25/python.exe
# coding: cp1252
print "Content-Type: text/html\n\n"
print "<html><body>\n"
import cgi
par = cgi.FieldStorage()
if len (par) == 0 :
print "<b> aucun paramètre à cette page </b>"
else :
print "<b>liste des paramètres </b><br>"
print """<table border="1">"""
for p in par :
print "<tr>"
14. Outils d’aide au développement 410
14.5.6.6 Formulaires
Le langages HTML donne la possibilité de construire des formulaires puis de récupérer les données saisies
par l’internaute. Bien souvent, la page qui affiche le formulaire est un script CGI capable de traiter les
données qu’il s’envoie lui-même. L’utilisateur sélectionne l’adresse :
http://localhost/pycgi/pyform.cgi
Puis le formulaire, lorsque l’utilisateur appuie sur Envoyer, appelle l’URL suivant :
http://localhost/pycgi/pyform.cgi?nom=dupre&prenom=xavier
C’est ce que fait le script CGI suivant. Il affiche d’abord le formulaire de la figure 14.18.
Figure 14.18 : Exemple de formulaire au format HTML engendré par un script CGI écrit en Python.
#!c:/Python25/python.exe
# coding: cp1252
print "Content-Type: text/html\n\n"
print "<html><body>\n"
def print_form () :
# création du formulaire avec deux champs nom et et prenom
print """
<FORM action="/pycgi/pyform.cgi" method=post>
<P>
<LABEL for="nom">Nom : </LABEL> <INPUT type="text" name="nom"><BR>
<LABEL for="prenom">Prénom : </LABEL> <INPUT type="text" name="prenom"><BR>
<INPUT type=submit value="Envoyer"> <INPUT type=reset value="Annuler">
</P>
</FORM>
"""
import cgi
par = cgi.FieldStorage ()
if len (par) == 0 :
# s’il n’y a aucun paramètre, on affiche le formulaire
print_form ()
else :
# sinon, cela signifie que le formulaire a rappelé le script CGI
# avec des paramètres et le traitement suit une direction différente
# ici, cela consiste à afficher les informations reçues en gras
nom = par.getvalue ("nom")
prenom = par.getvalue ("prenom")
print "Votre nom est <b>", nom, "</b> et votre prénom <b>", prenom, "</b>"
print "</html></body>\n"
14. Outils d’aide au développement 411
14.6 SQL
Le langage SQL est devenu incontournable pour quiconque souhaite stocker, consulter de grandes quantité
de données. Les paragraphes qui suivent ne feront que présenter sommairement le langage SQL, il s’agit
ici de l’utiliser via un programme Python plutôt que de le maîtriser.
Cette table, qu’on appelle ELEVE, sera représentée sous la forme d’un tableau. Le numéro permet de désigner
chaque élève plus facilement dans le système informatique que par son nom et son prénom, il permet aussi
de distinguer portant même nom et prénom. Ce numéro porte souvent le nom d’index.
Le pays est entier, on aurait pu choisir une chaîne de caractères également. Toutefois, il est possible de
définir une seconde table qu’on appelle PAYS avec la liste des pays et le numéro qui leur sont attribués.
pays codepays
France 33
Royaume-Uni 44
Lorsqu’on cherche des élèves d’une certaine nationalité sans connaître son code, il suffit de chercher d’abord
le code dans la table PAYS puis de chercher les élèves ayant ce code dans la table ELEVE. En langage SQL 29 ,
la première requête s’ecrit :
SELECT codepays
FROM PAYS
WHERE pays = ’France’
La seconde requête utilise les résultats de la première, en lanage SQL, cela donne :
SELECT num,nom,prenom
FROM ELEVE
WHERE codepays IN (
SELECT codepays
FROM PAYS
WHERE pays = ’France’
)
Si on relie les colonnes portant la même information, on aboutit au graphe de la figure 14.19. Ces liens
illustrent le concept de base de données relationnelle. Il serait possible de représenter toutes ces données
sous la forme d’un unique tableau mais il aurait beaucoup de colonnes et autant de lignes que de notes.
Autrement dit, les mêmes informations sont présentes en de nombreux endroits. Pour éviter la redondance
d’un tel système, il faut scinder ce tableau en autant de tables que nécessaire pour faire disparaître la la
redondance.
Figure 14.19 : Graphe illustrant les bases de données relationnelles, les champs reliés entre eux ont le même sens.
Un numéro d’élève désigne aussi bien un élève dans la table ELEVE que dans la table NOTE. Si on connaît le numéro
d’un élève, on connaît également toutes les autres informations le concernant, c’est pourquoi, dans la table NOTE,
on inscrit que son numéro. Toute autre information serait superflue.
Le langage SQL permet de recherche des informations dans cet ensemble de tables. Par exemple, la requête
suivante permet de construire la liste des élèves suivi de la moyenne de leurs note en français :
Le chapitre suivant montrera comment créer ces différentes tables, y insérer des lignes (ou enregistrements),
lancer des requêtes.
30. http://www.sqlite.org/
14. Outils d’aide au développement 413
date date,
adresse varchar (100),
codepays integer,
classe integer)
Le mot-clé primary key permet d’indiquer au langage SQL qu’il peut utiliser ce champ comme un index :
il n’y aura jamais deux élèves avec des numéros identiques. C’est aussi une clé qui est attribuée automa-
tiquement par le langage SQL. Si une table contient un tel champ, sa consultation est plus rapide. Il ne
reste plus qu’à exécuter cette requête grâce au module sqlite3 :
cur.execute (""" 5
CREATE TABLE ELEVE (
num integer primary key,
nom varchar (30),
prenom varchar (30),
date date, 10
adresse varchar (100),
codepays integer,
classe integer)
""")
La première instruction permet d’ouvrir une connexion à la base de données, d’indiquer qu’on s’en sert.
La seconde instruction permet de créer ce qu’on appelle un curseur. Cela permet d’avoir un accès à la base
qui autorise des accès simultanés en nombre limité. La troisième instruction exécute la requête proprement
dite.
cur.execute ("CREATE TABLE NOTE (nume integer, numm integer, note double)")
cur.execute ("CREATE TABLE MATIERES (num integer, matiere varchar (30))")
cur.execute ("CREATE TABLE PAYS (codepays integer, pays varchar (30))")
cur.execute (""" 5
CREATE TABLE ELEVE (
num integer primary key,
nom varchar (30),
prenom varchar (30),
date date, 10
adresse varchar (100),
codepays integer,
classe integer)
""")
Il ne faut pas oublier l’instruction cx.commit() qui met à jour la base de données. Sans cette instruction,
les tables ne sont pas modifiées et pendant l’exécution de cette instruction, toutes les autres requêtes
sont momentanément interrompues le temps que la modifications de la base soit effective. Le programme
suivant permet de vérifier que les modifications ont bien été prises en compte :
cur = cx.cursor()
cur.execute("select * from ELEVE")
for row in cur.fetchall(): print row
Il affiche 31 :
(1, u’dupre’, u’xavier’, u’11/08/1975’, u’---- paris’, 33, 19)
(2, u’dupre’, u’gilles’, u’24/12/1946’, u’---- charleville’, 33, 56)
note = [(1, 1, 12), (1, 1, 14), (1, 2, 16), (1, 2, 8), (1, 2, 12), \
(2, 1, 8), (2, 1, 9), (2, 2, 11), (2, 2, 8), (2, 2, 12)]
for n in note :
req = "INSERT INTO NOTE (nume,numm,note) VALUES " + str (n)
cur.execute (req)
cx.commit ()
Le programme suivant permet de déterminer la liste des prénoms des élèves suivi de la moyenne de leurs
notes :
req = """
SELECT nom,prenom,AVG(note) FROM ELEVE,NOTE
WHERE num = nume and
numm IN ( SELECT num FROM MATIERES WHERE matiere = ’français’ )
GROUP BY nume
"""
cur.execute (req)
for row in cur.fetchall(): print row
En ajoutant une condition avec le mot-clé HAVING, il est possible de ne retourner que la liste des élèves
ayant la moyenne :
req = """
SELECT nom,prenom,AVG(note) FROM ELEVE,NOTE
WHERE num = nume and
numm IN ( SELECT num FROM MATIERES WHERE matiere = ’français’ )
GROUP BY nume
HAVING AVG(note) >= 10
"""
cur.execute (req)
for row in cur.fetchall(): print row
31. Le symbol u devant les chaînes de caractères indique qu’elles sont codées au format unicode et que les accents sont pris
en compte.
32. En langage SQL, le mot-clé UPDATE sert à modifier un enregistrement.
14. Outils d’aide au développement 415
installation
Les paragraphes qui suivent décrivent l’installation de serveur MySQL. Il est parfois plus simple d’utiliser
l’application EasyPHP 33 qui en simplifie l’installation lorsqu’elle est couplé avec le serveur Apache (voir
également le paragraphe 14.9).
Il existe de nombreux serveurs de base de données. Les plus connus sont Microsoft SQL Server, Oracle,
Sybase. Parmi les serveurs gratuits, on trouve MySQL, ou encore PostGreSQL. Ces applications sont
souvent regroupées sous le terme de SGBD ou encore Système de gestions de base de données.
L’installation de MySQL. n’est pas très compliquée. Il faut dans un premier temps téléchercher MySQL 34
puis suivre les instructions de la figure 14.20. Il est tout de même possible de changer les paramètres de la
configuration sans avoir à refaire l’installation, il suffit d’aller dans le sous-menu ajouter par MySQL dans
le menu Démarrer / Tous les programmes.
33. http://www.easyphp.org/
34. http://dev.mysql.com/downloads/mysql/5.0.html#downloads
14. Outils d’aide au développement 416
Figure 14.20 : L’installation de MySQL fait intervenir une petite dizaine d’écrans qui se succèdent. Chacun d’eux
propose quelques options d’installation que l’utilisateur doit choisir. Voici les choix pour une utilisation sur une
machine de bureau qui n’est pas exclusivement réservé à MySQL et qui autorise une dizaine de connexion simultanée.
Cette base n’est donc pas configurée pour un usage intense. Les derniers écrans font référence à la sécurité. Il est
conseillé de définir un mot de passe root ou administrateur. C’est le seul utilisateur ayant tout pouvoir sur la gestion
du système.
Il faut maintenant installer un second morceau qui est le pilote ODBC pour MySQL 35 . Il n’y a cette fois-ci
rien à configurer et tout se passe presque sans intervention. Cette seconde étape est facultative mais elle
rend l’écriture de programmes plus facile et surtout indépendante du serveur de base de données choisie.
Avant d’utiliser Python pour lancer des requêtes, le serveur nouvellement installé est entièrement vide
et il faut d’abord créer ce qu’on appelle une database. C’est principalement un ensemble de tables. Pour
cela, il faut cliquer sur l’intitulé MySQL Command Line client du menu Démarrer / Tous les programmes
/ MySQL / MySQL Server 5.0. Il vous est d’abord demandé un mot de passe, celui renseigné lors de
l’installation. Ensuite il faut taper la ligne suivante :
35. Il est téléchargeable à l’adresse http://dev.mysql.com/doc/refman/5.0/fr/myodbc-windows-binary-installation.html.
14. Outils d’aide au développement 417
L’instruction ne devrait pas retourner d’erreur et à partir de ce moment, la database datatest est créée.
Il sera alors possible d’y connecter un pilote ODBC pour s’en servir dans un programme Python.
Il reste une dernière installation à effectuer, celle du module pywin32 36 qui permet d’utiliser une connexion
ODBC. Son installation utilise un programme exécutable et ne requiert aucune intervention.
Il existe de nombreux modules Python qui permet de se connecter directement à un serveur de base de
données particulier comme le module MySQLdb 37 qui accède au serveur MySQL. Même si cet accès est le
plus rapide, l’inconvénient de ce système est qu’il faut utiliser un module différent pour chaque serveur de
bases de données 38 .
Il existe un moyen d’homogénéiser les accès aux serveurs en ajoutant une couche intermédiaire appelée
ODBC ou Object Database Connectivity. Grâce à ce système, le code nécessaire à l’utilisation d’un serveur
ne dépend plus du système de base de données utilisés. Celui-ci peut être MySQL ou Microsoft SQL Server
ou encore un fichier texte, Excel, une base de données Access. Il suffit pour cela de déclarer ce pilote ODBC
au système d’exploitation Windows. Il faut pour cela aller dans le panneau de configuration, icône Outils
d’administration puis choisir Database ODBC. On tombe alors sur une fenêtre du type de celle présentée
à la figure 14.21. La configuration du pilot ODBC suit les instructions de la figure 14.22.
Figure 14.21 : Fenêtre gérant les accès ODBC sous Windows. La fenêtre montre quatre connexions ODBC dis-
ponibles de types différents. Il suffit de cliquer sur le bouton Add pour ajouter une connexion ODBC et ouvrir un
accès à une nouvelle source, que ce soit un fichier texte, un fichier Excel, un serveur, ...
En utilisant une connexion ODBC, les lignes écrites au paragraphe 14.6.2 ne diffèrent excepté celle qui
ouvre la connexion ODBC qui s’effectue avec un module différent. Le module à utliser devient odbc. La
création d’une table fait appel au script suivant :
import odbc
cx = odbc.odbc("mysqlperso")
cur = cx.cursor()
cur.execute (""" 5
CREATE TABLE ELEVE (
num integer primary key,
36. https://sourceforge.net/projects/pywin32/
37. http://sourceforge.net/projects/mysql-python
38. On trouve à l’adresse http://www.pygresql.org/ le module pour le serveur PostGreSQL.
14. Outils d’aide au développement 418
L’ajout de données diffère un peu de sqlite3 car le serveur SQL requiert une valeur par défaut pour la
colonne num alors qu’elle était renseigné automatiquement par sqlite3. Le langage SQL est en principe le
même d’un serveur SQL à l’autre mais il arrive parfois que certains logiciels proposent des spécificités non
compréhensibles par un autre SGBD. Par exemple, le format des dates par défaut n’est pas le même sur
MySQL :
import odbc
cx = odbc.odbc("mysqlperso")
cur = cx.cursor()
cur.execute ("""INSERT INTO ELEVE (num, nom, prenom, date, adresse, codepays, classe) 5
VALUES (1, ’dupre’, ’xavier’, ’1975-08-11’, ’---- paris’, 33, 19) ;""")
cur.execute ("""INSERT INTO ELEVE (num, nom, prenom, date, adresse, codepays, classe)
VALUES (2, ’dupre’, ’gilles’, ’1946-12-24’, ’---- charleville’, 33, 56) ;""")
cx.commit ()
import odbc
cx = odbc.odbc("mysqlperso")
14. Outils d’aide au développement 419
cur = cx.cursor()
cur.execute ("SELECT * from ELEVE")
for row in cur.fetchall () :
print row
Mise à part la connexion, les autres requêtes et le code Python les exécutant extraits des exemples déve-
loppés au paragraphe 14.6.2 seront identiques avec MySQL.
import odbc
cx = odbc.odbc("odbc_sql_server")
dbi.operation-error: [Microsoft][ODBC SQL Server Driver][SQL Server]Login failed for user ’’.
The user is not associated with a trusted SQL Server connection. in LOGIN
Dans le cas, il faut répéter le login et le mot de passe utilisé lors de la configuration de la connexion ODBC
comme ceci :
import odbc
cx = odbc.odbc("odbc_sql_server/login/password")
Une autre syntaxe comme celle qui suit est également correcte :
cx = odbc.odbc("DSN=odbc_sql_server;UID=login;PWD=password")
La première ligne permet de se placer dans le répertoire d’Apache. La seconde arrête l’exécution du
serveur Apache en tant que service, la dernière ligne redémarre le serveur Apache en tant que programme
indépendant. En contrepartie, il ne faut pas fermer la fenêtre de commande Windows à moins de vouloir
arrêter le serveur Apache. Cette solution fonctionne localement, il est possible que le serveur ne soit plus
accessible depuis une autre machine connectée sur le même réseau interne.
Une autre option consiste à donner au service Apache l’accès au serveur de base de données. Pour cela,
il faut associer à au serveur Apache un utilisateur et donner à cet utilisateur les droits nécessaires à une
bonne utilisateur de Apache. La première consiste à créer cet utilisateur (voir figure 14.23) puis à lui
conférer les bons droits.
Figure 14.23 : Pour ajouter un utilisateur, il faut aller dans le Panneau de Contrôle, choisir Outils d’administration
puis choisir Computer Management. La seconde image indique comment ajouter un utilisateur en cliquant avec le
bouton droit sur le répertoire Users ou Utilisateurs.
L’étape suivante consiste à donner à cet utilisateur nouvellement créé les droits nécessaires à la modification
des répertoires d’Apache (voir la figure 14.24). L’étape suivante est l’attribution de droits administrateur
à l’utiliseur apache (figure 14.25). La dernière étape est la configuration du service Apache ce qu’illustre
la figure 14.26.
Il arrive que sur certains systèmes, la configuration d’Apache en tant que service soit difficile pour un non
expert bien que cela soit rendu nécessaire par l’utilisation de pilotes ODBC dans de scripts CGI écrits en
Python. Il existe une alternative qui est de se connecter directement au serveur MySQL. Il faut pour cela
utiliser le module MySQLdb 39 . Le code permettant de lancer une requête diffère peu de celui utiliser pour
une connexion ODBC comme le montre les deux exemples suivant.
39. http://sourceforge.net/projects/mysql-python
14. Outils d’aide au développement 421
Figure 14.24 : L’utilisateur apache créé par les étapes illustrées la figure 14.23 doit avoir les droits d’accès en lecture
et exécution pour le répertoire d’installation d’Apache. Il doit aussi avoir les droits en écriture pour le répertoire
logs. Si Apache utilise des extensions comme SVN ou des répertoires de pages ou de scripts situés ailleurs, il faudra
également ajouter l’utilisateur apache à la liste des utilisateurs autorisés à lire ou écrire ces emplacements.
Il faut toutefois penser à fermer la connexion vers le serveur MySQL, tâche que le pilote ODBC gère seul.
Le serveur MySQL ne peut supporter trop de connexions ouvertes au même moment 40 .
Si le serveur de base de données est différent de MySQL, il suffit de taper python + <nom du serveur>
dans un moteur de recherche pour trouver le module qui permet de communiquer avec ce serveur. Par
exemple, pour Oracle, on trouve le module cx − oracle 41 . La syntaxe permettant de lancer une requête
est sensiblement la même. Un serveur déroge à cette règle, il s’agit de MSSQL Server de Microsoft qui
utilise le module win32com 42 dont voici un exemple d’utilisation.
import win32com.client
connexion = win32com.client.gencache.EnsureDispatch(’ADODB.Connection’)
connexion.Open("Provider=’SQLOLEDB’;Data Source=’localhost’;Initial Catalog=’datatest’;User ID=’root’;Password=’admin’
recordset = connexion.Execute(’select * from client’)[0]
while not recordset.EOF:
for i in recordset.Fields : print i,
print "<br>\n"
recordset.MoveNext()
connexion.Close()
ODBC fournit une interface de connexion qui ne change pas quelque soit le serveur SQL utilisé, c’est tout
l’avantage d’un tel système. Accéder directement au serveur est plus rapide mais plus contraignant si la
base de données est transférée sur un serveur différent. C’est pourquoi il est recommandé de regrouper le
code qui a trait aux bases de données. Ceci peut être fait manuellement ou en utilisant un module qui
propose une unique interface quelque soit le serveur de base de données utilisé.
40. Ceci dépend de la configuration du serveur et de la puissance de l’ordinateur sur lequel il est installé.
41. http://cx-oracle.sourceforge.net/
42. http://python.net/crew/mhammond/win32/Downloads.html
14. Outils d’aide au développement 422
Figure 14.25 : Il faut donner à l’utilisateur apache des droits d’administrateurs. Il faut pour cela aller dans le
Panneau de configuration, sur Outils d’Administration puis sur Local Secruity Policy. On termine par l’ajout de
l’utilisateur apache au droit Act a part of the operating system.
SQLObject 43 s’appuie sur les classes pour représenter les tables d’une base de données. Une table est
considérée comme une liste d’instances d’un même objet dont les attributs sont stockés dans la table. Cette
conception objet remplace en partie le langage SQL et est bien adapté aux bases de données relationnelles
qui permettent de lier des attributs de plusieurs tables.
43. http://www.sqlobject.org/
44. http://www.reportlab.org/rl_toolkit.html
45. http://www.boddie.org.uk/david/Projects/Python/pdftools/
14. Outils d’aide au développement 423
Figure 14.26 : Il faut maintenant préciser au service Apache qu’il doit utiliser les droits de l’utilisateur apache.
Il faudra redémarrer pour prendre en compte les modifications.
Le module pyPDF 46 propose des fonctionnalités équivalentes. Le module le plus récemment mis à jour est
souvent le meilleur choix.
46. http://pybrary.net/pyPdf/
14. Outils d’aide au développement 424
14.7.2 Tesseract
Tesseract est un outil gratuit de reconnaissance de l’écriture imprimée (OCR) qui est capable de transcrire
le texte depuis une page imprimée puis scannée. Il s’utilise depuis le module pytesser 47 Il fonctionne assez
simplement et il suffit d’exécuter le programme pytesser.py pour le faire fonctionner.
14.8 Graphisme
14.8.1 GNUPlot
GNUPlot 48 est un logiciel permettant de tracer des graphes. Il a son propre langage de programmation
et dispose d’une large panoplie de types de graphismes. Il existe à la fois sous Windows et Linux. Le
programme suivant construit un script GNUPlot et l’exécute de façon à construire automatiquement
l’image du graphique.
# coding: latin-1
import sys
import os
5
default_path = r"C:\Program Files\gp423win32\gnuplot\bin\pgnuplot.exe"
default_temp_dir = "tempgnuplot"
imagenumber = 0
# avant
scr = "set term png\n" + scr
image = default_temp_dir + ("/image_%05d.png" % imagenumber)
imagenumber += 1 20
scr = "set out \"" + image + "\"\n" + scr
# après
scr += "show out\n"
scr += "exit\n" 25
return image 35
47. http://code.google.com/p/pytesser/
48. http://www.gnuplot.info/
14. Outils d’aide au développement 425
id = 0
for s,lab in zip (series, seriesname) : 55
name = default_temp_dir + "/series%d.txt" % (id)
id += 1
f = open (name, "w")
for l in s :
if histo : f.write ("%f\n" % (l [1])) 60
else : f.write ("%f\t%f\n" % (l [0], l [1]))
f.close ()
scr += "\"" + name + "\" title \"" + lab + "\", "
scr = scr [:len (scr)-2]
scr += "\n" 65
if __name__ == "__main__" : 70
print "chemin pour gnuplot ", default_path
series = [ [], [] ]
for i in range (0, 100) :
x = float (i) / 100 75
y = x ** 0.5
z = 1.0 - y
series [0].append ( (x,y) )
series [1].append ( (x,z) )
80
image = build_script_gnuplot (series, ["serie 1", "serie 2"], \
xlabel="abscisses", ylabel="ordonnées", histo = False)
print "image ", image
image = build_script_gnuplot (series, ["serie 1", "serie 2"], \
xlabel="abscisses", ylabel="ordonnées", histo = True) 85
print "image ", image
14.8.2 MatPlotLib
Ce module imite la syntaxe de Matlab en ce qui concerne la création de graphe. Il propose les mêmes
fonctionnalités. Le programme suivant montre comment tracer une série financière.
import pylab
# on crée la figure
fig = pylab.figure (figsize=(size [0]/100, size [1]/100))
ax = fig.add_subplot(111)
30
# la première série à tracer est celle des volumes sous forme d’histogramme
for i in range (0, len (ohlc)) :
oh = ohlc [i]
14.9.1 Installation
L’installation commence par un premier écran qu’il faut remplir en suivant les instructions de la fi-
gure 14.27. Pour vérifier que l’installation a fonctionné, il suffit d’ouvrir un navigateur comme Firefox.
Après avoir rentré http://localhost/ dans la barre d’adresse, le message It works ! apparaît si Apache
fonctionne.
Figure 14.27 : Installation de Apache, DomainName est un nom de domaine, dans un réseau local, cela correspond
à l’adresse IP de l’ordinateur. (écrire ipconfig /all en ligne de commande pour l’obtenir. ServerName est le nom
du serveur, le plus souvent localhost qui désigne l’ordinateur local. L’adresse email peut être n’importe quel email.
Listen 81
Ensuite, dans l’exporateur internet, il faut préciser ce port qui n’est plus le port par défaut 80 et entrer
l’adresse suivante : http://localhost:81/ ou ajouter http : //nom_de_machine : 81 si le serveur Apache est
appelée depuis une autre machine du réseau.
49. http://www.easyphp.org/
50. Lors de l’écriture du livre, la version était 2.2.8 et téléchargeable à l’adresse http://httpd.apache.org/.
14. Outils d’aide au développement 428
<Location /svn/>
DAV svn
SVNListParentPath on
SVNParentPath "c:/svnrepository/"
SVNPathAuthz on
#AuthzSVNAccessFile "C:/Program Files/Apache Software Foundation/Apache2.2/bin/apachesvnauth
AuthName "Subversion Repositories"
AuthType Basic
AuthBasicProvider file
AuthUserFile "C:/Program Files/Apache Software Foundation/Apache2.2/bin/apachesvnpasswd"
require valid-user
</Location>
Il faut remplacer c : /svnrepository/ par le répertoire du repository créé et C : /Program Files/Apache SoftwareFou
par le répertoire d’installation de Apache.
3. Il reste à insérer des utilisateurs authorisés à utiliser SVN. Pour cela il faut ouvrir une fenêtre de
commande Windows :
cd C:\Program Files\Apache Software Foundation\Apache2.2\bin
htpasswd.exe -c apachesvnpasswd utilisateur
La seconde étape est l’attribution de droits au répertoire contenant plusieurs répertoires essentiels au Wiki
mais qui sont situés en dehors du chemin DocumentRoot. Toujours dans le fichier httpd.conf, il faut ajouter
les lignes en fin de fichier :
51. Cette information est présente dans le fichier logs/error.log dans le répertoire d’installation d’Apache.
14. Outils d’aide au développement 429
<Directory "C:/Python25/share/moin/htdocs">
Order deny,allow
Allow from all
</Directory>
<Directory "C:/Moin/mywiki">
Options FollowSymLinks
AllowOverride None
Order deny,allow
Allow from all
</Directory>
Il ne faut pas oublier de redémarrer le serveur pour que ces modifications soient bien prises en compte. Avec
cette configuration, il suffit que chaque script Python commence par une ligne indiquant l’emplacement
de l’interpréteur Python.
#!c:/python25/python.exe
Ou sous Linux :
#!/usr/bin/python
Cette première ligne est appelée par Apache un shebang. Si un script CGI doit être exécuté sur des serverus
Apache installés sur deux systèmes d’exploitation différent, cette ligne empêche le passage de l’un à l’autre.
Apache peut être configurer de façon à ce qu’il sache automatiquement quoi faire lors de l’exécution d’un
script CGI d’extension .py.
ScriptInterpreterSource registry
<Directory "C:/Python25"> # il faut également une section
AllowOverride None # comme celle-ci définie
Options ExecCGI # pour le répertoire contenant
Order allow,deny # le script CGI
Allow from all #
</Directory>
AddType application/x-httpd-py .py
AddHandler cgi-script .py
Action application/x-httpd-py "C:/python25/python.exe"
netstat -ao
Il est possible de configurer un second serveur Apache, c’est-à-dire un nouveau service, un second port, un
second fichier de configuration.
Cette modification est parfois nécessaire car pour installer un serveur SVN, il existe deux solutions :
1. La solution présentée précédemment, qu’il est possible de simplifier en utilisant la distribution Col-
labNet 53 qui installe tout automatiquement excepté qu’il faut faire attention si un autre serveur
Apache existe (cette distribution utilise Python 2.5 et il vaut mieux ne pas mélanger les différentes
versions de Python.)
2. Lancer le serveur SVN (svnserve.exe) disponible lors de l’installation de SVN.
Lors de l’installation du serveur Apache, il est possible de rencontrer cette erreur :
(OS 10048)Only one usage of each socket address (protocol/network address/port) is normally permitted. : make_sock: c
no listening sockets available, shutting down
Unable to open logs
J’ai rencontrée cette erreur alors le fichier de configuration contenait deux fois l’instruction Listen pour
deux ports différents. Autre détail, il est possible de restreindre l’accès au serveur en créant une règle au
niveau de Firewall de Windows.
<Directory "C:/Python25">
AllowOverride None
Options ExecCGI
Order allow,deny
Allow from all
</Directory>
<Directory "C:/_home/query-intent/projects/common_tools/cgi/">
AllowOverride None
Options FollowSymLinks ExecCGI
Order allow,deny
Allow from all
</Directory>
Il faut préciser ensuite que l’extension .py est celle d’un script CGI et qu’il doit s’exécuter avec l’interpréteur
Python :
53. http://www.collab.net/, cette distribution n’aime pas trop les login avec un symbol "-".
14. Outils d’aide au développement 431
<IfModule mime_module>
TypesConfig conf/mime.types
AddType application/x-httpd-py .py
AddHandler cgi-script .py
</IfModule>
Action application/x-httpd-py "C:/python25/python.exe"
Par exemple, le fichier suivant nommé script.py est placé dans le répertoire précédent.
footer = """</body></html>"""
print header
print "<b>Script CGI</b><br>\n"
print footer
http://localhost:80/py/script.py
Chapitre 15
Algorithmes usuels
Figure 15.1 : Illustration du calcul approché d’une intégrale à l’aide de la méthode des rectangles.
de l’intégrale est noté [a, b] et la fonction à intégrer f . On divise cet intervalle en n petits segments et on
fait la somme des aires des petits rectangles délimités par l’axe des abscisses et la courbe de la fonction f .
Z b n
b−a X b−a
f (x)dx ≈ f a+i (15.1)
a n n
i=1
a = −2
b =3
(15.2)
f (x) = xcos(x)
n = 20
# coding: cp1252
import math
n = 2000
a = -2.0 5
b = 3.0
h = (b-a)/n
sum = 0
for i in xrange (1,n): 10
x = a + h * i
sum += h * x * math.cos (x)
Le module SciPy 1 propose de nombreux algorithmes mathématiques tels que le calcul d’intégrales. Le
programme précédent se résume alors à quelques lignes.
intégrale : -1.96640795695
intégrale avec scipy : -1.96908048953
x = [1,3,2,5,8,4,9,0,7,6]
x.sort ()
print x
Cependant, le tri est très courant en informatique et il est parfois utile de s’inspirer de telles méthodes pour
résoudre des problèmes similaires. On distingue principalement deux familles d’algorithmes qui permettent
de trier un ensemble. La première, la plus simple, inclus notemment le tri par sélection. Les algorithmes de
cette classe ont un coût 2 en O(n2 ) où n est le nombre d’éléments à trier. La seconde classe d’algorithmes
inclut le tri rapide ou quicksort qui est détaillé dans le TD 4. Elle inclut également le tri par fusion décrit
au paragraphe 15.2.3 ou le tri par insertion dichotomique décrit au paragraphe 15.2.4. Le point commun
de ces algorithmes est leur coût qui est en O(n ln n).
1. Le module SciPy propose de nombreux algorithmes, algorithmes de classification, intégration, optimisation, génération
de nombres aléatoires, traitement du signal, interpolation, algorithmes génétiques, algèbre linéaire, transformée de Fourier,
dessin de courbes 2D, 3D, adresse : http://www.scipy.org/.
2. Le coût d’un algorithme est une estimation ou l’exact nombre d’opérations nécessaires à l’algorithme pour traiter les
informations qu’il reçoit. Le coût sera en général exprimé à l’aide du symbol O(...) qui désigne un multiple d’une expression.
O(n) désigne par exemple un multiple de n.
15. Algorithmes usuels 434
Ce tri s’appelle tri par sélection car, à chaque étape B, le plus grand élément remonte vers le haut de la
liste, vers la surface. On peut se demander si, après l’application de cet algorithme, la liste est effectivement
triée et comment cet algorithme serait implémenté en langage Python. Le coût de cet algorithme est en
O(n2 ).
Pour trier un ensemble E en utilisant la méthode du tri par fusion, on trie d’abord toutes les paires
d’éléments de E. On fusionne ensemble ces paires d’éléments deux par deux pour obtenir des ensembles
de quatre éléments. On fusionne à nouveau des groupes de quatre éléments deux par deux pour obtenir
des groupes de huit éléments. On réitère le processus jusqu’à ce que l’ensemble E soit trié, ce qui mène à
15. Algorithmes usuels 435
l’algorithme suivant.
Le coût de cet algorithme est en O(n ln n). Cependant, l’algorithme 15.2 implique de conserver en mémoire
un tableau d’indices le temps de la fusion et ce tableau temporaire est de même taille que la liste fusionnée.
Cette contrainte n’existe pas avec le tri par sélection ou le tri rapide mais le coût de ces algorithmes est
supérieur à celui du tri par fusion.
On utilise l’algorithme suivant pour trier une liste par insertion dichotomique.
Le coût d’une recherche dichotomique est en O(ln n) où n est le cardinal de l’ensemble de recherche. Le
coût d’un algorithme par insertion dichotomique est en O(n ln n). Ce coût peut varier en fonction de la
structure utilisée pour stocker les données. Si c’est sous forme de graphe, le tri par insertion est en fait un
tri rapide ou quicksort. En revanche, si les données sont insérées dans un tableau, même si la recherche
de la position d’un élément à insérer est rapide, insérer cette information dans le tableau revient à décaler
les données pour créer une place vide. Dans ce cas, tout dépend du coût de ce décalage.
Et :
n
f (n) = 2f +n (15.8)
2
n
= 4f + 2n (15.9)
4
n
= 8f + 3n (15.10)
8
... (15.11)
La récurrence s’arrête lorsque 2nk < 1 auquel cas le coût est nul. Le coût de l’algorithme par fusion est donc
n ln n
ln 2 soit O(n ln n). On peut montrer que le coût du tri par fusion est O(n ln n) simplement à partir de la
récurrence. On va tout d’abord déterminer le lien entre deux fonctions de classe C 1 vérifiant la récurrence
(15.3). On suppose pour cela qu’il existe deux fonctions f et g qui vérifient (15.3). On pose h = f − g. h
vérifie :
Comme f et g sont de classe C 1 sur [0, ∞[, h l’est aussi. On en déduit que :
∀k ∈ ℕ, h(x) = 2k h 2−k x (15.13)
∀k ∈ ℕ, h0 (x) = h0 2−k x (15.14)
La dérivée h0 est continue, on démontre donc que ∀x ∈ ℝ, h0 (x) = h0 (0) = α. Par extension, h(x) = αx + β.
Afin que la fonction h vérifie la condition 15.12, il faut que β = 0. L’ensemble des solutions de l’équation
15.12 est donc {h(x) = αx | α ∈ ℝ}.
On pose f (n) = n ln n
ln 2 . Si g vérifie également g(2n) = 2g(n) + 2n alors il existe α ∈ ℝ tel qu’on puisse
écrire g de la manière suivante :
Comme O(n) est négligeable devant O(n ln n), ceci démontre d’une autre façon que le coût d’un tri par
fusion est en O(n ln n).
15. Algorithmes usuels 438
Le coût d’un tri par comparaisons successives est au mieux de O(n ln n). Comme c’est le coût du tri par
fusion, ce raisonnement permet d’affirmer qu’un tri de coût O(n ln n) existe et qu’il est impossible de faire
mieux.
fin pour
pour i = 1 à n faire
pli ←− pli + 1
fin pour
On détermine ensuite le nombre de valeurs inférieures à un certain niveau, (Pm , . . . , PM ). Elle corres-
pond à la fonction de répartition du vecteur (l1 , . . . , ln ) tandis que le vecteur (pm , . . . , pM ) correspond
à sa fonction de densité.
Etape B : fonction de répartition
Pm ←− pm pour k = m + 1 à M faire
Pk ←− Pk−1 + pk
fin pour
On termine par l’obtention de la suite triée (s1 , . . . , sn ).
Etape C : tri
k ←− 0
pour i = 2 à n faire
tant que ( Pk < i ) faire
k ←− k + 1
Chaque boucle de cet algorithme excepté est une boucle qui dépend soit de la taille de l’échantillon à trier
soit de l’écart entre le maximum et le minimum. La dernière étape est de façon évidente la plus coûteuse
ou au moins aussi coûteuse que toutes les autres. Le coût de l’algorithme est en O(max(n, M − m)). Cet
algorithme est lié à un théorème statistique. Si X est une variable aléatoire et P sa fonction de répartition
alors P −1 (X) suit une loi uniforme sur l’intervalle [0, 1]. Il suffit d’appliquer ce théorème à une loi discrète,
P −1 (x) correspond à la position de x dans la liste triée. Ce tri ne s’applique qu’à des entiers, il serait
néanmoins possible de discrétiser un tableau de réels à condition de savoir l’écart minimum entre deux
valeurs du tableaux, information qu’il est aisé de déterminer une fois le tableau trié.
def construit_suite(n):
"""construit une liste de n nombres entiers compris entre 0 et 99""" 5
l = []
for i in range(0,n):
15. Algorithmes usuels 440
l.append (random.randint(0,100))
return l
10
def tri_selection(l):
""" tri une liste l, tri par sélection"""
# première boucle, répétition des étapes A et B
for i in range(0,len(l)):
15
# recherche du maximum, on suppose pour commencer
# qu’il est à la position 0
pos = 0
# boucle de l’étape A
for j in range(1,len(l)-i): 20
if l [pos] < l [j]: pos = j
# échange de l’étape B
# la position du maximum est conservé dans la variable pos
# on fait l’échange avec l’élément à la position len(l)-i-1 25
k = len(l)-i-1
ech = l [k]
l [k] = l [pos]
l [pos] = ech
30
l = construit_suite(8) # création d’une suite aléatoirement
print "liste non triée :\t",l # affichage
tri_selection (l) # tri
print "liste triée :\t",l # affichage
def construit_suite(n):
"""construit une liste de n nombres entiers compris entre 0 et 99""" 5
l = []
for i in range(0,n):
l.append (random.randint(0,100))
return l
10
def fusion_liste (l1,l2):
"""fusionne deux listes l1 et l2 triées par ordre croissant
de sorte que la liste résultante soit également triée"""
i,j,k = 0,0,0
fin = len (l1) + len (l2) 15
l = []
while k < fin :
if j >= len (l2) or (i < len (l1) and l1 [i] < l2 [j]) :
l.append (l1 [i])
k += 1 20
i += 1
else :
l.append (l2 [j])
k += 1
j += 1 25
return l
def tri_fusion(l):
""" tri une liste l par fusion"""
if len (l) <= 1 : return None # on élimine le cas simple 30
n = 1
while n < len (l) :
for i in xrange (0, len (l), 2*n):
15. Algorithmes usuels 441
a = i
m = min (len (l), a + n) 35
b = min (len (l), a + 2 * n)
if m < b:
t = fusion_liste (l [a:m], l [m:b])
l [a:b] = t
n *= 2 40
def construit_suite(n):
"""construit une liste de n nombres entiers compris entre 0 et 99""" 5
l = []
for i in range(0,n):
l.append (random.randint(0,100))
return l
10
def recherche_dichotomique (l,x):
"""cherche l’élément x dans la liste l, la liste l est
supposée être triée par ordre croissant"""
a = 0
b = len(l)-1 15
while a <= b :
m = (a+b) // 2
r = cmp (x, l[m])
if r == 0 : return m
elif r == -1 : b = m-1 20
else : a = m + 1
return a
def tri_insertion(l):
""" tri une liste l par insertion dichotomique, on suppose que l est non vide""" 25
lt = []
for x in l :
if len (lt) == 0 :
lt.append (x)
continue 30
m = recherche_dichotomique (lt, x)
lt.insert (m, x)
l [0:len (l)] = lt
35
l = construit_suite(13) # création d’une suite aléatoirement
print l [0:3]
print "liste non triée :\t",l # affichage
tri_insertion (l) # tri
print "liste triée :\t",l # affichage 40
# fonction de répartition 25
P = [0 for i in range (m,M+1) ]
P [0] = p [0]
for k in range (1, len (p)) :
P [k] = P [k-1] + p [k]
30
# tri
pos = 0
for i in range (1, len (l)) :
while P [pos] < i : pos += 1
l [i-1] = pos + m 35
l [len (l)-1] = M
[Martelli2004] - jamais cité - Alex Martelli, Python en concentré, O’Reilly, édition française (2004)
[Swinnen2004] - jamais cité - Gérard Swinnen, Apprendre à programmer avec Python, voir à l’adresse
http:// www.cifen.ulg.ac.be/ inforef/ swi/ python.htm, O’Reilly, édition française (2004)
[Pilgrim2004] - jamais cité - Mark Pilgrim, Dive Into Python, voir à l’adresse http:// diveintopython.org ,
titre français Plongez au coeur de Python, APress (2004)
[Ziadé2006] - jamais cité - Tériak Ziadé, Programmation Python, Eyrolles (2006)
[Dayley2007] - jamais cité - Brad Dayley, Python - L’essentiel du code et des commandes, Campuss
Press (2007)
[Brucher2008] - jamais cité - Matthieu Brucher, Les Fondamentaux du langage - La Programmation
pour les scientifiques, Editions ENI (2008)
[Bailly2008] - jamais cité - Yves Bailly, Initiation à la programmation avec Python et C++, Editions
Le programmeur (2008)
Remerciements
De nombreuses personnes m’ont aidé à réaliser ce livre dont la rédaction s’est étalée sur plusieurs années.
La décision de choisir le langage Python à l’ENSAE a été prise grâce à une initiative de David Sibaï
qui m’a aussi apporté ses conseils lors de la conception des examens à l’ENSAE. Emmanuel Guérin m’a
largement aidé à comprendre les subitilités du langage Python notamment son interfaçage avec le langage
C++. Christopher Kermorvant m’a souvent fait découvrir des utilisations intéressantes du langage grâce
à sa curiosité 3 . Je remercie également Jérémie Jakubowicz, un autre curieux, qui enseigne également.
Je remercie Georges Pavlov et Emmanuel Héry qui ont toujours très bien organisé mes enseignements à
l’ENSAE. Je remercie aussi Clémence Dupré dont l’aide a été précieuse lors de la rédaction. Il ne faut
pas oublier tous les élèves de l’ENSAE qui m’ont eu comme professeur dont les questions nombreuses et
pertinentes m’ont aidé à concevoir les exercices que j’ai insérés dans ce livre. Je remercie enfin l’ENSAE,
école dont je suis sorti et qui m’a permis de revenir pour enseigner. Sans elle, il n’y aurait rien eu de tout
cela.
446
Index 447
constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 écrit . . 289, 301, 309, 319, 325, 335, 344, 349
dictionnaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 pratique . . . . . . . . . . . . . . . . . . . . . . . 247, 249, 252
exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 ENSAE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Eratosthène . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
fonction récursive . . . . . . . . . . . . . . . . . . . . . . . . 65 événement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
héritage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .107 brut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
instance. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .80 exception . . . . . . . . . . . . 36, 118, 119, 146, 217, 360
instruction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 ArithmeticError . . . . . . . . . . . . . . . . . . . . . . 122
langage interprété . . . . . . . . . . . . . . . . . . . . . . . . . 8 AttributeError . . . . . . . . . . . . . . . . . . . . . . . . 122
liste. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34 Exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
méthode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 FloatingPointError . . . . . . . . . . . . . . . . . . . 122
méthode statique . . . . . . . . . . . . . . . . . . . . . . . . 92 ImportError . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
mot-clé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 IndentationError . . . . . . . . . . . . . . . . . . . . . 122
point d’entrée du programme . . . . . . . . . . . 128 IndexError . . . . . . . . . . . . . . 117, 122, 294, 321
portée d’une variable . . . . . . . . . . . . . . . . . . . . . 66 IOError . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
programme. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8 KeyError . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
surcharge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 NameError . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
T-uple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 OverflowError . . . . . . . . . . . . . . . . . . . . . . . . . 122
test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 StopIteration . . . . . . . . . . . . . . . . . . . . 122, 126
type immuable (ou immutable) . . . . . . . . . . . 26 TypeError . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
type modifiable (ou mutable). . . . . . . . . . . . .34 UnicodeError . . . . . . . . . . . . . . . . . . . . . . . . . . 122
variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 ValueError . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
variable globale . . . . . . . . . . . . . . . . . . . . . . . . . . 66 ZeroDivisionError . . . . . . . . . . . . . . . . . . . . 122
variable locale . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 héritage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .123
deprecated . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 imbrication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
dériver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 message . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122, 123
Design Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 piège . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
dichotomie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75, 247 exercice
dictionnaire arrondi. . . . . . . . . . . . . . . . . . . . . . . . . . . . .309, 319
clé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 boucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
clé modifiable . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 calcul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290, 302
copie. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .42 classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305, 307
exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 comparaison . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
valeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 comptage . . . . . . . . . . . . . . . . . 303, 313, 323, 349
diviser pour régner . . . . . . . . . . . . . . . . . . . . . . . . . . 434 copie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293, 307
division entière . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 coût d’un algorithme . . . . . . . . . . . . . . . . . . . . 295
Dynamic Link Library (DLL) . . . . . . . . . . . . . . . 140 dictionnaire . . . . . . . . . . . . . . . . . . . 292, 303, 323
division . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
E écriture en base 3 . . . . . . . . . . . . . . . . . . . . . . . 306
éditeur Fibonacci . . . . . . . . . . . . . . . . . . . . . 291, 310, 319
Boa Constructor . . . . . . . . . . . . . . . . . . . . . . . . . 16 grenouille . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
DrPython . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296, 314
Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Emacs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 logarithme . . . . . . . . . . . . . . . . . . . . . . . . . 301, 350
Python Scripter . . . . . . . . . . . . . . . . . . . . . . . . . . 14 matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
SciTe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 moyenne . . . . . . . . . . . . . . . . . . . . . . . . . . . 295, 307
TextWrangler . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 parcours . . . . . . . . . . . . . . . . . . 301, 309, 321, 350
The Eric Python IDE . . . . . . . . . . . . . . . . . . . . 16 précision des calculs . . . . . . . . . . . 298, 322, 350
efficacité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 probabilité . . . . . . . . . . . . . . . . . . . . . . . . . 304, 317
encodage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 somme de chiffres . . . . . . . . . . . . . . . . . . . . . . . 289
énoncé suite récurrente . . . . . . . . . . . 290, 291, 310, 319
apprentissage . . . . . . . . 210, 213, 216, 224, 230 suppression dans une boucle . . . . . . . . . . . . 294
calcul matriciel . . . . . . . . . . . . . . . . . . . . . . . . . 238 tri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289, 295
Index 449
exemple de test plus complet avec doctest lancer un navigateur en ligne de commande
375 161
exercice pour s’évaluer , correction 2006 . 248 lancer un programme Python en ligne de
exercice pour s’évaluer , correction 2007 . 250 commande . . . . . . . . . . . . . . . . . . . . 160, 379
exercice pour s’évaluer , correction 2008 . 253 lecture d’un fichier texte . . . . . . . . . . . . . . . 217
exercice pour s’évaluer , correction 2009 256, lecture d’une page HTML . . . . . . . . . . . . . . 134
261 ligne de commande (1) . . . . . . . . . . . . . . . . . . 160
exercice pour s’évaluer , correction 2010 268, ligne de commande (2) . . . . . . . . . . . . . . . . . . 161
275 ligne de commande SciTe . . . . . . . . . . . . . . 380
exercice pour s’évaluer , correction 2011 . 279 liste ComboBox . . . . . . . . . . . . . . . . . . . . . . . . . 178
exercice pour s’évaluer , correction 2012 . 286 liste avec barre de défilement . . . . . . . . . . . 178
existence d’un module . . . . . . . . . . . . . . . . . . 131 liste de fichiers (1) . . . . . . . . . . . . . . . . . . . . . . 154
expressions régulières (1) . . . . . . . . . . . . . . . . 164 liste de fichiers (2) . . . . . . . . . . . . . . . . . . . . . . 155
expressions régulières (2) . . . . . . . . . . . . . . . . 167 liste de fichiers (3) . . . . . . . . . . . . . . . . . . . . . . 160
extraction de liens Internet . . . . . . . . . . . . . 222 liste de tests avec unittest . . . . . . . . . . . . 376
extraction du texte d’un fichier pdf . . . . . 422 liste des modules . . . . . . . . . . . . . . . . . . . . . . . 131
factorielle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 liste récursive (1) . . . . . . . . . . . . . . . . . . . . . . . 326
FDA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223 liste récursive (2) . . . . . . . . . . . . . . . . . . . . . . . 326
Fibonacci . . . . . . . . . . . . . . . . . . . . .310, 311, 319 logarithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
fichier , écriture . . . . . . . . . . . . . . . . . . . . . . . . . 150 matrice . . . . . . . . . . . . . . . . . . . . . . . . . 75, 77, 115
fichier , lecture . . . . . . . . . . . . . . . . . . . . . 150, 151 matrice , écriture. . . . . . . . . . . . . . . . . . .149, 151
fichier ouvert et exception . . . . . . . . . . . . . . 127 matrice , lecture . . . . . . . . . . . . . . . . . . . . . . . . 151
fichier zip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 matrice, lignes nulles . . . . . . . . . . . . . . . . . . . 305
fichiers binaires (1) . . . . . . . . . . . . . . . . . . . . . 157 maximum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
fichiers binaires (2) . . . . . . . . . . . . . . . . . . . . . 157 maximum avec indices . . . . . . . . . . . . . . . . . . . 74
fonction isinstance . . . . . . . . . . . . . . . . . . . 113 maximum avec la fonction max . . . . . . . . . . . 74
formatage de chaîne de caractères . . . . . . . 32 maximum sur un sous-ensemble . . . . . . . . . . 74
génération automatique d’un fichier chm 390 méthode paramètre d’une fonction . . . . . 115
génération automatique de l’aide . . 372, 387 méthode statique . . . . . . . . . . . . . . . . . . . . 92, 93
graphe avec matplotlib . . . . . . . . . . . . . . . . . 220 module pickle et classes (1) . . . . . . . . . . . . 159
graphiques avec GNUPlot . . . . . . . . . . . . . . 424 module pickle et classes (2) . . . . . . . . . . . . 159
graphiques avec MatPlotLib . . . . . . . . . . . . 425 module à tester . . . . . . . . . . . . . . . . . . . . . . . . 376
grenouille . . . . . . . . . . . . . . . . . . . . . . . . . 291, 292 moyenne mobile centrée (1) . . . . . . . . . . . . . 328
héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 moyenne mobile centrée (2) . . . . . . . . . . . . . 329
héritage multiple . . . . . . . . . . . . . . . . . . . . . . . 112 moyenne, variance . . . . . . . . . . . . . . . . . . . . . .295
hypercube . . . . . . . . . . . . . . . . . . . . . . . . . 323–325 opérateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
import d’une DLL . . . . . . . . . . . . . . . . . . . . . 145 opérateur addition . . . . . . . . . . . . . . . . . . . . . . 88
import de module (1) . . . . . . . . . . . . . . . . . . . 128 partager des données . . . . . . . . . . . . . . . . . . . 201
import de module (2) . . . . . . . . . . . . . . . . . . . 129 pièce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105–107
import de module (3) . . . . . . . . . . . . . . . . . . . 129 plusieurs fenêtre Toplevel . . . . . . . . . . . . . 191
import de module (4) . . . . . . . . . . . . . . . . . . . 130 point de départ du TD noté , énoncé 2012
import dynamique de module . . . . . . . . . . 131 282
importer un module en C++ . . . . . . . . . . . 137 premier thread . . . . . . . . . . . . . . . . . . . . . . . . . 198
insertion de valeurs dans une table sqlite3 propriété . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
413 racine carrée . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
installation d’un module . . . . . . . . . . . . . . . 135 recherche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
intégrale de Monte Carlo . . . . . . . . . . . . . . . 134 recherche dichotomique . . . . . . . . . . . . . . . . . . 75
intégrale, scipy . . . . . . . . . . . . . . . . . . . . . . . . . 433 récupération d’un texte sur Internet . . . . 217
interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 référencement d’objets . . . . . . . . . . . . . . . . . . 98
itérateur . . . . . . . . . . . . . . . . . . . . . . . . 55, 90, 126 sélection d’un fichier . . . . . . . . . . . . . . . . . . . 191
jeu de caractères . . . . . . . . . . . . . . . . . . . . . . . 169 séquence aléatoire . . . . . . . . . . . . . . . . . . . . . . . 84
jeu de caractères , fichier . . . . . . . . . . . . . . . . 169 sérialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
joueurs asynchrones . . . . . . . . . . . . . . . . . . . . 205 somme . . . . . . . . . . . . . . . . . . . . . . . 25, 37, 47, 75
Index 457
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1 Ordinateur et langages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.1 L’ordinateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.2 Termes informatiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2 Présentation du langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2.1 Histoire résumée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2.2 Le langage en quelques points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.3 Avantages et inconvénients du langage Python . . . . . . . . . . . . . . . . . . . . . 9
1.3 Installation du langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3.1 Installation du langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3.2 Particularité de Mac OS X et Linux . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3.3 Utilisation de l’éditeur de texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4 Installation d’un éditeur de texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4.1 Editeur SciTe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4.2 Python Scripter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4.3 Eclipse et PyDev . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.4.4 Côté Mac OS X : TextWrangler . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.4.5 Autres éditeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.4.6 Tabulations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.5 Premier programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.5.1 Afficher le résultat d’un calcul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.5.2 Les accents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.6 Installation d’extensions (ou modules externes) . . . . . . . . . . . . . . . . . . . . . . . . 18
1.6.1 Graphes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.6.2 Calculs scientifiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.6.3 Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.6.4 Base de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.6.5 Interfaces graphiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.6.6 Microsoft Windows, Microsoft Excel, PDF . . . . . . . . . . . . . . . . . . . . . . . 21
1.6.7 Accélération de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.7 Outils connexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.7.1 Outils quotidiens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.7.2 Outils occasionnels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Table des matières 465
4. Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.1 Présentation des classes : méthodes et attributs . . . . . . . . . . . . . . . . . . . . . . . . 79
4.1.1 Définition, déclaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.1.2 Méthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.1.3 Attributs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.2 Constructeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.3 Apport du langage Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.3.1 Liste des attributs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.3.2 Attributs implicites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.3.3 Commentaires, aide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.3.4 Classe incluse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.4 Opérateurs, itérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.4.1 Opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
4.4.2 Itérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
4.5 Méthodes, attributs statiques et ajout de méthodes . . . . . . . . . . . . . . . . . . . . . . 92
4.5.1 Méthode statique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
4.5.2 Attributs statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
4.5.3 Ajout de méthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
4.5.4 Propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
4.6 Copie d’instances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.6.1 Copie d’instance de classe simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.6.2 Copie d’instance de classes incluant d’autres classes . . . . . . . . . . . . . . . . . . 98
4.6.3 Listes et dictionnaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
4.6.4 copyet deepcopy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
4.7 Attributs figés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
4.8 Héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4.8.1 Exemple autour de pièces de monnaie . . . . . . . . . . . . . . . . . . . . . . . . . 104
4.8.2 Syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
4.8.3 Sens de l’héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.8.4 Héritage multiple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.8.5 Fonctions issubclasset isinstance . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
4.9 Compilation de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
4.10 Constructions classiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Table des matières 468
5. Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
5.1 Principe des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
5.1.1 Attraper toutes les erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
5.1.2 Obtenir le type d’erreur, attraper un type d’exception . . . . . . . . . . . . . . . . 120
5.1.3 Lancer une exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
5.1.4 Héritage et exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
5.1.5 Instructions try, exceptimbriquées . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
5.2 Définir ses propres exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
5.2.1 Dériver une classe d’exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
5.2.2 Personnalisation d’une classe d’exception . . . . . . . . . . . . . . . . . . . . . . . . 125
5.3 Exemples d’utilisation des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
5.3.1 Les itérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
5.3.2 Exception ou valeur aberrante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
5.3.3 Le piège des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6. Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
6.1 Modules et fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
6.1.1 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
6.1.2 Autres syntaxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
6.1.3 Nom d’un module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
6.1.4 Emplacement d’un module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
6.1.5 Ajouter un module en cours d’exécution . . . . . . . . . . . . . . . . . . . . . . . . 131
6.1.6 Liste des modules importés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
6.1.7 Attributs communs à tout module . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
6.1.8 Arborescence de modules, paquetage . . . . . . . . . . . . . . . . . . . . . . . . . . 132
6.2 Modules internes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
6.3 Modules externes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
6.4 Python et les autres langages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.4.1 Langage Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.4.2 Langage C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.5 Ecrire un module en langage C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
6.6 Boost Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
6.6.1 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
6.6.2 Les grandes lignes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
6.6.2.1 Initialiser le module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
6.6.2.2 Ajout d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
6.6.2.3 Ajout d’une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
6.6.2.4 Ajout d’une méthode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
6.6.2.5 Ajout d’un attribut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
6.6.3 Exemple concret : ajout d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . 144
Table des matières 469
9. Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
9.1 Premier thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
9.2 Synchronisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
9.2.1 Attente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
9.2.2 Partage d’informations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
9.3 Interface graphique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
9.4 Files de messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Quatrième partie III Résumé pratique, outils d’aide, algorithmes à connaître 353
Index 446