Cours Python
Cours Python
Avant-propos 8
Quelques mots sur l’origine de ce cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Remerciements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Le livre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1 Introduction 10
1.1 Qu’est-ce que Python ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2 Conseils pour l’apprentissage de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3 Conseils pour installer et configurer Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4 Notations utilisées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.5 Introduction au shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.6 Premier contact avec Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.7 Premier programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.8 Commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.9 Notion de bloc d’instructions et d’indentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.10 Autres ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2 Variables 16
2.1 Définition et création . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2 Les types de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3 Nommage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.4 Écriture scientifique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.5 Opérations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.6 La fonction type() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.7 Conversion de types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.8 Note sur le vocabulaire et la syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.9 Minimum et maximum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.10 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3 Affichage 24
3.1 La fonction print() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.2 Messages d’erreur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.3 Écriture formatée et f-strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2
Table des matières Table des matières
4 Listes 32
4.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2 Utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.3 Opération sur les listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.4 Indiçage négatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.5 Tranches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.6 Fonction len() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.7 Les fonctions range() et list() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.8 Listes de listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.9 Minimum, maximum et somme d’une liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.10 Problème avec les copies de listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.11 Note sur le vocabulaire et la syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.12 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5 Boucles et comparaisons 39
5.1 Boucles for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.2 Comparaisons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5.3 Boucles while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6 Tests 49
6.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.2 Tests à plusieurs cas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.3 Importance de l’indentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
6.4 Tests multiples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
6.5 Instructions break et continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.6 Tests de valeur sur des floats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.7 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
7 Fichiers 58
7.1 Lecture dans un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
7.2 Écriture dans un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
7.3 Ouvrir deux fichiers avec l’instruction with . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
7.4 Note sur les retours à la ligne sous Unix et sous Windows . . . . . . . . . . . . . . . . . . . . . . . . . . 62
7.5 Importance des conversions de types avec les fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7.6 Du respect des formats de données et de fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7.7 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
8 Dictionnaires et tuples 66
8.1 Dictionnaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
8.2 Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
8.3 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
9 Modules 76
9.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
9.2 Importation de modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
9.3 Obtenir de l’aide sur les modules importés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
9.4 Quelques modules courants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
9.5 Module random : génération de nombres aléatoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
9.6 Module sys : passage d’arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.7 Module pathlib : gestion des fichiers et des répertoires . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
9.8 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
10 Fonctions 88
10.1 Principe et généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
10.2 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
10.3 Passage d’arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
10.4 Renvoi de résultats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
10.5 Arguments positionnels et arguments par mot-clé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
10.6 Variables locales et variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
10.7 Principe DRY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
10.8 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
14 Conteneurs 138
14.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
14.2 Plus sur les dictionnaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
14.3 Plus sur les tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
14.4 Sets et frozensets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
14.5 Récapitulation des propriétés des conteneurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
14.6 Dictionnaires et sets de compréhension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
14.7 Module collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
14.8 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
27 Mini-projets 349
27.1 Description des projets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
27.2 Accompagnement pas à pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
27.3 Scripts de correction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
Remerciements
Merci à tous les contributeurs, occasionnels ou réguliers, entre autre : Jennifer Becq, Benoist Laurent, Hubert Santuz,
Virginie Martiny, Romain Laurent, Benjamin Boyer, Jonathan Barnoud, Amélie Bâcle, Thibault Tubiana, Romain Retu-
reau, Catherine Lesourd, Philippe Label, Rémi Cuchillo, Cédric Gageat, Philibert Malbranche, Mikaël Naveau, Alexandra
Moine-Franel, Dominique Tinel, et plus généralement les promotions des masters de biologie informatique et in silico
drug design, ainsi que du diplôme universitaire en bioinformatique intégrative.
Nous remercions tout particulièrement Sander Nabuurs pour la première version de ce cours remontant à 2003, Denis
Mestivier pour les idées de certains exercices et Philip Guo pour son site Python Tutor 4 .
Enfin, merci à vous tous, les curieux de Python, qui avez été nombreux à nous envoyer des retours sur ce cours, à nous
suggérer des améliorations et à nous signaler des coquilles. Cela rend le cours vivant et dynamique, continuez comme ça !
De nombreuses personnes nous ont aussi demandé les corrections des exercices. Nous ne les mettons pas sur le site
afin d’éviter la tentation de les regarder trop vite, mais vous pouvez nous écrire et nous vous les enverrons.
Le livre
Ce cours est également publié aux éditions Dunod sous le titre « Programmation en Python pour les sciences de la
vie 5 ». Le livre en est à sa 2e édition, vous pouvez vous le procurer dans toutes les bonnes librairies.
Afin de promouvoir le partage des connaissances et le logiciel libre, nos droits d’auteurs provenant de la vente de cet
ouvrage sont reversés à deux associations : Wikimédia France 6 qui s’occupe notamment de l’encyclopédie libre Wikipédia
1. https://www.u-paris.fr/
2. https://python.sdv.u-paris.fr/index.html
3. https://python.sdv.u-paris.fr/cours-python.pdf
4. http://pythontutor.com/
5. https://www.dunod.com/sciences-techniques/programmation-en-python-pour-sciences-vie-0
6. https://www.wikimedia.fr/
8
Table des matières Table des matières
et NumFOCUS 7 qui soutient le développement de logiciels libres scientifiques et notamment l’écosystème scientifique
autour de Python.
7. https://numfocus.org/
Introduction
1. https://www.python.org/psf/
2. Nous sommes d’accord, cette notion est très relative.
3. https://www.tiobe.com/tiobe-index/
4. https://spectrum.ieee.org/the-top-programming-languages-2023
10
1.2. Conseils pour l’apprentissage de Python Chapitre 1. Introduction
1.3.2 Miniconda
Nous vous conseillons d’installer Miniconda 5 , logiciel gratuit, disponible pour Windows, Mac OS X et Linux, et qui
installera pour vous Python 3.
Avec le gestionnaire de paquets conda, fourni avec Miniconda, vous pourrez installer des modules supplémentaires
qui sont très utiles en bioinformatique (NumPy, scipy, matplotlib, pandas, Biopython), mais également Jupyter Lab qui
vous permettra d’éditer des notebooks Jupyter. Vous trouverez en ligne 6 une documentation pas-à-pas pour installer
Miniconda, Python 3 et les modules supplémentaires qui seront utilisés dans ce cours.
Pour ces derniers, le numéro à gauche indique le numéro de la ligne et sera utilisé pour faire référence à une instruction
particulière. Ce numéro n’est bien sûr là qu’à titre indicatif.
Par ailleurs, dans le cas de programmes, de contenus de fichiers ou de résultats trop longs pour être inclus dans leur
intégralité, la notation [...] indique une coupure arbitraire de plusieurs caractères ou lignes.
5. https://conda.io/miniconda.html
6. https://python.sdv.u-paris.fr/livre-dunod
pour Mac OS X :
iMac-de-pierre:Downloads$ python
Python 3.12.2 | packaged by Anaconda, Inc. | (main, Feb 27 2024, 12:57:28) [Clang 14.0.6 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
ou pour Linux :
pierre@jeera:~$ python
Python 3.12.2 | packaged by conda-forge | (main, Feb 16 2024, 20:50:58) [GCC 12.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Les blocs
• PS C:\Users\pierre> pour Windows,
• iMac-de-pierre:Downloads$ pour Mac OS X,
• pierre@jeera:~$ pour Linux.
représentent l’invite de commande de votre shell. Il se peut que vous ayez aussi le mot (base) qui indique que vous
avez un environnement conda activé. Par la suite, cette invite de commande sera représentée simplement par le caractère
$, que vous soyez sous Windows, Mac OS X ou Linux.
Le triple chevron >>> est l’invite de commande (prompt en anglais) de l’interpréteur Python. Ici, Python attend une
commande que vous devez saisir au clavier. Tapez par exemple l’instruction :
print("Hello world!")
puis, validez cette commande en appuyant sur la touche Entrée.
Python a exécuté la commande directement et a affiché le texte Hello world!. Il attend ensuite une nouvelle
instruction en affichant l’invite de l’interpréteur Python (>>>). En résumé, voici ce qui a dû apparaître sur votre écran :
7. https://fr.wikipedia.org/wiki/Shell_Unix
8. https://fr.wikipedia.org/wiki/Windows_PowerShell
Vous pouvez refaire un nouvel essai en vous servant cette fois de l’interpréteur comme d’une calculatrice :
1 >>> 1+1
2 2
3 >>> 6*3
4 18
À ce stade, vous pouvez entrer une autre commande ou bien quitter l’interpréteur Python, soit en tapant la commande
exit() puis en validant en appuyant sur la touche Entrée, soit en pressant simultanément les touches Ctrl et D sous
Linux et Mac OS X ou Ctrl et Z puis Entrée sous Windows.
En résumant, l’interpréteur fonctionne sur le modèle :
1 >>> instruction python
2 résultat
où le triple chevron correspond à l’entrée (input) que l’utilisateur tape au clavier, et l’absence de chevron en début
de ligne correspond à la sortie (output) générée par Python. Une exception se présente toutefois : lorsqu’on a une longue
ligne de code, on peut la couper en deux avec le caractère \ (backslash) pour des raisons de lisibilité :
1 >>> Voici une longue ligne de code \
2 ... décrite sur deux lignes
3 résultat
En ligne 1 on a rentré la première partie de la ligne de code. On termine par un \, ainsi Python sait que la ligne de
code n’est pas finie. L’interpréteur nous l’indique avec les trois points .... En ligne 2, on rentre la fin de la ligne de code
puis on appuie sur Entrée. À ce moment, Python nous génère le résultat. Si la ligne de code est vraiment très longue, il
est même possible de la découper en trois voire plus :
1 >>> Voici une ligne de code qui \
2 ... est vraiment très longue car \
3 ... elle est découpée sur trois lignes
4 résultat
L’interpréteur Python est donc un système interactif dans lequel vous pouvez entrer des commandes, que Python
exécutera sous vos yeux (au moment où vous validerez la commande en appuyant sur la touche Entrée).
Il existe de nombreux autres langages interprétés comme Perl 9 ou R 10 . Le gros avantage de ce type de langage est
qu’on peut immédiatement tester une commande à l’aide de l’interpréteur, ce qui est très utile pour débugger (c’est-à-dire
trouver et corriger les éventuelles erreurs d’un programme). Gardez bien en mémoire cette propriété de Python qui pourra
parfois vous faire gagner un temps précieux !
Remarque
L’extension de fichier standard des scripts Python est .py.
9. http://www.perl.org
10. http://www.r-project.org
11. https://python.sdv.u-paris.fr/livre-dunod
Pour exécuter votre script, ouvrez un shell et entrez la commande : python test.py
Vous devriez obtenir un résultat similaire à ceci :
$ python test.py
Hello world!
Si c’est bien le cas, bravo ! Vous avez exécuté votre premier programme Python.
1.8 Commentaires
Dans un script, tout ce qui suit le caractère # est ignoré par Python jusqu’à la fin de la ligne et est considéré comme
un commentaire.
Les commentaires doivent expliquer votre code dans un langage humain. L’utilisation des commentaires est rediscutée
dans le chapitre 16 Bonnes pratiques en programmation Python.
Voici un exemple :
1 # Votre premier commentaire en Python.
2 print("Hello world!")
3
4 # D'autres commandes plus utiles pourraient suivre.
Remarque
On appelle souvent à tort le caractère # « dièse ». On devrait plutôt parler de « croisillon 12 ».
Pour chaque base de la séquence ATCCGACTG, nous souhaitons effectuer deux actions : d’abord afficher la base
puis compter une base de plus. Pour indiquer cela, on décalera vers la droite ces deux instructions par rapport à la
ligne précédente (pour chaque base [...]). Ce décalage est appelé indentation et l’ensemble des lignes indentées
constitue un bloc d’instructions.
Une fois qu’on aura réalisé ces deux actions sur chaque base, on pourra passer à la suite, c’est-à-dire afficher la taille
de la séquence. Pour bien préciser que cet affichage se fait à la fin, donc une fois l’affichage puis le comptage de chaque
base terminés, la ligne correspondante n’est pas indentée (c’est-à-dire qu’elle n’est pas décalée vers la droite).
12. https://fr.wikipedia.org/wiki/Croisillon_(signe)
Pratiquement, l’indentation en Python doit être homogène (soit des espaces, soit des tabulations, mais pas un mélange
des deux). Une indentation avec 4 espaces est le style d’indentation recommandé (voir le chapitre 16 Bonnes pratiques
en programmation Python).
Si tout cela semble un peu complexe, ne vous inquiétez pas. Vous allez comprendre tous ces détails chapitre après
chapitre.
13. http://www.inforef.be/swi/python.htm
14. https://perso.limsi.fr/pointal/python:courspython3
15. https://openclassrooms.com/fr/courses/235344-apprenez-a-programmer-en-python
16. http://www.python.org
17. https://docs.python.org/fr/3/py-modindex.html
Variables
Définition
Une variable est une zone de la mémoire de l’ordinateur dans laquelle une valeur est stockée. Aux yeux du program-
meur, cette variable est définie par un nom, alors que pour l’ordinateur, il s’agit en fait d’une adresse, c’est-à-dire d’une
zone particulière de la mémoire.
En Python, la déclaration d’une variable et son initialisation (c’est-à-dire la première valeur que l’on va stocker
dedans) se font en même temps. Pour vous en convaincre, testez les instructions suivantes après avoir lancé l’interpréteur :
1 >>> x = 2
2 >>> x
3 2
Ligne 1. Dans cet exemple, nous avons déclaré, puis initialisé la variable x avec la valeur 2. Notez bien qu’en réalité,
il s’est passé plusieurs choses :
• Python a « deviné » que la variable était un entier. On dit que Python est un langage au typage dynamique.
• Python a alloué (réservé) l’espace en mémoire pour y accueillir un entier. Chaque type de variable prend plus ou
moins d’espace en mémoire. Python a aussi fait en sorte qu’on puisse retrouver la variable sous le nom x.
• Enfin, Python a assigné la valeur 2 à la variable x.
Dans d’autres langages (en C par exemple), il faut coder ces différentes étapes une par une. Python étant un langage
dit de haut niveau, la simple instruction x = 2 a suffi à réaliser les trois étapes en une fois !
Lignes 2 et 3. L’interpréteur nous a permis de connaître le contenu de la variable juste en tapant son nom. Retenez
ceci, car c’est une spécificité de l’interpréteur Python, très pratique pour chasser (debugger) les erreurs dans un
programme. En revanche, la ligne d’un script Python qui contient seulement le nom d’une variable (sans aucune autre
indication) n’affichera pas la valeur de la variable à l’écran lors de l’exécution (pour autant, cette instruction reste valide
et ne générera pas d’erreur).
Depuis la version 3.10, l’interpréteur Python a amélioré ses messages d’erreur. Il est ainsi capable de suggérer des
noms de variables existants lorsqu’on fait une faute de frappe :
16
2.2. Les types de variables Chapitre 2. Variables
Si le mot qu’on tape n’est pas très éloigné, cela fonctionne également lorsqu’on se trompe à différents endroits du
mot !
1 pharmacie = "vente de médicaments"
2 >>> farmacia
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 NameError: name 'farmacia' is not defined. Did you mean: 'pharmacie'?
Définition
Le symbole = est appelé opérateur d’affectation. Il permet d’assigner une valeur à une variable en Python. Cet
opérateur s’utilise toujours de la droite vers la gauche. Par exemple, dans l’instruction x = 2 ci-dessus, Python attribue
la valeur située à droite (ici, 2) à la variable située à gauche (ici, x). D’autres langages de programmation comme R
utilisent les symboles <- pour rendre l’affectation d’une variable plus explicite, par exemple x <- 2.
Ligne 2. Ici on a un nom de variable à gauche et à droite de l’opérateur =. Dans ce cas, on garde la règle d’aller
toujours de la droite vers la gauche. C’est donc le contenu de la variable y qui est affecté à la variable x.
Ligne 5. Comme on le verra plus bas, si on a à droite de l’opérateur = une expression, ici la soustraction 4 - 2, celle-ci
est d’abord évaluée et c’est le résultat de cette opération qui sera affecté à la variable x. On pourra noter également que
la valeur de x précédente (2) a été écrasée.
Attention
L’opérateur d’affectation = écrase systématiquement la valeur de la variable située à sa gauche si celle-ci existe déjà.
Définition
Le type d’une variable correspond à la nature de celle-ci. Les trois principaux types dont nous aurons besoin dans
un premier temps sont les entiers (integer ou int), les nombres décimaux que nous appellerons floats et les chaînes de
caractères (string ou str).
Bien sûr, il existe de nombreux autres types (par exemple, les booléens, les nombres complexes, etc.). Si vous n’êtes
pas effrayés, vous pouvez vous en rendre compte ici 1 .
1. https://docs.python.org/fr/3.12/library/stdtypes.html
Dans l’exemple précédent, nous avons stocké un nombre entier (int) dans la variable x, mais il est tout à fait possible
de stocker des floats, des chaînes de caractères (string ou str) ou de nombreux autres types de variables que nous verrons
par la suite :
1 >>> y = 3.14
2 >>> y
3 3.14
4 >>> a = "bonjour"
5 >>> a
6 'bonjour'
7 >>> b = 'salut'
8 >>> b
9 'salut'
10 >>> c = """girafe"""
11 >>> c
12 'girafe'
13 >>> d = '''lion'''
14 >>> d
15 'lion'
Remarque
Python reconnaît certains types de variables automatiquement (entier, float). Par contre, pour une chaîne de carac-
tères, il faut l’entourer de guillemets (doubles, simples, voire trois guillemets successifs doubles ou simples) afin d’indiquer
à Python le début et la fin de la chaîne de caractères.
Dans l’interpréteur, l’affichage direct du contenu d’une chaîne de caractères se fait avec des guillemets simples, quel
que soit le type de guillemets utilisé pour définir la chaîne de caractères.
En Python, comme dans la plupart des langages de programmation, c’est le point qui est utilisé comme séparateur
décimal. Ainsi, 3.14 est un nombre reconnu comme un float en Python alors que ce n’est pas le cas de 3,14.
Il existe également des variables de type booléen. Un booléen 2 est une variable qui ne prend que deux valeurs : Vrai
ou Faux. En python, on utilise pour cela les deux mots réservés True et False :
1 >>> var = True
2 >>> var2 = False
3 >>> var
4 True
5 >>> var2
6 False
Nous verrons l’utilité des booléens dans les chapitres 5 Boucles et 6 Tests.
2.3 Nommage
Le nom des variables en Python peut être constitué de lettres minuscules (a à z), de lettres majuscules (A à Z), de
nombres (0 à 9) ou du caractère souligné (_). Vous ne pouvez pas utiliser d’espace dans un nom de variable.
Par ailleurs, un nom de variable ne doit pas débuter par un chiffre et il n’est pas recommandé de le faire débuter par
le caractère _ (sauf cas très particuliers).
De plus, il faut absolument éviter d’utiliser un mot « réservé » par Python comme nom de variable (par exemple :
print, range, for, from, etc.).
Dans la mesure du possible, il est conseillé de mettre des noms de variables explicites. Sauf dans de rares cas que
nous expliquerons plus tard dans le cours, évitez les noms de variables à une lettre.
Enfin, Python est sensible à la casse, ce qui signifie que les variables TesT, test et TEST sont différentes.
2. https://fr.wikipedia.org/wiki/Bool%C3%A9en
1 >>> 1e6
2 1000000.0
3 >>> 3.12e-3
4 0.00312
On appelle cela écriture ou notation scientifique. On pourra noter deux choses importantes :
• 1e6 ou 3.12e-3 n’implique pas l’utilisation du nombre exponentiel e, mais signifie 1 × 106 ou 3.12 × 10−3 respec-
tivement ;
• même si on ne met que des entiers à gauche et à droite du symbole e (comme dans 1e6), Python génère systéma-
tiquement un float.
Enfin, vous avez sans doute constaté qu’il est parfois pénible d’écrire des nombres composés de beaucoup de chiffres,
par exemple le nombre d’Avogradro 6.02214076 × 1023 ou le nombre d’humains sur Terre 3 8094752749 au 5 mars 2024
à 19h34. Pour s’y retrouver, Python autorise l’utilisation du caractère « souligné » (ou underscore) _ pour séparer des
groupes de chiffres. Par exemple :
1 >>> avogadro_number = 6.022_140_76e23
2 >>> print(avogadro_number)
3 6.02214076e+23
4 >>> humans_on_earth = 8_094_752_749
5 >>> print(humans_on_earth)
6 8094752749
Dans ces exemples, le caractère _ (underscore ou « souligné ») est utilisé pour séparer des groupes de trois chiffres,
mais on peut faire ce qu’on veut :
1 >>> print(80_94_7527_49)
2 8094752749
2.5 Opérations
2.5.1 Opérations sur les types numériques
Les quatre opérations arithmétiques de base se font de manière simple sur les types numériques (nombres entiers et
floats) :
1 >>> x = 45
2 >>> x + 2
3 47
4 >>> x - 2
5 43
6 >>> x * 3
7 135
8 >>> y = 2.5
9 >>> x - y
10 42.5
11 >>> (x * 10) + y
12 452.5
Remarquez toutefois que si vous mélangez les types entiers et floats, le résultat est renvoyé comme un float (car ce
type est plus général). Par ailleurs, l’utilisation de parenthèses permet de gérer les priorités.
L’opérateur / effectue une division. Contrairement aux opérateurs +, - et *, celui-ci renvoie systématiquement un
float :
1 >>> 3 / 4
2 0.75
3 >>> 2.5 / 2
4 1.25
5 >>> 6 / 3
6 2.0
7 >>> 10 / 2
8 5.0
3. https://thepopulationproject.org/
Pour obtenir le quotient et le reste d’une division entière (voir ici 4 pour un petit rappel sur la division entière), on
utilise respectivement les symboles // et modulo % :
1 >>> 5 // 4
2 1
3 >>> 5 % 4
4 1
5 >>> 8 // 4
6 2
7 >>> 8 % 4
8 0
Les symboles +, -, *, /, **, // et % sont appelés opérateurs, car ils réalisent des opérations sur les variables.
Enfin, il existe des opérateurs « combinés » qui effectue une opération et une affectation en une seule étape :
1 >>> i = 0
2 >>> i = i + 1
3 >>> i
4 1
5 >>> i += 1
6 >>> i
7 2
8 >>> i += 2
9 >>> i
10 4
L’opérateur += effectue une addition puis affecte le résultat à la même variable. Cette opération s’appelle une «
incrémentation ».
Les opérateurs -=, *= et /= se comportent de manière similaire pour la soustraction, la multiplication et la division.
Attention
Vous observez que les opérateurs + et * se comportent différemment s’il s’agit d’entiers ou de chaînes de caractères.
Ainsi, l’opération 2 + 2 est une addition alors que l’opération "2" + "2" est une concaténation. On appelle ce compor-
tement redéfinition des opérateurs. Nous serons amenés à revoir cette notion dans le chapitre 24 Avoir plus la classe
avec les objets (en ligne).
4. https://fr.wikipedia.org/wiki/Division_euclidienne
Notez que Python vous donne des informations dans son message d’erreur. Dans le second exemple, il indique que
vous devez utiliser une variable de type str, c’est-à-dire une chaîne de caractères et pas un int, c’est-à-dire un entier.
Attention
Pour Python, la valeur 2 (nombre entier) est différente de 2.0 (float) et est aussi différente de '2' (chaîne de
caractères).
On verra au chapitre 7 Fichiers que ces conversions sont essentielles. En effet, lorsqu’on lit ou écrit des nombres dans
un fichier, ils sont considérés comme du texte, donc des chaînes de caractères.
Toute conversion d’une variable d’un type en un autre est appelé casting en anglais, il se peut que vous croisiez ce
terme si vous consultez d’autres ressources.
Par rapport à la discussion de la rubrique précédente, min() et max() sont des exemples de fonctions prenant plusieurs
arguments. En Python, quand une fonction prend plusieurs arguments, on doit les séparer par une virgule. min() et max()
prennent en argument autant d’entiers et de floats que l’on veut, mais il en faut au moins deux.
2.10 Exercices
Conseil
Pour ces exercices, utilisez l’interpréteur Python.
• (1 + 28 ) × 5
• (2 + 18 )7
Affichage
Ligne 1. On a utilisé l’instruction print() classiquement en passant la chaîne de caractères "Hello world!" en
argument.
Ligne 3. On a ajouté un second argument end="", en précisant le mot-clé end. Nous aborderons les arguments
par mot-clé dans le chapitre 10 Fonctions. Pour l’instant, dites-vous que cela modifie le comportement par défaut des
fonctions.
Ligne 4. L’effet de l’argument end="" est que les trois chevrons >>> se retrouvent collés après la chaîne de caractères
"Hello world!".
Une autre manière de s’en rendre compte est d’utiliser deux fonctions print() à la suite. Dans la portion de code
suivante, le caractère « ; » sert à séparer plusieurs instructions Python sur une même ligne :
1 >>> print("Hello") ; print("Joe")
2 Hello
3 Joe
4 >>> print("Hello", end="") ; print("Joe")
5 HelloJoe
6 >>> print("Hello", end=" ") ; print("Joe")
7 Hello Joe
La fonction print() peut également afficher le contenu d’une variable quel que soit son type. Par exemple, pour un
entier :
1 >>> var = 3
2 >>> print(var)
3 3
24
3.2. Messages d’erreur Chapitre 3. Affichage
Il est également possible d’afficher le contenu de plusieurs variables (quel que soit leur type) en les séparant par des
virgules :
1 >>> x = 32
2 >>> nom = "John"
3 >>> print(nom, "a", x, "ans")
4 John a 32 ans
Python a écrit une phrase complète en remplaçant les variables x et nom par leur contenu. Vous remarquerez que
pour afficher plusieurs éléments de texte sur une seule ligne, nous avons utilisé le séparateur « , » entre les différents
éléments. Python a également ajouté un espace à chaque fois que l’on utilisait le séparateur « , ». On peut modifier ce
comportement en passant à la fonction print() l’argument par mot-clé sep :
1 >>> x = 32
2 >>> nom = "John"
3 >>> print(nom, "a", x, "ans", sep="")
4 Johna32ans
5 >>> print(nom, "a", x, "ans", sep="-")
6 John-a-32-ans
7 >>> print(nom, "a", x, "ans", sep="_")
8 John_a_32_ans
Pour afficher deux chaînes de caractères l’une à côté de l’autre, sans espace, on peut soit les concaténer, soit utiliser
l’argument par mot-clé sep avec une chaîne de caractères vide :
1 >>> ani1 = "chat"
2 >>> ani2 = "souris"
3 >>> print(ani1, ani2)
4 chat souris
5 >>> print(ani1 + ani2)
6 chatsouris
7 >>> print(ani1, ani2, sep="")
8 chatsouris
Vous avez sans doute repéré l’oubli d’une parenthèse fermante en ligne 1. Lorsqu’on lance le script, on obtient :
$ python test.py
File "test.py", line 1
print("chat"
^
SyntaxError: '(' was never closed
Comment doit-on lire ce message d’erreur ? Et bien cela se fait toujours du bas vers le haut. Le message s’appelle
une Traceback et contient plusieurs types d’information :
• Tout en bas : On a le type d’erreur qui a été généré (on verra plus tard que cela s’appelle en réalité une exception).
Ici une erreur de syntaxe appelée SyntaxError. Puis sur la même ligne, un indice supplémentaire (ici l’absence
d’une parenthèse).
• Un peu plus haut : une description de l’erreur où on voit la parenthèse ouverte qui n’a jamais été fermée.
• Encore plus haut : le numéro de ligne dans le code où l’erreur a été détectée.
Avec cette Traceback il devient facile de corriger l’erreur.
Attention
Dans les vieilles versions de Python (< 3.10), la même erreur conduisait à une Traceback beaucoup moins claire :
$ python3.5 test.py
File "test.py", line 2
print("souris")
^
SyntaxError: invalid syntax
L’interpréteur vous indiquait une erreur de syntaxe en ligne 2 alors que l’oubli de parenthèse était en ligne 1 ! Pour
cette raison, utilisez dans la mesure du possible une version récente de Python (3.12 ou 3.13).
Si nous corrigeons la ligne 1 en mettant la parenthèse finale et que nous relançons le script, nous aurons cette fois-ci
une erreur due à une division par zéro :
$ python test.py
chat
souris
Traceback (most recent call last):
File "test.py", line 3, in <module>
print(1 / 0)
~~^~~
ZeroDivisionError: division by zero
Si nous corrigeons cette erreur en ligne 3 en évitant la division par zéro (par exemple en mettant print(1 / 1)),
l’exécution donnera un autre message d’erreur dû à la ligne 4 où la transformation d’une chaîne de caractères en entier
n’est pas possible :
$ python test.py
chat
souris
1
Traceback (most recent call last):
File "test.py", line 4, in <module>
print(int("deux"))
^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'deux'
À nouveau dans ces deux derniers exemples de Traceback, vous voyez qu’on a la même construction. Tout en bas, le
type d’erreur, puis en remontant une description du problème et le numéro de ligne où l’erreur a été détectée.
Conseil
Il est important de bien lire chaque message d’erreur généré par Python. En général, la clé du problème est mentionnée
dans ce message vous donnant des éléments pour le corriger.
Définition
L’écriture formatée est un mécanisme permettant d’afficher des variables avec un format précis, par exemple justifiées
à gauche ou à droite, ou encore avec un certain nombre de décimales pour les floats. L’écriture formatée est incontournable
lorsqu’on veut créer des fichiers organisés en « belles colonnes » comme par exemple les fichiers PDB (pour en savoir
plus sur ce format, reportez-vous à l’annexe A Quelques formats de données en biologie).
Depuis la version 3.6, Python a introduit les f-strings pour mettre en place l’écriture formatée que nous allons décrire
en détail dans cette rubrique. Il existe d’autres manières pour formater des chaînes de caractères qui étaient utilisées
avant la version 3.6, nous expliquons cela dans le chapitre 26 Remarques complémentaires (en ligne). Toutefois, nous
vous conseillons vivement l’utilisation des f-strings si vous débutez l’apprentissage de Python. Il est inutile d’apprendre
les anciennes manières.
Définition
f-string est le diminutif de formatted string literals. Mais encore ? Dans le chapitre précédent, nous avons vu les
chaînes de caractères ou encore strings qui étaient représentées par un texte entouré de guillemets simples ou doubles.
Par exemple :
1 "Ceci est une chaîne de caractères"
L’équivalent en f-string est la même chaîne de caractères précédée du caractère f sans espace entre les deux :
1 f"Ceci est une chaîne de caractères"
Ce caractère f avant les guillemets va indiquer à Python qu’il s’agit d’une f-string mettant en place le mécanisme de
l’écriture formatée, contrairement à une string normale.
Nous expliquons plus en détail dans le chapitre 11 Plus sur les chaînes de caractères pourquoi on doit mettre ce f et
quel est le mécanisme sous-jacent.
Il suffit de passer un nom de variable au sein de chaque couple d’accolades et Python les remplace par leur contenu.
La syntaxe apparait plus lisible que l’équivalent vu précédemment :
1 >>> print(nom, "a", x, "ans")
2 John a 32 ans
Bien sûr, il ne faut pas omettre le f avant le premier guillemet, sinon Python prendra cela pour une chaîne de
caractères normale et ne mettra pas en place le mécanisme de remplacement entre les accolades :
1 >>> print("{nom} a {x} ans")
2 {nom} a {x} ans
Remarque
Une variable est utilisable plus d’une fois pour une f-string donnée :
1 >>> var = "to"
2 >>> print(f"{var} et {var} font {var}{var}")
3 to et to font toto
4 >>>
Enfin, il est possible de mettre entre les accolades des valeurs numériques ou des chaînes de caractères :
1 >>> print(f"J'affiche l'entier {10} et le float {3.14}")
2 J'affiche l'entier 10 et le float 3.14
3 >>> print(f"J'affiche la chaine {'Python'}")
4 J'affiche la chaine Python
Même si cela ne présente que peu d’intérêt pour l’instant, il s’agit d’une commande Python parfaitement valide. Nous
verrons des exemples plus pertinents par la suite. Cela fonctionne avec n’importe quel type de variable (entiers, chaînes
de caractères, floats, etc.). Attention toutefois pour les chaînes de caractères, utilisez des guillemets simples au sein des
accolades si vous définissez votre f-string avec des guillemets doubles.
Le résultat obtenu présente trop de décimales (seize dans le cas présent). Pour écrire le résultat plus lisiblement, vous
pouvez spécifier dans les accolades {} le format qui vous intéresse. Dans le cas présent, vous voulez formater un float
pour l’afficher avec deux puis trois décimales :
1 >>> print(f"La proportion de GC est {prop_GC:.2f}")
2 La proportion de GC est 0.48
3 >>> print(f"La proportion de GC est {prop_GC:.3f}")
4 La proportion de GC est 0.478
Les instructions étant longues dans cet exemple, nous avons coupé chaque chaîne de caractères sur deux lignes. Il faut
mettre à chaque fois le f pour préciser à Python qu’on utilise une f-string. Les ... indiquent que l’interpréteur attend
que l’on ferme la parenthèse du print entamé sur la ligne précédente. Nous reverrons cette syntaxe dans le chapitre 11
Plus sur les chaînes de caractères.
Enfin, il est possible de préciser sur combien de caractères vous voulez qu’un résultat soit écrit et comment se fait
l’alignement (à gauche, à droite), ou si vous voulez centrer le texte. Dans la portion de code suivante, le caractère ; sert
de séparateur entre les instructions sur une même ligne :
Notez que > spécifie un alignement à droite, < spécifie un alignement à gauche et ^ spécifie un alignement centré.
Il est également possible d’indiquer le caractère qui servira de remplissage lors des alignements (l’espace est le caractère
par défaut).
Ce formatage est également possible sur des chaînes de caractères avec la lettre s (comme string) :
1 >>> print("atom HN") ; print("atom HDE1")
2 atom HN
3 atom HDE1
4 >>> print(f"atom {'HN':>4s}") ; print(f"atom {'HDE1':>4s}")
5 atom HN
6 atom HDE1
Vous voyez tout de suite l’énorme avantage de l’écriture formatée. Elle vous permet d’écrire en colonnes parfaitement
alignées. Nous verrons que ceci est très pratique si l’on veut écrire les coordonnées des atomes d’une molécule au format
PDB (pour en savoir plus sur ce format, reportez-vous à l’annexe A Quelques formats de données en biologie).
Pour les floats, il est possible de combiner le nombre de caractères à afficher avec le nombre de décimales :
1 >>> print(f"{perc_GC:7.3f}")
2 47.804
3 >>> print(f"{perc_GC:10.3f}")
4 47.804
L’instruction 7.3f signifie que l’on souhaite écrire un float avec 3 décimales et formaté sur 7 caractères (par défaut
justifiés à droite). L’instruction 10.3f fait la même chose sur 10 caractères. Remarquez que le séparateur décimal .
compte pour un caractère. De même, si on avait un nombre négatif, le signe - compterait aussi pour un caractère.
Une remarque importante, si on ne met pas de variable à formater entre les accolades dans une f-string, cela conduit
à une erreur :
1 >>> print(f"accolades sans variable {}")
2 File "<stdin>", line 1
3 SyntaxError: f-string: empty expression not allowed
Enfin, il est important de bien comprendre qu’une f-string est indépendante de la fonction print(). Si on donne
une f-string à la fonction print(), Python évalue d’abord la f-string et c’est la chaîne de caractères qui en résulte qui
est affichée à l’écran. Tout comme dans l’instruction print(5*5), c’est d’abord la multiplication (5*5) qui est évaluée,
puis son résultat qui est affiché à l’écran. On peut s’en rendre compte de la manière suivante dans l’interpréteur :
1 >>> f"{perc_GC:10.3f}"
2 ' 47.804'
3 >>> type(f"{perc_GC:10.3f}")
4 <class 'str'>
Python considère le résultat de l’instruction f"{perc_GC:10.3f}" comme une chaîne de caractères et la fonction
type() nous le confirme.
Nous aurons l’occasion de revenir sur cette fonctionnalité au fur et à mesure de ce cours.
Les possibilités offertes par les f-strings sont nombreuses. Pour vous y retrouver dans les différentes options de
formatage, nous vous conseillons de consulter ce mémo 1 (en anglais).
Il est également possible de définir le nombre de chiffres après la virgule. Dans l’exemple ci-dessous, on affiche un
nombre avec aucun, 3 et 6 chiffres après la virgule :
1 >>> avogadro_number = 6.022_140_76e23
2 >>> print(f"{avogadro_number:.0e}")
3 6e+23
4 >>> print(f"{avogadro_number:.3e}")
5 6.022e+23
6 >>> print(f"{avogadro_number:.6e}")
7 6.022141e+23
3.5 Exercices
Conseil
Pour les exercices 2 à 6, utilisez l’interpréteur Python.
3.5.2 Poly-A
Générez une chaîne de caractères représentant un brin d’ADN poly-A (c’est-à-dire qui ne contient que des bases A)
de 20 bases de longueur, sans taper littéralement toutes les bases.
Ici, 2 est le résultat de la division entière du numérateur par le dénominateur et 1 est le reste de la division entière du
numérateur par le dénominateur.
Faites de même pour les fractions suivantes :
9 23 21 7
, , et
4 5 8 2
Listes
4.1 Définition
Définition
Une liste est une structure de données qui contient une collection d’objets Python. Il s’agit d’un nouveau type par
rapport aux entiers, float, booléens et chaînes de caractères que nous avons vus jusqu’à maintenant. On parle aussi
d’objet séquentiel en ce sens qu’il contient une séquence d’autres objets.
Python autorise la construction de liste contenant des valeurs de types différents (par exemple entier et chaîne de
caractères), ce qui leur confère une grande flexibilité. Une liste est déclarée par une série de valeurs (n’oubliez pas les
guillemets, simples ou doubles, s’il s’agit de chaînes de caractères) séparées par des virgules, et le tout encadré par des
crochets. En voici quelques exemples :
1 >>> animaux = ["girafe", "tigre", "singe", "souris"]
2 >>> tailles = [5, 2.5, 1.75, 0.15]
3 >>> mixte = ["girafe", 5, "souris", 0.15]
4 >>> animaux
5 ['girafe', 'tigre', 'singe', 'souris']
6 >>> tailles
7 [5, 2.5, 1.75, 0.15]
8 >>> mixte
9 ['girafe', 5, 'souris', 0.15]
Lorsque l’on affiche une liste, Python la restitue telle qu’elle a été saisie.
4.2 Utilisation
Un des gros avantages d’une liste est que vous accédez à ses éléments par leur position. Ce numéro est appelé indice
(ou index) de la liste.
liste : ["girafe", "tigre", "singe", "souris"]
indice : 0 1 2 3
Soyez très attentif au fait que les indices d’une liste de n éléments commencent à 0 et se terminent à n − 1. Voyez
l’exemple suivant :
32
4.3. Opération sur les listes Chapitre 4. Listes
Par conséquent, si on appelle l’élément d’indice 4 de notre liste, Python renverra un message d’erreur :
1 >>> animaux[4]
2 Traceback (innermost last):
3 File "<stdin>", line 1, in ?
4 IndexError: list index out of range
Remarque
La notion de méthode est introduite dans la rubrique Note sur le vocabulaire et la syntaxe à la fin de ce chapitre.
puis lui ajouter deux éléments, l’un après l’autre, d’abord avec la concaténation :
1 >>> liste1 = liste1 + [15]
2 >>> liste1
3 [15]
4 >>> liste1 = liste1 + [-5]
5 >>> liste1
6 [15, -5]
Dans cet exemple, nous ajoutons des éléments à une liste en utilisant l’opérateur de concaténation + ou la méthode
.append().
Conseil
Nous vous conseillons dans ce cas précis d’utiliser la méthode .append(), dont la syntaxe est plus élégante.
Nous reverrons en détail la méthode .append() dans le chapitre 12 Plus sur les listes.
ou encore :
liste : ["A", "B", "C", "D", "E", "F"]
indice positif : 0 1 2 3 4 5
indice négatif : -6 -5 -4 -3 -2 -1
Les indices négatifs reviennent à compter à partir de la fin. Leur principal avantage est que vous pouvez accéder au
dernier élément d’une liste à l’aide de l’indice -1 sans pour autant connaître la longueur de cette liste. L’avant-dernier
élément a lui l’indice -2, l’avant-avant dernier l’indice -3, etc. :
1 >>> animaux = ["girafe", "tigre", "singe", "souris"]
2 >>> animaux[-1]
3 'souris'
4 >>> animaux[-2]
5 'singe'
Pour accéder au premier élément de la liste avec un indice négatif, il faut par contre connaître le bon indice :
1 >>> animaux[-4]
2 'girafe'
4.5 Tranches
Un autre avantage des listes est la possibilité de sélectionner une partie d’une liste en utilisant un indiçage construit
sur le modèle [m:n+1] pour récupérer tous les éléments, du émième au énième (de l’élément m inclu à l’élément n+1
exclu). On dit alors qu’on récupère une tranche de la liste, par exemple :
1 >>> animaux = ["girafe", "tigre", "singe", "souris"]
2 >>> animaux[0:2]
3 ['girafe', 'tigre']
4 >>> animaux[0:3]
5 ['girafe', 'tigre', 'singe']
6 >>> animaux[0:]
7 ['girafe', 'tigre', 'singe', 'souris']
8 >>> animaux[:]
9 ['girafe', 'tigre', 'singe', 'souris']
10 >>> animaux[1:]
11 ['tigre', 'singe', 'souris']
12 >>> animaux[1:-1]
13 ['tigre', 'singe']
Notez que lorsqu’aucun indice n’est indiqué à gauche ou à droite du symbole deux-points :, Python prend par défaut
tous les éléments depuis le début ou tous les éléments jusqu’à la fin respectivement.
On peut aussi préciser le pas en ajoutant un symbole deux-points supplémentaire et en indiquant le pas par un entier :
Finalement, on se rend compte que l’accès au contenu d’une liste fonctionne sur le modèle liste[début:fin:pas].
La commande list(range(10)) a généré une liste contenant tous les nombres entiers de 0 inclus à 10 exclu. Nous
verrons l’utilisation de la fonction range() toute seule dans le chapitre 5 Boucles et comparaisons.
Dans l’exemple ci-dessus, la fonction range() a pris un argument, mais elle peut également prendre deux ou trois
arguments, voyez plutôt :
1 >>> list(range(0, 5))
2 [0, 1, 2, 3, 4]
3 >>> list(range(15, 20))
4 [15, 16, 17, 18, 19]
5 >>> list(range(0, 1000, 200))
6 [0, 200, 400, 600, 800]
7 >>> list(range(2, -2, -1))
8 [2, 1, 0, -1]
L’instruction range() fonctionne sur le modèle range([début,] fin[, pas]). Les arguments entre crochets sont
optionnels. Pour obtenir une liste de nombres entiers, il faut l’utiliser systématiquement avec la fonction list().
Enfin, prenez garde aux arguments optionnels par défaut (0 pour début et 1 pour pas) :
1 >>> list(range(10,0))
2 []
Ici la liste est vide car Python a pris la valeur du pas par défaut qui est de 1. Ainsi, si on commence à 10 et qu’on
avance par pas de 1, on ne pourra jamais atteindre 0. Python génère ainsi une liste vide. Pour éviter ça, il faudrait, par
exemple, préciser un pas de -1 pour obtenir une liste d’entiers décroissants :
1 >>> list(range(10,0,-1))
2 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
Dans cet exemple, chaque sous-liste contient une catégorie d’animal et le nombre d’animaux pour chaque catégorie.
Pour accéder à un élément de la liste, on utilise l’indiçage habituel :
1 >>> savane[1]
2 ['tigre', 2]
On verra un peu plus loin qu’il existe en Python des dictionnaires qui sont également très pratiques pour stocker
de l’information structurée. On verra aussi qu’il existe un module nommé NumPy qui permet de créer des listes ou des
tableaux de nombres (vecteurs et matrices) et de les manipuler.
Même si en théorie ces fonctions peuvent prendre en argument une liste de strings, on les utilisera la plupart du temps
avec des types numériques (liste d’entiers et / ou de floats).
Nous avions déjà croisé min(), max() dans le chapitre 2 Variables. Ces deux fonctions pouvaient prendre plusieurs
arguments entiers et / ou floats, par exemple :
1 >>> min(3, 4)
2 3
Attention toutefois à ne pas mélanger entiers et floats d’une part avec une liste d’autre part, car cela renvoie une
erreur :
1 >>> min(liste1, 3, 4)
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 TypeError: '<' not supported between instances of 'int' and 'list'
Soit on passe plusieurs entiers et / ou floats en argument, soit on passe une liste unique.
Comme vous voyez en ligne 8, la modification de liste1 a modifié également liste2. Cela vient du fait que Python
a effectué la copie de liste en ligne 5 par référence. Ainsi, les deux listes pointent vers le même objet dans la mémoire.
Pour contrer ce problème et faire en sorte que liste2 soit bien distincte de liste1, on peut utiliser la fonction list() :
1 >>> liste1 = list(range(5))
2 >>> liste1
3 [0, 1, 2, 3, 4]
4 >>> liste2 = list(liste1)
5 >>> liste2
6 [0, 1, 2, 3, 4]
7 >>> liste1[3] = -50
8 >>> liste1
9 [0, 1, 2, -50, 4]
10 >>> liste2
11 [0, 1, 2, 3, 4]
Attention
Cette astuce ne fonctionne que pour des listes à une dimension (c’est-à-dire pour des listes qui ne contiennent que
des éléments de type simple comme des entiers, des floats, des chaînes de caractères et des booléens), mais pas pour des
listes de listes. Le chapitre 12 Plus sur les listes explique l’origine de ce comportement et comment s’en sortir à tous les
coups.
la méthode .append() est liée à liste1 qui est un objet de type liste. La méthode modifie l’objet liste en lui
ajoutant un élément.
Nous aurons de nombreuses occasions de revoir cette notation objet.méthode().
4.12 Exercices
Conseil
Pour ces exercices, utilisez l’interpréteur Python.
Prédisez le comportement de chaque instruction ci-dessous, sans les recopier dans un script ni dans l’interpréteur
Python :
• print(liste1[2])
• print(liste1[var])
• print(liste1[var2])
• print(liste1["var"])
Lorsqu’une instruction produit une erreur, identifiez pourquoi.
4.12.3 Saisons
Créez quatre listes hiver, printemps, ete et automne contenant les mois correspondants à ces saisons. Créez
ensuite une liste saisons contenant les listes hiver, printemps, ete et automne. Prévoyez ce que renvoient les
instructions suivantes, puis vérifiez-le dans l’interpréteur :
1. saisons[2]
2. saisons[1][0]
3. saisons[1:2]
4. saisons[:][1]. Comment expliquez-vous ce dernier résultat ?
Boucles et comparaisons
Si votre liste ne contient que 4 éléments, ceci est encore faisable mais imaginez qu’elle en contienne 100 voire 1 000 !
Pour remédier à cela, il faut utiliser les boucles . Regardez l’exemple suivant :
1 >>> animaux = ["girafe", "tigre", "singe", "souris"]
2 >>> for animal in animaux:
3 ... print(animal)
4 ...
5 girafe
6 tigre
7 singe
8 souris
39
Chapitre 5. Boucles et comparaisons 5.1. Boucles for
Nous verrons plus loin que la variable d’itération peut être de n’importe quel type selon la liste parcourue. En Python,
une boucle itère la plupart du temps sur un objet dit séquentiel (c’est-à-dire un objet constitué d’autres objets) tel
qu’une liste. De tels objets sont dits itérables car on peut effectuer une boucle dessus. Nous verrons aussi plus tard
d’autres objets séquentiels sur lesquels on peut itérer dans une boucle.
D’ores et déjà, prêtez attention au caractère deux-points « : » à la fin de la ligne débutant par for. Cela signifie
que la boucle for attend un bloc d’instructions, en l’occurrence toutes les instructions que Python répétera à chaque
itération de la boucle. On appelle ce bloc d’instructions le corps de la boucle. Comment indique-t-on à Python où ce
bloc commence et se termine ? Cela est signalé uniquement par l’indentation, c’est-à-dire le décalage vers la droite de
la (ou des) ligne(s) du bloc d’instructions.
Remarque
Les notions de bloc d’instruction et d’indentations ont été introduites dans le chapitre 1 Introduction.
Dans l’exemple suivant, le corps de la boucle contient deux instructions (ligne 2 et ligne 3) car elles sont indentées
par rapport à la ligne débutant par for :
1 for animal in animaux:
2 print(animal)
3 print(animal*2)
4 print("C'est fini")
La ligne 4 ne fait pas partie du corps de la boucle car elle est au même niveau que le for (c’est-à-dire non indentée par
rapport au for). Notez également que chaque instruction du corps de la boucle doit être indentée de la même manière
(ici 4 espaces).
Remarque
Outre une meilleure lisibilité, les deux-points et l’indentation sont formellement requis en Python. Même si on
peut indenter comme on veut (plusieurs espaces ou plusieurs tabulations, mais pas une combinaison des deux), les
développeurs recommandent l’utilisation de quatre espaces. Vous pouvez consulter à ce sujet le chapitre 16 Bonnes
pratiques de programmation en Python.
Faites en sorte de configurer votre éditeur de texte favori de façon à écrire quatre espaces lorsque vous tapez sur la
touche Tab (tabulation).
Dans les exemples ci-dessus, nous avons exécuté une boucle en itérant directement sur une liste. Une tranche d’une
liste étant elle même une liste, on peut également itérer dessus :
1 >>> animaux = ["girafe", "tigre", "singe", "souris"]
2 >>> for animal in animaux[1:3]:
3 ... print(animal)
4 ...
5 tigre
6 singe
On a vu que les boucles for pouvaient utiliser une liste contenant des chaînes de caractères, mais elles peuvent tout
aussi bien utiliser des listes contenant des entiers (ou n’importe quel type de variable) :
1. https://docs.python.org/fr/3/library/stdtypes.html#typesseq-range
La variable i prendra les valeurs successives 0, 1, 2 et 3 et on accèdera à chaque élément de la liste animaux par son
indice (i.e. animaux[i]). Notez à nouveau le nom i de la variable d’itération car on itère sur les indices.
Quand utiliser l’une ou l’autre des deux méthodes ? La plus efficace est celle qui réalise les itérations directement
sur les éléments :
1 >>> animaux = ["girafe", "tigre", "singe", "souris"]
2 >>> for animal in animaux:
3 ... print(animal)
4 ...
5 girafe
6 tigre
7 singe
8 souris
Remarque
Dans le chapitre 18 Jupyter et ses notebooks, nous mesurerons le temps d’exécution de ces deux méthodes pour vous
montrer que l’itération sur les éléments est la méthode la plus rapide.
Toutefois, il se peut qu’au cours d’une boucle vous ayez besoin des indices, auquel cas vous devrez itérer sur les
indices :
1 >>> animaux = ["girafe", "tigre", "singe", "souris"]
2 >>> for i in range(len(animaux)):
3 ... print(f"L'animal {i} est un(e) {animaux[i]}")
4 ...
5 L'animal 0 est un(e) girafe
6 L'animal 1 est un(e) tigre
7 L'animal 2 est un(e) singe
8 L'animal 3 est un(e) souris
Enfin, Python possède la fonction enumerate() qui vous permet d’itérer sur les indices et les éléments eux-mêmes :
1 >>> animaux = ["girafe", "tigre", "singe", "souris"]
2 >>> for i, animal in enumerate(animaux):
3 ... print(f"L'animal {i} est un(e) {animal}")
4 ...
5 L'animal 0 est un(e) girafe
6 L'animal 1 est un(e) tigre
7 L'animal 2 est un(e) singe
8 L'animal 3 est un(e) souris
5.2 Comparaisons
Avant de passer aux boucles while, abordons tout de suite les comparaisons. Celles-ci seront reprises dans le chapitre
6 Tests.
Python est capable d’effectuer toute une série de comparaisons entre le contenu de deux variables, telles que :
1 >>> x = 5
2 >>> x == 5
3 True
4 >>> x > 10
5 False
6 >>> x < 10
7 True
Python renvoie la valeur True si la comparaison est vraie et False si elle est fausse. True et False sont des booléens
comme nous avions vu au chapitre 2 Variables.
Faites bien attention à ne pas confondre l’opérateur d’affectation = qui affecte une valeur à une variable et
l’opérateur de comparaison == qui compare les valeurs de deux variables.
Vous pouvez également effectuer des comparaisons sur des chaînes de caractères.
1 >>> animal = "tigre"
2 >>> animal == "tig"
3 False
4 >>> animal != "tig"
5 True
6 >>> animal == "tigre"
7 True
Dans le cas des chaînes de caractères, a priori seuls les tests == et != ont un sens. En fait, on peut aussi utiliser les
opérateurs <, >, <= et >=. Dans ce cas, l’ordre alphabétique est pris en compte, par exemple :
1 >>> "a" < "b"
2 True
"a" est inférieur à "b" car le caractère a est situé avant le caractère b dans l’ordre alphabétique. En fait, c’est
l’ordre ASCII 2 des caractères qui est pris en compte (à chaque caractère correspond un code numérique), on peut donc
aussi comparer des caractères spéciaux (comme # ou ~) entre eux. Enfin, on peut comparer des chaînes de caractères de
plusieurs caractères :
1 >>> "ali" < "alo"
2 True
3 >>> "abb" < "ada"
4 True
Dans ce cas, Python compare les deux chaînes de caractères, caractère par caractère, de la gauche vers la droite (le
premier caractère avec le premier, le deuxième avec le deuxième, etc). Dès qu’un caractère est différent entre l’une et
l’autre des deux chaînes, il considère que la chaîne la plus petite est celle qui présente le caractère ayant le plus petit
code ASCII (les caractères suivants de la chaîne de caractères sont ignorés dans la comparaison), comme dans l’exemple
"abb" < "ada" ci-dessus.
Remarquez qu’il est encore une fois nécessaire d’indenter le bloc d’instructions correspondant au corps de la boucle
(ici, les instructions lignes 3 et 4).
2. http://fr.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange
Une boucle while nécessite généralement trois éléments pour fonctionner correctement :
1. Initialisation de la variable d’itération avant la boucle (ligne 1).
2. Test de la variable d’itération associée à l’instruction while (ligne 2).
3. Mise à jour de la variable d’itération dans le corps de la boucle (ligne 4).
Faites bien attention aux tests et à l’incrémentation que vous utilisez, car une erreur mène souvent à des « boucles
infinies » qui ne s’arrêtent jamais. Vous pouvez néanmoins toujours stopper l’exécution d’un script Python à l’aide de la
combinaison de touches Ctrl-C (c’est-à-dire en pressant simultanément les touches Ctrl et C). Par exemple :
1 i = 0
2 while i < 10:
3 print("Le Python c'est cool !")
Ici, nous avons omis de mettre à jour la variable i dans le corps de la boucle. Par conséquent, la boucle ne s’arrêtera
jamais (sauf en pressant Ctrl-C) puisque la condition i < 10 sera toujours vraie.
La boucle while combinée à la fonction input() peut s’avérer commode lorsqu’on souhaite demander à l’utilisateur
une valeur numérique. Par exemple :
1 >>> i = 0
2 >>> while i < 10:
3 ... reponse = input("Entrez un entier supérieur à 10 : ")
4 ... i = int(reponse)
5 ...
6 Entrez un entier supérieur à 10 : 4
7 Entrez un entier supérieur à 10 : -3
8 Entrez un entier supérieur à 10 : 15
9 >>> i
10 15
La fonction input() prend en argument un message (sous la forme d’une chaîne de caractères), demande à l’utilisateur
d’entrer une valeur et renvoie celle-ci sous forme d’une chaîne de caractères, qu’il faut ensuite convertir en entier (avec
la fonction int() ligne 4). Si on reprend les trois éléments d’une boucle while, on trouve l’initialisation de la variable
d’itération en ligne 1, le test de sa valeur en ligne 2, et sa mise à jour en ligne 4.
Conseil
Comment choisir entre la boucle while et la boucle for ? La boucle while s’utilisera généralement lorsqu’on ne sait pas
à l’avance le nombre d’itérations (comme dans le dernier exemple). Si on connait à l’avance le nombre d’itérations, par
exemple si on veut écrire 10 fois Le Python c'est cool, nous vous conseillons la boucle for.
5.4 Exercices
Conseil
Pour ces exercices, créez des scripts puis exécutez-les dans un shell.
Conseil
Pensez à relire le début du chapitre 3 Affichage qui discute de la fonction print().
5.4.7 Triangle
Créez un script qui dessine un triangle comme celui-ci :
*
**
***
****
*****
******
*******
********
*********
**********
*
**
***
****
*****
******
*******
********
*********
**********
5.4.10 Pyramide
Créez un script pyra.py qui dessine une pyramide comme celle-ci :
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************
Essayez de faire évoluer votre script pour dessiner la pyramide à partir d’un nombre arbitraire de lignes N. Vous
pourrez demander à l’utilisateur le nombre de lignes de la pyramide avec les instructions suivantes qui utilisent la fonction
input() :
1 reponse = input("Entrez un nombre de lignes (entier positif): ")
2 N = int(reponse)
Attention à bien respecter l’alignement des chiffres qui doit être justifié à droite sur 4 caractères. Testez avec une
matrice de dimensions 3 × 3, puis 5 × 5, et enfin 10 × 10.
Créez une seconde version de votre script, cette fois-ci avec deux boucles while.
ligne colonne
1 2
1 3
1 4
2 3
2 4
3 4
Pour une matrice 4x4, on a parcouru 6 cases
Conseil
Utilisez l’instruction random.choice([-1,1]) qui renvoie au hasard les valeurs -1 ou 1 avec la même probabilité.
Avant d’utiliser cette instruction, mettez au tout début de votre script la ligne
import random
Nous verrons la signification de cette syntaxe particulière dans le chapitre 9 Modules.
3. https://fr.wikipedia.org/wiki/Suite_de_Fibonacci
Tests
6.1 Définition
Les tests sont un élément essentiel à tout langage informatique si on veut lui donner un peu de complexité car ils
permettent à l’ordinateur de prendre des décisions. Pour cela, Python utilise l’instruction if ainsi qu’une comparaison
que nous avons abordée au chapitre précédent. Voici un premier exemple :
1 >>> x = 2
2 >>> if x == 2:
3 ... print("Le test est vrai !")
4 ...
5 Le test est vrai !
et un second :
1 >>> x = "souris"
2 >>> if x == "tigre":
3 ... print("Le test est vrai !")
4 ...
49
Chapitre 6. Tests 6.3. Importance de l’indentation
1 >>> x = 2
2 >>> if x == 2:
3 ... print("Le test est vrai !")
4 ... else:
5 ... print("Le test est faux !")
6 ...
7 Le test est vrai !
8 >>> x = 3
9 >>> if x == 2:
10 ... print("Le test est vrai !")
11 ... else:
12 ... print("Le test est faux !")
13 ...
14 Le test est faux !
On peut utiliser une série de tests dans la même instruction if, notamment pour tester plusieurs valeurs d’une même
variable.
Par exemple, on se propose de tirer au sort une base d’ADN puis d’afficher le nom de cette dernière. Dans le code
suivant, nous utilisons l’instruction random.choice(liste) qui renvoie un élément choisi au hasard dans une liste.
L’instruction import random sera vue plus tard dans le chapitre 9 Modules, admettez pour le moment qu’elle est
nécessaire :
1 >>> import random
2 >>> base = random.choice(["a", "t", "c", "g"])
3 >>> if base == "a":
4 ... print("choix d'une adénine")
5 ... elif base == "t":
6 ... print("choix d'une thymine")
7 ... elif base == "c":
8 ... print("choix d'une cytosine")
9 ... elif base == "g":
10 ... print("choix d'une guanine")
11 ...
12 choix d'une cytosine
Dans cet exemple, Python teste la première condition puis, si et seulement si elle est fausse, teste la deuxième et ainsi
de suite… Le code correspondant à la première condition vérifiée est exécuté puis Python sort du bloc d’instructions du
if. Il est également possible d’ajouter une condition else supplémentaire qui est exécutée si aucune des conditions du
if et des elif n’est vraie.
Code 1
1 nombres = [4, 5, 6]
2 for nb in nombres:
3 if nb == 5:
4 print("Le test est vrai")
5 print(f"car la variable nb vaut {nb}")
Résultat :
Le test est vrai
car la variable nb vaut 5
Code 2
1 nombres = [4, 5, 6]
2 for nb in nombres:
3 if nb == 5:
4 print("Le test est vrai")
5 print(f"car la variable nb vaut {nb}")
Résultat :
car la variable nb vaut 4
Le test est vrai
car la variable nb vaut 5
car la variable nb vaut 6
Les deux codes pourtant très similaires produisent des résultats très différents. Si vous observez avec attention
l’indentation des instructions sur la ligne 5, vous remarquerez que dans le code 1, l’instruction est indentée deux fois,
ce qui signifie qu’elle appartient au bloc d’instructions du test if. Dans le code 2, l’instruction de la ligne 5 n’est
indentée qu’une seule fois, ce qui fait qu’elle n’appartient plus au bloc d’instructions du test if, d’où l’affichage de
car la variable nb vaut xx pour toutes les valeurs de nb.
et de l’opérateur ET :
En Python, on utilise le mot réservé and pour l’opérateur ET et le mot réservé or pour l’opérateur OU. Respectez
bien la casse des opérateurs and et or qui, en Python, s’écrivent en minuscule. En voici un exemple d’utilisation :
1 >>> x = 2
2 >>> y = 2
3 >>> if x == 2 and y == 2:
4 ... print("le test est vrai")
5 ...
6 le test est vrai
Notez que le même résultat serait obtenu en utilisant deux instructions if imbriquées :
1 >>> x = 2
2 >>> y = 2
3 >>> if x == 2:
4 ... if y == 2:
5 ... print("le test est vrai")
6 ...
7 le test est vrai
Conseil
Nous vous conseillons la syntaxe avec un and qui est plus compacte. De manière générale, moins il y a de niveau
d’indentations mieux c’est pour la lisibilité.
Vous pouvez aussi tester directement l’effet de ces opérateurs à l’aide de True et False (attention à respecter la
casse) :
1 >>> True or False
2 True
Enfin, on peut utiliser l’opérateur logique de négation not qui inverse le résultat d’une condition :
1 >>> not True
2 False
3 >>> not False
4 True
5 >>> not (True and True)
6 False
L’instruction continue saute à l’itération suivante, sans exécuter la suite du bloc d’instructions de la boucle :
1 >>> for nombre in range(4):
2 ... if nombre == 2:
3 ... continue
4 ... print(nombre)
5 ...
6 0
7 1
8 3
Toutefois, nous vous le déconseillons formellement. Pourquoi ? Python stocke les valeurs numériques des floats sous
forme de nombres flottants (d’où leur nom), et cela mène à certaines limitations 1 . Observez l’exemple suivant :
1 >>> (3 - 2.7) == 0.3
2 False
3 >>> 3 - 2.7
4 0.2999999999999998
Nous voyons que le résultat de l’opération 3 - 2.7 n’est pas exactement 0.3 d’où le résultat False en ligne 2.
En fait, ce problème ne vient pas de Python, mais plutôt de la manière dont un ordinateur traite les nombres flottants
(comme un rapport de nombres binaires). Ainsi certaines valeurs de float ne peuvent être qu’approchées. Une manière
1. https://docs.python.org/fr/3/tutorial/floatingpoint.html
de s’en rendre compte est d’utiliser l’écriture formatée en demandant l’affichage d’un grand nombre de décimales :
1 >>> 0.3
2 0.3
3 >>> f"{0.3:.5f}"
4 '0.30000'
5 >>> f"{0.3:.60f}"
6 '0.299999999999999988897769753748434595763683319091796875000000'
7 >>> var = 3 - 2.7
8 >>> f"{var:.60f}"
9 '0.299999999999999822364316059974953532218933105468750000000000'
10 >>> abs(var - 0.3)
11 1.6653345369377348e-16
On observe que lorsqu’on tape 0.3, Python affiche une valeur arrondie. En réalité, le nombre réel 0.3 ne peut être
qu’approché lorsqu’on le code en nombre flottant. Il est essentiel d’avoir cela en tête lorsque l’on compare deux floats.
Même si 0.3 et 3 - 2.7 ne donnent pas le même résultat, la différence est toutefois infinétisimale, de l’ordre de 1e-16
soit la 16me décimale !
Pour ces raisons, il ne faut surtout pas utiliser l’opérateur == pour tester si un float est égal à une certaine valeur, car
cet opérateur correspond à une égalité stricte. La bonne pratique est de vérifier si un float est compris dans un intervalle
avec une certaine précision. Si on appelle cette précision delta, on peut procéder ainsi :
1 >>> delta = 1e-5
2 >>> var = 3.0 - 2.7
3 >>> 0.3 - delta < var < 0.3 + delta
4 True
5 >>> abs(var - 0.3) < delta
6 True
Ici on teste si var est compris dans l’intervalle 0.3 ± delta. En choisissant delta à 1e-5, on teste jusqu’à la cinquième
décimale. Les deux méthodes mènent à un résultat strictement équivalent :
• La ligne 3 est plus intuitive car elle ressemble à un encadrement mathématique.
• La ligne 5 utilise la fonction valeur absolue abs() et est plus compacte.
Une dernière manière pour tester la valeur d’un float, apparue en Python 3.5, est d’utiliser la fonction math.isclose
() :
1 >>> import math
2 >>> var = 3.0 - 2.7
3 >>> math.isclose(var, 0.3, abs_tol=1e-5)
4 True
Cette fonction prend en argument les deux floats à comparer, ainsi que l’argument par mot-clé abs_tol correspondant
à la précision souhaitée (que nous avions appelée delta ci-dessus). Nous vous conseillons de toujours préciser cet argument
abs_tol. Comme vu au dessus pour tirer une base au hasard, l’instruction import math sera vue dans le chapitre 9
Modules, admettez pour le moment qu’elle est nécessaire.
Conseil
Sur les trois manières de procéder pour comparer un float à une valeur, nous vous conseillons celle avec math.
isclose() qui nous parait la plus lisible.
6.7 Exercices
Conseil
Pour ces exercices, créez des scripts puis exécutez-les dans un shell.
Vous remarquerez qu’un nombre est pair lorsque le reste de sa division entière par 2 est nul.
Jusqu’à présent, la conjecture de Syracuse, selon laquelle depuis n’importe quel entier positif la suite de Syracuse
atteint 1, n’a pas été mise en défaut.
Par exemple, les premiers éléments de la suite de Syracuse si on prend comme point de départ 10 sont : 10, 5, 16, 8,
4, 2, 1…
Créez un script qui, partant d’un entier positif n (par exemple 10 ou 20), crée une liste des nombres de la suite de
Syracuse. Avec différents points de départ (c’est-à-dire avec différentes valeurs de n), la conjecture de Syracuse est-elle
toujours vérifiée ? Quels sont les nombres qui constituent le cycle trivial ?
Conseil
1. Pour cet exercice, vous avez besoin de faire un nombre d’itérations inconnu pour que la suite de Syracuse atteigne le
chiffre 1 puis entame son cycle trivial. Vous pourrez tester votre algorithme avec un nombre arbitraire d’itérations,
typiquement 20 ou 100, suivant votre nombre n de départ.
2. Un nombre est pair lorsque le reste de sa division entière (opérateur modulo %) par 2 est nul.
6.7.8 Attribution de la structure secondaire des acides aminés d’une protéine (exercice
+++)
Dans une protéine, les différents acides aminés sont liés entre eux par une liaison peptidique. Les angles phi et psi sont
deux angles mesurés autour de cette liaison peptidique. Leurs valeurs sont utiles pour définir la conformation spatiale
(appelée « structure secondaire ») adoptée par les acides aminés.
Par exemple, les angles phi et psi d’une conformation en « hélice alpha » parfaite ont une valeur de -57 degrés et -47
degrés respectivement. Bien sûr, il est très rare que l’on trouve ces valeurs parfaites dans une protéine, et il est habituel
de tolérer une déviation de ± 30 degrés autour des valeurs idéales de ces angles.
Vous trouverez ci-dessous une liste de listes contenant les valeurs des angles phi et psi de 15 acides aminés de la
protéine 1TFE 3 :
1 [[48.6, 53.4],[-124.9, 156.7],[-66.2, -30.8], \
2 [-58.8, -43.1],[-73.9, -40.6],[-53.7, -37.5], \
3 [-80.6, -26.0],[-68.5, 135.0],[-64.9, -23.5], \
4 [-66.9, -45.5],[-69.6, -41.0],[-62.7, -37.5], \
5 [-68.2, -38.3],[-61.2, -49.1],[-59.7, -41.1]]
Pour le premier acide aminé, l’angle phi vaut 48.6 et l’angle psi 53.4. Pour le deuxième, l’angle phi vaut -124.9 et
l’angle psi 156.7, etc.
En utilisant cette liste, créez un script qui teste, pour chaque acide aminé, s’il est ou non en hélice et affiche les
valeurs des angles phi et psi et le message adapté est en hélice ou n’est pas en hélice.
Par exemple, pour les trois premiers acides aminés :
[48.6, 53.4] n'est pas en hélice
[-124.9, 156.7] n'est pas en hélice
[-66.2, -30.8] est en hélice
D’après vous, quelle est la structure secondaire majoritaire de ces 15 acides aminés ?
Remarque
Pour en savoir plus sur le monde merveilleux des protéines, n’hésitez pas à consulter la page Wikipedia sur la structure
secondaire des protéines 4 .
Un nombre premier est un entier naturel qui admet exactement deux diviseurs distincts entiers et positifs (qui sont
alors 1 et lui-même). Cette définition exclut 1, qui n’a qu’un seul diviseur entier positif. Par opposition, un nombre non
nul produit de deux nombres entiers différents de 1 est dit composé. Par exemple 6 = 2 × 3 est composé, tout comme
21 = 3 × 7, mais 11 est premier car 1 et 11 sont les seuls diviseurs de 11. Les nombres 0 et 1 ne sont ni premiers ni
composés.
Déterminez tous les nombres premiers inférieurs à 100. Combien y a-t-il de nombres premiers entre 0 et 100 ? Pour
vous aider, nous vous proposons plusieurs méthodes.
Pour vous guider, voici ce que donnerait le programme avec la conversation précédente :
Pensez à un nombre entre 1 et 100.
Est-ce votre nombre est plus grand, plus petit ou égal à 50 ? [+/-/=] +
Est-ce votre nombre est plus grand, plus petit ou égal à 75 ? [+/-/=] +
Est-ce votre nombre est plus grand, plus petit ou égal à 87 ? [+/-/=] -
Est-ce votre nombre est plus grand, plus petit ou égal à 81 ? [+/-/=] -
Est-ce votre nombre est plus grand, plus petit ou égal à 78 ? [+/-/=] +
Est-ce votre nombre est plus grand, plus petit ou égal à 79 ? [+/-/=] =
J'ai trouvé en 6 questions !
Les caractères [+/-/=] indiquent à l’utilisateur comment il doit interagir avec l’ordinateur, c’est-à-dire entrer soit le
caractère + si le nombre choisi est plus grand que le nombre proposé par l’ordinateur, soit le caractère - si le nombre
choisi est plus petit que le nombre proposé par l’ordinateur, soit le caractère = si le nombre choisi est celui proposé par
l’ordinateur (en appuyant ensuite sur la touche Entrée).
Fichiers
Enregistrez ce fichier dans votre répertoire courant avec le nom animaux.txt. Puis, testez le code suivant dans
l’interpréteur Python :
1 >>> filin = open("animaux.txt", "r")
2 >>> filin
3 <_io.TextIOWrapper name='animaux.txt' mode='r' encoding='UTF-8'>
4 >>> filin.readlines()
5 ['girafe\n', 'tigre\n', 'singe\n', 'souris\n']
6 >>> filin.close()
7 >>> filin.readlines()
8 Traceback (most recent call last):
9 File "<stdin>", line 1, in <module>
10 ValueError: I/O operation on closed file.
58
7.1. Lecture dans un fichier Chapitre 7. Fichiers
• Ligne 2. Lorsqu’on affiche le contenu de la variable filin, on se rend compte que Python la considère comme un
objet de type fichier ouvert (ligne 3).
• Ligne 4. Nous utilisons à nouveau la syntaxe objet.méthode() (présentée dans le chapitre 3 Affichage). Ici la
méthode .readlines() agit sur l’objet filin en déplaçant le curseur de lecture du début à la fin du fichier, puis
elle renvoie une liste contenant toutes les lignes du fichier (dans notre analogie avec un livre, ceci correspondrait à
lire toutes les lignes du livre).
• Ligne 6. Enfin, on applique la méthode .close() sur l’objet filin, ce qui, vous vous en doutez, ferme le fichier
(ceci reviendrait à fermer le livre). Vous remarquerez que la méthode .close() ne renvoie rien, mais modifie l’état
de l’objet filin en fichier fermé. Ainsi, si on essaie de lire à nouveau les lignes du fichier, Python renvoie une
erreur, car il ne peut pas lire un fichier fermé (lignes 7 à 10).
Vous voyez qu’en cinq lignes de code, vous avez lu, parcouru le fichier et affiché son contenu.
Remarque
• Chaque élément de la liste lignes est une chaîne de caractères. C’est en effet sous forme de chaînes de caractères
que Python lit le contenu d’un fichier.
• Chaque élément de la liste lignes se termine par le caractère \n. Ce caractère un peu particulier correspond au «
saut de ligne 1 » qui permet de passer d’une ligne à la suivante (en anglais line feed). Ceci est codé par un caractère
spécial que l’on représente par \n. Vous pourrez parfois rencontrer également la notation octale \012. Dans la
suite de cet ouvrage, nous emploierons aussi l’expression « retour à la ligne » que nous trouvons plus intuitive.
• Par défaut, l’instruction print() affiche quelque chose puis revient à la ligne. Ce retour à la ligne dû à print()
se cumule alors avec celui de la fin de ligne (\n) de chaque ligne du fichier et donne l’impression qu’une ligne est
sautée à chaque fois.
Il existe en Python le mot-clé with qui permet d’ouvrir et de fermer un fichier de manière efficace. Si pour une raison
ou une autre l’ouverture ou la lecture du fichier conduit à une erreur, l’utilisation de with garantit la bonne fermeture
du fichier, ce qui n’est pas le cas dans le code précédent. Voici donc le même exemple avec with :
1. https://fr.wikipedia.org/wiki/Saut_de_ligne
Remarque
• L’instruction with introduit un bloc d’instructions qui doit être indenté. C’est à l’intérieur de ce bloc que nous
effectuons toutes les opérations sur le fichier.
• Une fois sorti du bloc d’instructions, Python fermera automatiquement le fichier. Vous n’avez donc plus besoin
d’utiliser la méthode .close().
L’objet filin est « itérable », ainsi la boucle for va demander à Python d’aller lire le fichier ligne par ligne.
Conseil
Privilégiez cette méthode par la suite.
Remarque
Les méthodes abordées précédemment permettent d’accéder au contenu d’un fichier, soit ligne par ligne (méthode
.readline()), soit globalement en une seule chaîne de caractères (méthode .read()), soit globalement avec les lignes
différenciées sous forme d’une liste de chaînes de caractères (méthode .readlines()). Il est également possible en
Python de se rendre à un endroit particulier d’un fichier avec la méthode .seek() mais qui sort du cadre de cet ouvrage.
• Ligne 4. L’écriture formatée, vue au chapitre 3 Affichage, permet d’ajouter un retour à la ligne (\n) après le nom
de chaque animal.
• Lignes 6 à 8. Le nombre d’octets écrits dans le fichier est augmenté de 1 par rapport à l’exemple précédent, car
le caractère retour à la ligne compte pour un seul octet.
Le contenu du fichier animaux2.txt est alors :
poisson
abeille
chat
Vous voyez qu’il est relativement simple en Python de lire ou d’écrire dans un fichier.
Dans cet exemple, with permet une notation très compacte en s’affranchissant de deux méthodes .close().
Si vous souhaitez aller plus loin, sachez que l’instruction with est plus générale et peut être utilisée dans d’autres
contextes 2 .
7.4 Note sur les retours à la ligne sous Unix et sous Windows
Conseil
Si vous êtes débutant, vous pouvez sauter cette rubrique.
On a vu plus haut que le caractère spécial \n correspondait à un retour à la ligne. C’est le standard sous Unix (Mac
OS X et Linux).
Toutefois, Windows utilise deux caractères spéciaux pour le retour à la ligne : \r correspondant à un retour chariot
(hérité des machines à écrire) et \n comme sous Unix.
Si vous avez commencé à programmer en Python 2, vous aurez peut-être remarqué que, selon les versions, la lecture
de fichier supprimait parfois les \r et d’autres fois les laissait. Heureusement, la fonction open() dans Python 3 3 gère
2. https://docs.python.org/fr/3/reference/compound_stmts.html#the-with-statement
3. https://docs.python.org/fr/3/library/functions.html#open
tout ça automatiquement et renvoie uniquement des sauts de ligne sous forme d’un seul \n (même si le fichier a été
conçu sous Windows et qu’il contient initialement des \r).
7.7 Exercices
Conseil
Pour ces exercices, créez des scripts puis exécutez-les dans un shell.
Les coordonnées cartésiennes xA et yA d’un point A sur un cercle de rayon r s’expriment en fonction de l’angle θ
représenté sur la figure 7.1 comme :
xA = cos(θ ) × r
yA = sin(θ ) × r
Pour calculer les coordonnées cartésiennes qui décrivent la spirale, vous allez faire varier deux variables en même
temps :
• l’angle θ , qui va prendre des valeurs de 0 à 4π radians par pas de 0,1, ce qui correspond à deux tours complets ;
• le rayon du cercle r, qui va prendre comme valeur initiale 0,5 puis que vous allez incrémenter (c’est-à-dire augmenter)
par pas de 0,1.
Les fonctions trigonométriques sinus et cosinus sont disponibles dans le module math que vous découvrirez plus en
détails dans le chapitre 9 Modules. Pour les utiliser, vous ajouterez au début de votre script l’instruction :
import math
La fonction sinus sera math.sin() et la fonction cosinus math.cos(). Ces deux fonctions prennent comme argument
une valeur d’angle en radian. La constante mathématique π sera également accessible grâce à ce module via math.pi.
Par exemple :
1 >>> math.sin(0)
2 0.0
3 >>> math.sin(math.pi/2)
4 1.0
5 >>> math.cos(math.pi)
6 -1.0
Sauvegardez ensuite les coordonnées cartésiennes dans le fichier spirale.dat en respectant le format suivant :
• un couple de coordonnées (xA et yA ) par ligne ;
• au moins un espace entre les deux coordonnées xA et yA ;
• les coordonnées affichées sur 10 caractères avec 5 chiffres après la virgule.
Les premières lignes de spirale.dat devrait ressembler à :
0.50000 0.00000
0.59700 0.05990
0.68605 0.13907
0.76427 0.23642
0.82895 0.35048
0.87758 0.47943
[...] [...]
Une fois que vous avez généré le fichier spirale.dat, visualisez votre spirale avec le code suivant (que vous pouvez
recopier dans un autre script ou à la suite de votre script spirale.py) :
1 import matplotlib.pyplot as plt
2
3 x = []
4 y = []
5 with open("spirale.dat", "r") as f_in:
6 for line in f_in:
7 coords = line.split()
8 x.append(float(coords[0]))
9 y.append(float(coords[1]))
10
11 fig, ax = plt.subplots(figsize=(8,8))
12 mini = min(x+y) - 2
13 maxi = max(x+y) + 2
14 ax.set_xlim(mini, maxi)
15 ax.set_ylim(mini, maxi)
16 ax.plot(x, y)
17 fig.savefig("spirale.png")
Remarque
Le module matplotlib est utilisé ici pour la visualisation de la spirale. Son utilisation est détaillée dans le chapitre 21
Module matplotlib.
Essayez de jouer sur les paramètres θ et r, et leur pas d’incrémentation, pour construire de nouvelles spirales.
Dictionnaires et tuples
Dans ce chapitre, nous allons voir deux nouveaux types d’objet qui s’avèrent extrêmement utiles : les dictionnaires et
les tuples. Comme les listes vues dans le chapitre 4, les dictionnaires et tuples contiennent une collection d’autres objets.
Toutefois, nous verrons que ces trois types sont régis par des règles différentes pour accéder à leur contenu, ainsi que
dans leur fonctionnement.
8.1 Dictionnaires
8.1.1 Définition et fonctionnement
Définition
Un dictionnaire contient une collection d’objets Python auxquels on accède à l’aide d’une clé de correspondance
plutôt qu’un indice. Ainsi, il ne s’agit pas d’objets séquentiels comme les listes, mais plutôt d’objets dits de correspondance
(mapping objects en anglais) ou tableaux associatifs.
• Ligne 1. On définit un dictionnaire vide avec les accolades {} (tout comme on peut le faire pour les listes avec
[]).
• Lignes 2 à 4. On remplit le dictionnaire avec plusieurs clés ("nom", "taille", "poids") auxquelles on affecte
des valeurs ("girafe", 5.0, 1100).
• Ligne 5. On affiche le contenu du dictionnaire. Les accolades nous montrent qu’il s’agit bien d’un dictionnaire,
et pour chaque élément séparé par une virgule on a une association du type clé: valeur. Ici, les clés sont des
chaînes de caractères (ce qui sera souvent le cas), et les valeurs peuvent être n’importe quel objet Python.
Une fois le dictionnaire créé, on récupère la valeur associée à une clé donnée avec une syntaxe du type dictionnaire
["clé"]. Par exemple :
66
8.1. Dictionnaires Chapitre 8. Dictionnaires et tuples
1 >>> animal1["nom"]
2 'girafe'
3 >>> animal1["taille"]
4 5.0
On se souvient que pour accéder à l’élément d’une liste, il fallait utiliser un indice (par exemple, liste[2]). Ici,
l’utilisation d’une clé (qui est souvent une chaîne de caractères) rend les choses plus explicites.
Vous pouvez mettre autant de couples clé / valeur que vous voulez dans un dictionnaire (tout comme vous pouvez
ajouter autant d’éléments que vous le souhaitez dans une liste).
Remarque
Jusqu’à la version 3.6 de Python, un dictionnaire était affiché sans ordre particulier. L’ordre d’affichage des éléments
n’était pas forcément le même que celui dans lequel il avait été rempli. De même, lorsqu’on itérait dessus, l’ordre n’était
pas garanti. Depuis Python 3.7 (inclus), ce comportement a changé : un dictionnaire est toujours affiché dans le même
ordre que celui utilisé pour le remplir. Et si on itère sur un dictionnaire, cet ordre est aussi respecté. Ce détail provient de
l’implémentation interne des dictionnaires dans Python, mais cela nous concerne peu. Ce qui importe, c’est de se rappeler
qu’on accède aux éléments par leur clé, et non par leur position telle que le dictionnaire est affiché. Cet ordre n’a pas
d’importance, sauf dans de rares cas.
On peut aussi initialiser toutes les clés et les valeurs d’un dictionnaire en une seule opération :
1 >>> animal2 = {"nom": "singe", "poids": 70, "taille": 1.75}
Mais rien ne nous empêche d’ajouter une clé et une valeur supplémentaire :
1 >>> animal2["age"] = 15
2 >>> animal2
3 {'nom': 'singe', 'poids': 70, 'taille': 1.75, 'age': 15}
Après ce premier tour d’horizon, on perçoit l’avantage des dictionnaires : pouvoir retrouver des éléments par des noms
(clés) plutôt que par des indices.
Les humains retiennent mieux les noms que les chiffres. Ainsi, les dictionnaires se révèlent très pratiques lorsque vous
devez manipuler des structures complexes à décrire et que les listes présentent leurs limites. L’usage des dictionnaires
rend en général le code plus lisible. Par exemple, si nous souhaitions stocker les coordonnées (x, y, z) d’un point dans
l’espace, nous pourrions utiliser coors = [0, 1, 2] pour la version liste et coors = {"x": 0, "y": 1, "z": 2}
pour la version dictionnaire. Quelqu’un qui lit le code comprendra tout de suite que coors["z"] contient la coordonnée
z, ce sera moins intuitif avec coors[2].
Conseil
Nous verrons dans le chapitre 14 Conteneurs que plusieurs types d’objets sont utilisables en tant que clé de dictionnaire.
Malgré cela, nous conseillons de n’utiliser que des chaînes de caractères lorsque vous débutez.
Par défaut, l’itération sur un dictionnaire se fait sur les clés. Dans cet exemple, la variable d’itération key prend
successivement la valeur de chaque clé, animal2[key] donne la valeur correspondant à chaque clé.
Les mentions dict_keys et dict_values indiquent que nous avons à faire à des objets un peu particuliers. Ils ne
sont pas indexables (on ne peut pas retrouver un élément par indice, par exemple dico.keys()[0] renverra une erreur).
Si besoin, nous pouvons les transformer en liste avec la fonction list() :
1 >>> animal2.values()
2 dict_values(['singe', 70, 1.75])
3 >>> list(animal2.values())
4 ['singe', 70, 1.75]
Toutefois, on peut itérer dessus dans une boucle (on dit qu’ils sont itérables) :
1 >>> for cle in animal2.keys():
2 ... print(cle)
3 ...
4 nom
5 poids
6 taille
On ne peut pas retrouver un élément par son indice dans un objet dict_items, toutefois on peut itérer dessus :
1 >>> dico.items()[2]
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 TypeError: 'dict_items' object is not subscriptable
5 >>> for key, val in dico.items():
6 ... print(key, val)
7 ...
8 0 t
9 1 o
10 2 t
11 3 o
Notez la syntaxe particulière qui ressemble à la fonction enumerate() vue au chapitre 5 Boucles et comparaisons.
On itère à la fois sur key et sur val. Nous aurons l’explication de ce mécanisme dans la rubrique sur les tuples ci-après.
Dans le second test (lignes 10 à 12), le message n’est pas affiché car la clé age n’est pas présente dans le dictionnaire
animal2.
Si on souhaite tester si une valeur existe dans un dictionnaire, on peut utiliser l’opérateur in avec l’objet renvoyé par
la méthode .values() :
1 >>> animal2 = {'nom': 'singe', 'poids': 70, 'taille': 1.75}
2 >>> animal2.values()
3 dict_values(['singe', 70, 1.75])
4 >>> "singe" in animal2.values()
5 True
La méthode .get() s’affranchit de ce problème. Elle extrait la valeur associée à une clé mais ne renvoie pas d’erreur
si la clé n’existe pas :
1 >>> animal2.get("nom")
2 'singe'
3 >>> animal2.get("age")
4 >>>
Ici, la valeur associée à la clé nom est singe, mais la clé age n’existe pas. On peut également indiquer à .get() une
valeur par défaut si la clé n’existe pas :
1 >>> animal2.get("age", 42)
2 42
Vous constatez ainsi que les dictionnaires permettent de gérer des structures complexes de manière plus explicite que
les listes.
8.2 Tuples
8.2.1 Définition
Définition
Les tuples (« n-uplets » en français) sont des objets séquentiels correspondant aux listes, mais ils sont toutefois
non modifiables. On dit aussi qu’ils sont immuables. Vous verrez ci-dessous que nous les avons déjà croisés à plusieurs
reprises !
Pratiquement, on utilise les parenthèses au lieu des crochets pour les créer :
1 >>> tuple1 = (1, 2, 3)
2 >>> tuple1
3 (1, 2, 3)
4 >>> type(tuple1)
5 <class 'tuple'>
6 >>> tuple1[2]
7 3
8 >>> tuple1[0:2]
9 (1, 2)
10 >>> tuple1[2] = 15
11 Traceback (most recent call last):
12 File "<stdin>", line 1, in <module>
13 TypeError: 'tuple' object does not support item assignment
L’affectation et l’indiçage fonctionnent comme avec les listes. Mais si on essaie de modifier un des éléments du tuple
(en ligne 10), Python renvoie un message d’erreur car les tuples sont non modifiables. Si vous voulez ajouter un élément
(ou le modifier), vous devez créer un nouveau tuple :
1 >>> tuple1 = (1, 2, 3)
2 >>> tuple1
3 (1, 2, 3)
4 >>> tuple1 = tuple1 + (2,)
5 >>> tuple1
6 (1, 2, 3, 2)
Conseil
Cet exemple montre que les tuples sont peu adaptés lorsqu’on a besoin d’ajouter, retirer, modifier des éléments. La
création d’un nouveau tuple à chaque étape s’avère lourde et il n’y a aucune méthode pour faire cela puisque les tuples
sont non modifiables. Pour ce genre de tâche, les listes sont clairement mieux adaptées.
Remarque
Pour créer un tuple d’un seul élément comme ci-dessus, utilisez une syntaxe avec une virgule (element,), pour
éviter une ambiguïté avec une simple expression. Par exemple, (2) équivaut à l’entier 2, alors que l’expression (2,) est
un tuple contenant l’élément 2.
Autre particularité des tuples, il est possible de les créer sans les parenthèses, dès lors que ceci ne pose pas d’ambiguïté
avec une autre expression :
1 >>> tuple1 = (1, 2, 3)
2 >>> tuple1
3 (1, 2, 3)
4 >>> tuple1 = 1, 2, 3
5 >>> tuple1
6 (1, 2, 3)
Toutefois, afin d’éviter les confusions, nous vous conseillons d’utiliser systématiquement les parenthèses lorsque vous
débutez.
Les opérateurs + et * fonctionnent comme pour les listes (concaténation et duplication) :
1 >>> (1, 2) + (3, 4)
2 (1, 2, 3, 4)
3 >>> (1, 2) * 4
4 (1, 2, 1, 2, 1, 2, 1, 2)
Enfin, on peut utiliser la fonction tuple(sequence) qui fonctionne exactement comme la fonction list(), c’est-
à-dire qu’elle prend en argument un objet et renvoie le tuple correspondant (opération de casting) :
1 >>> tuple([1,2,3])
2 (1, 2, 3)
3 >>> tuple("ATGCCGCGAT")
4 ('A', 'T', 'G', 'C', 'C', 'G', 'C', 'G', 'A', 'T')
5 >>> tuple(range(5))
6 (0, 1, 2, 3, 4)
Remarque
Comme la fonction list(), la fonction tuple() prend en argument un objet contenant d’autres objets. Elle ne
fonctionne pas avec les entiers, floats ou booléens. Par exemple, tuple(2) renvoie une erreur. On en verra plus sur ces
questions dans le chapitre 14 Conteneurs.
Attention, le nombre de variables et de valeurs doit être cohérents à gauche et à droite de l’opérateur = :
1 >>> x, y = 1, 2, 3
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 ValueError: too many values to unpack (expected 2)
Il est aussi possible de faire des affectations multiples avec des listes, par exemple :
[x, y, z] = [1, 2, 3].
Toutefois, cette syntaxe est alourdie par la présence des crochets. On préfèrera donc la syntaxe avec les tuples sans
parenthèses.
Remarque
Nous avons appelé l’opération x, y, z = 1, 2, 3 affectation multiple pour signifier que l’on affectait des valeurs
à plusieurs variables en même temps.
Vous pourrez rencontrer aussi l’expression tuple unpacking que l’on pourrait traduire par « désempaquetage de tuple
». De même, il existe le list unpacking.
Ce terme tuple unpacking provient du fait que l’on peut décomposer un tuple initial de n éléments en autant de
variables différentes en une seule instruction.
Par exemple, si on crée un tuple de trois éléments :
Cela serait possible également avec l’indiçage, mais il faudrait utiliser autant d’instruction que d’éléments :
1 >>> x = tuple1[0]
2 >>> y = tuple1[1]
3 >>> z = tuple1[2]
Conseil
La syntaxe x, y, z = tuple1 pour désempaqueter un tuple est plus élégante, plus lisible et plus compacte. Elle
sera donc à privilégier.
L’affectation multiple est un mécanisme très puissant et important en Python. Nous verrons qu’il est particulièrement
utile avec les fonctions dans les chapitres 10 Fonctions et 13 Plus sur les fonctions.
Lignes 7 à 12. La fonction enumerate() itère sur une série de tuples. Pouvoir séparer indice et element dans la
boucle est possible avec l’affectation multiple, par exemple : indice, element = 0, 75 (voir rubrique précédente).
Dans le même ordre d’idée, nous avons déjà vu la méthode .items() qui permettait d’itérer sur des couples clé /
valeur d’un dictionnaire :
1 >>> dico = {"pinson": 2, "merle": 3}
2 >>> for cle, valeur in dico.items():
3 ... print(cle, valeur)
4 ...
5 pinson 2
6 merle 3
7 >>> for bidule in dico.items():
8 ... print(bidule, type(bidule))
9 ...
10 ('pinson', 2) <class 'tuple'>
11 ('merle', 3) <class 'tuple'>
On pourrait concevoir la même chose sur quatre ou cinq éléments, voire plus. La seule contrainte est d’avoir une
correspondance systématique entre le nombre de variables d’itération (par exemple trois variables dans l’exemple ci-dessus
avec x, y, z) et la longueur de chaque sous-tuple de la liste sur laquelle on itère (dans l’exemple ci-dessus, chaque
sous-tuple a trois éléments).
En utilisant l’affectation multiple, on peut ainsi récupérer à la volée le quotient et le reste en une seule ligne :
1 >>> quotient, reste = divmod(3, 4)
2 >>> quotient
3 0
4 >>> reste
5 3
Cette fonction est très pratique, notamment quand on souhaite convertir des secondes en minutes et secondes
résiduelles. Par exemple, si on veut convertir 754 secondes en minutes :
1 >>> 754 / 60
2 12.566666666666666
3 >>> divmod(754, 60)
4 (12, 34)
La division normale nous donne un float en minutes qui n’est pas très pratique, il faut encore convertir 0.566666666666666
minute en secondes et gérer les problèmes d’arrondi. La fonction divmod() renvoie le résultat directement : 12 min et
34 s. On pourrait raisonner de manière similaire pour convertir des minutes en heures, des heures en jours, etc.
8.3 Exercices
Conseil
Pour le premier exercice, utilisez l’interpréteur Python. Pour les suivants, créez des scripts puis exécutez-les dans un
shell.
Prédisez le comportement de chaque instruction ci-dessous, sans les recopier dans un script ni dans l’interpréteur
Python :
• print(dico["age"])
• print(dico[var])
• print(dico[24])
• print(dico["var"])
• print(dico["taille"])
Lorsqu’une instruction produit une erreur, identifiez pourquoi. Vérifiez ensuite vos prédictions en recopiant les ins-
tructions dans l’interpréteur Python.
Calculez la moyenne de ses notes de deux manières différentes. Calculez à nouveau la moyenne sans la note de biologie.
On souhaite convertir la séquence en acide aminé du domaine transmembranaire d’une intégrine humaine SNADVVYEKQMLYLYVLSGIGG
en une série de signes indiquant la nature des acides aminés (a, p, + et -). Affichez tout d’abord la séquence sur une
ligne, puis la nature des acides aminés sur une seconde ligne.
La séquence contient une hélice transmembranaire, donc une succession de résidus apolaires, essayez de la retrouver
visuellement.
Pour cet exercice, nous vous conseillons d’itérer sur la chaîne de caractères contenant la séquence. Nous reverrons
cela dans le chapitre 11 Plus sur les chaînes de caractères.
Modules
9.1 Définition
Les modules sont des programmes Python qui contiennent des fonctions que l’on est amené à souvent réutiliser (on
les appelle aussi bibliothèques, ou libraries en anglais). Ce sont des « boîtes à outils » qui vous seront très utiles.
Les développeurs de Python ont mis au point de nombreux modules qui effectuent différentes tâches. Pour cette
raison, prenez toujours le réflexe de vérifier si une partie du code que vous souhaitez écrire n’existe pas déjà sous forme
de module.
La plupart de ces modules sont déjà installés dans les versions standards de Python. Vous pouvez accéder à une
documentation exhaustive 1 sur le site de Python. N’hésitez pas à explorer un peu ce site, la quantité de modules
disponibles est impressionnante (plus de 300 modules).
1. https://docs.python.org/fr/3/py-modindex.html
2. https://docs.python.org/fr/3/library/random.html#module-random
76
9.2. Importation de modules Chapitre 9. Modules
En résumé, l’utilisation de la syntaxe import module permet d’importer tout une série de fonctions organisées par
« thèmes ». Par exemple, les fonctions gérant les nombres aléatoires avec random et les fonctions mathématiques avec
math. Python possède de nombreux autres modules internes (c’est-à-dire présent de base lorsqu’on installe Python).
Remarque
Dans le chapitre 3 Affichage, nous avons introduit la syntaxe truc.bidule() avec truc étant un objet et .bidule()
une méthode. Nous vous avions expliqué qu’une méthode était une fonction un peu particulière :
• elle était liée à un objet par un point ;
• en général, elle agissait sur ou utilisait l’objet auquel elle était liée.
Par exemple, la méthode .append() vue dans le chapitre 4 Listes. Dans l’instruction liste1.append(3), la méthode
.append() ajoute l’entier 3 à l’objet liste1 auquel elle est liée.
Avec les modules, nous rencontrons une syntaxe similaire. Par exemple, dans l’instruction math.cos(), on pourrait
penser que .cos() est aussi une méthode. En fait la documentation officielle de Python 3 précise bien que dans ce cas
.cos() est une fonction. Dans cet ouvrage, nous utiliserons ainsi le mot fonction lorsqu’on évoquera des fonctions issues
de modules.
Si cela vous parait encore ardu, ne vous inquiétez pas : c’est à force de pratiquer et de lire que vous vous approprierez
le vocabulaire. La syntaxe module.fonction() est là pour rappeler de quel module provient la fonction en un coup
d’œil.
À l’aide du mot-clé from, on peut importer une fonction spécifique d’un module donné. Remarquez bien qu’il est
inutile de répéter le nom du module dans ce cas : seul le nom de la fonction en question est requis.
On peut également importer toutes les fonctions d’un module :
1 >>> from random import *
2 >>> randint(0,50)
3 46
4 >>> uniform(0,2.5)
5 0.64943174760727951
L’instruction from random import * importe toutes les fonctions du module random. On peut utiliser toutes ses
fonctions directement, comme par exemple randint() et uniform() qui renvoient des nombres aléatoires entiers et
floats.
Dans la pratique, plutôt que de charger toutes les fonctions d’un module en une seule fois :
1 from random import *
Il est également possible de définir un alias (un nom plus court) pour un module :
3. https://docs.python.org/fr/3/tutorial/modules.html
Dans cet exemple, les fonctions du module random sont accessibles via l’alias rand.
Enfin, pour vider de la mémoire un module déjà chargé, on peut utiliser l’instruction del :
1 >>> import random
2 >>> random.randint(0,10)
3 2
4 >>> random.uniform(1, 3)
5 2.825594756352219
6 >>> del random
7 >>> random.randint(0,10)
8 Traceback (most recent call last):
9 File "<stdin>", line 1, in ?
10 NameError: name 'random' is not defined. Did you forget to import 'random'?
On constate alors qu’un rappel (ligne 7) d’une fonction du module random, après l’avoir vidé de la mémoire (ligne 6),
retourne un message d’erreur (lignes 8-10). Dans le cas présent, le message d’erreur est explicite et demande à l’utilisateur
s’il n’a pas oublié d’importer le module random.
Ce qui renvoie :
Help on module random:
NAME
random - Random variable generators.
MODULE REFERENCE
https://docs.python.org/3.7/library/random
DESCRIPTION
integers
--------
uniform within range
sequences
---------
pick random element
pick random sample
Remarque
• Pour vous déplacer dans l’aide, utilisez les flèches du haut et du bas pour le parcourir ligne par ligne, ou les touches
Page-up et Page-down pour faire défiler l’aide page par page.
• Pour quitter l’aide, appuyez sur la touche Q.
• Pour chercher du texte, tapez le caractère / puis le texte que vous cherchez, puis la touche Entrée. Par exemple,
pour chercher l’aide sur la fonction randint(), tapez /randint puis Entrée.
• Vous pouvez également obtenir de l’aide sur une fonction particulière d’un module avec :
help(random.randint)
La commande help() est en fait une commande plus générale, permettant d’avoir de l’aide sur n’importe quel objet
chargé en mémoire :
1 >>> t = [1, 2, 3]
2 >>> help(t)
3 Help on list object:
4
5 class list(object)
6 | list() -> new list
7 | list(sequence) -> new list initialized from sequence's items
8 |
9 | Methods defined here:
10 |
11 | __add__(...)
12 | x.__add__(y) <==> x+y
13 |
14 |
15 ...
Enfin, pour connaître d’un seul coup d’œil toutes les méthodes ou variables associées à un objet, utilisez la fonction
dir() :
1 >>> import random
2 >>> dir(random)
3 ['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'Random', 'SG_MAGICCONST',
4 'SystemRandom', 'TWOPI', 'WichmannHill', '_BuiltinMethodType', '_MethodT
5 ype', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '_ac
6 os', '_ceil', '_cos', '_e', '_exp', '_hexlify', '_inst', '_log', '_pi',
7 '_random', '_sin', '_sqrt', '_test', '_test_generator', '_urandom', '_wa
8 rn', 'betavariate', 'choice', 'expovariate', 'gammavariate', 'gauss', 'g
9 etrandbits', 'getstate', 'jumpahead', 'lognormvariate', 'normalvariate',
10 'paretovariate', 'randint', 'random', 'randrange', 'sample', 'seed', 's
11 etstate', 'shuffle', 'uniform', 'vonmisesvariate', 'weibullvariate']
12 >>>
4. https://docs.python.org/fr/3/py-modindex.html
5. https://docs.python.org/fr/3/library/math.html#module-math
6. https://docs.python.org/fr/3/library/sys.html#module-sys
7. https://docs.python.org/fr/3/library/os.html#module-os
8. https://docs.python.org/fr/3/library/random.html#module-random
9. https://docs.python.org/fr/3/library/time.html#module-time
10. https://docs.python.org/fr/3/library/urllib.html#module-urllib
11. https://docs.python.org/fr/3/library/tkinter.html#module-tkinter
12. https://docs.python.org/fr/3/library/re.html#module-re
Vous verrez dans le chapitre 15 Création de modules comment créer votre propre module lorsque vous souhaitez
réutiliser souvent vos propres fonctions.
Enfin, notez qu’il existe de nombreux autres modules externes qui ne sont pas installés de base dans Python, mais
qui sont très utilisés en bioinformatique et en analyse de données. Par exemple : NumPy (manipulations de vecteurs
et de matrices), Biopython (manipulation de séquences ou de structures de biomolécules), matplotlib (représentations
graphiques), pandas (analyse de données tabulées), etc. Ces modules vous serons présentés dans les chapitres 19 à 22.
La fonction choices() (avec un s à la fin) réalise plusieurs tirages aléatoires (avec remise, c’est-à-dire qu’on peut
piocher plusieurs fois le même élément) dans une liste donnée. Le nombre de tirages est précisé par le paramètre k :
1 >>> random.choices(bases, k=5)
2 ['G', 'A', 'A', 'T', 'G']
3 >>> random.choices(bases, k=5)
4 ['A', 'T', 'A', 'A', 'C']
5 >>> random.choices(bases, k=10)
6 ['C', 'T', 'T', 'T', 'G', 'A', 'C', 'A', 'G', 'G']
Si vous exécutez vous-même les exemples précédents, vous devriez obtenir des résultats légèrement différents de ceux
indiqués.
Pour des besoins de reproductibilité des analyses en science, on a souvent besoin de retrouver les mêmes résultats
même si on utilise des nombres aléatoires. Pour cela, on peut définir ce qu’on appelle la « graine aléatoire ».
Définition
En informatique, la génération de nombres aléatoires est un problème complexe. On utilise plutôt des « générateurs
de nombres pseudo-aléatoires 14 ». Pour cela, une graine aléatoire 15 doit être définie. Cette graine est la plupart du temps
un nombre entier qu’on passe au générateur : celui-ci va alors produire une série donnée de nombres pseudo-aléatoires
qui dépendent de cette graine. Si on change la graine, la série de nombres change.
13. https://docs.python.org/fr/3/library/random.html#module-random
14. https://fr.wikipedia.org/wiki/G%C3%A9n%C3%A9rateur_de_nombres_pseudo-al%C3%A9atoires
15. https://fr.wikipedia.org/wiki/Graine_al%C3%A9atoire
Ici, la graine aléatoire est fixée à 42. Si on ne précise pas la graine, par défaut Python utilise la date (plus précisément,
il s’agit du nombre de secondes écoulées depuis une date fixe passée). Ainsi, à chaque fois qu’on relance Python, la graine
sera différente, car ce nombre de secondes sera différent.
Si vous exécutez ces mêmes lignes de code (depuis l’instruction random.seed(42)), il se peut que vous ayez des
résultats différents selon la version de Python. Néanmoins, vous devriez systématiquement obtenir les mêmes résultats si
vous relancez plusieurs fois de suite ces instructions sur une même machine.
Remarque
Quand on utilise des nombres aléatoires, il est fondamental de connaitre la distribution de probablités utilisée par la
fonction.
Par exemple, la fonction de base du module random est random.random(), elle renvoie un float aléatoire entre 0 et
1 tiré dans une distribution uniforme. Si on tire beaucoup de nombres, on aura la même probabilité d’obtenir tous les
nombres possibles entre 0 et 1. La fonction random.randint() tire aussi un entier dans une distribution uniforme. La
fonction random.gauss() tire quant à elle un float aléatoire dans une distribution gaussienne.
• Ligne 1. Le caractère $ représente l’invite du shell, test.py est le nom du script Python, salut, girafe et 42
sont les arguments passés au script (tous séparés par un espace).
• Ligne 2. Le script affiche le contenu de la variable sys.argv. Cette variable est une liste qui contient tous les
arguments de la ligne de commande, y compris le nom du script Python lui-même qu’on retrouve comme premier
élément de cette liste dans sys.argv[0]. On peut donc accéder à chacun des différents arguments du script avec
sys.argv[1], sys.argv[2], etc.
Toujours dans le module sys, la fonction sys.exit() est utile pour quitter un script Python. On peut donner un
argument à cette fonction (en général une chaîne de caractères) qui sera renvoyé au moment où Python quittera le script.
Par exemple, si vous attendez au moins un argument en ligne de commande, vous pouvez renvoyer un message pour
indiquer à l’utilisateur ce que le script attend comme argument :
1 import sys
2
3 if len(sys.argv) != 2:
4 sys.exit("ERREUR : il faut exactement un argument.")
5
6 print(f"Argument vaut : {sys.argv[1]}")
16. https://docs.python.org/fr/3/library/sys.html#module-sys
Remarque
On vérifie dans cet exemple que le script possède deux arguments, car le nom du script lui-même compte pour un
argument (le tout premier).
L’intérêt de récupérer des arguments passés dans la ligne de commande à l’appel du script est de pouvoir ensuite les
utiliser dans le script.
Voici comme nouvel exemple le script compte_lignes.py, qui prend comme argument le nom d’un fichier puis
affiche le nombre de lignes qu’il contient :
1 import sys
2
3 if len(sys.argv) != 2:
4 sys.exit("ERREUR : il faut exactement un argument.")
5
6 nom_fichier = sys.argv[1]
7 taille = 0
8 with open(nom_fichier, "r") as f_in:
9 taille = len(f_in.readlines())
10
11 print(f"{nom_fichier} contient {taille} lignes.")
Supposons que dans le même répertoire, nous ayons le fichier animaux1.txt dont voici le contenu :
girafe
tigre
singe
souris
Par contre, le script ne vérifie pas si le fichier fourni en argument existe bien :
$ python compte_lignes.py animaux3.txt
Traceback (most recent call last):
File "compte_lignes.py", line 8, in <module>
with open(nom_fichier, "r") as f_in:
^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'animaux3.txt'
Dans cet exemple, si le fichier n’existe pas sur le disque dur, on quitte le programme avec la fonction exit() du
module sys que nous venons de voir.
La méthode .cwd() renvoie le chemin complet du répertoire depuis lequel est lancé Python (cwd signifiant current
working directory) :
1 >>> from pathlib import Path
2 >>> Path().cwd()
3 PosixPath('/home/pierre')
On obtient un objet de type PosixPath qu’il est possible de transformer si besoin en chaîne de caractères avec la
fonction str(), que nous avons vu dans le chapitre 2 Variables :
1 >>> str(Path().cwd())
2 '/home/pierre'
Mais l’intérêt de récupérer un objet de type PosixPath est qu’on peut ensuite utiliser les méthodes .name et .parent
pour obtenir respectivement le nom du répertoire (sans son chemin complet) et le répertoire parent :
1 >>> Path().cwd()
2 PosixPath('/home/pierre')
3 >>> Path().cwd().name
4 'pierre'
5 >>> Path().cwd().parent
6 PosixPath('/home')
Enfin, la méthode .iterdir() liste le contenu du répertoire depuis lequel est lancé Python :
1 >>> list(Path().iterdir())
2 [PosixPath('demo.py'), PosixPath('tests'), PosixPath('1BTA.pdb')]
Tout comme la fonction range() (voir le chapitre 4 Listes), la méthode .iterdir() est un itérateur. La fonction
list() permet d’obtenir une liste.
Toutefois, il est possible d’itérer très facilement sur le contenu d’un répertoire et de savoir s’il contient des fichiers
ou des sous-répertoires :
17. https://docs.python.org/fr/3/library/pathlib.html
La méthode .is_file() renvoie True si l’objet est un fichier, et False si ce n’est pas le cas.
La méthode .iterdir() parcourt le contenu d’un répertoire, sans en explorer les éventuels sous-répertoires. Si on
souhaite parcourir récursivement un répertoire, on utilise la méthode .glob(). Prenons l’arborescence suivante comme
exemple :
1BTA.pdb
demo.py
tests
├── results.csv
├── script1.py
└── script2.py
Le répertoire courant contient les fichiers 1BTA.pdb et demo.py, ainsi que le répertoire tests. Ce dernier contient
lui-même les fichiers results.csv, script1.py et script2.py.
On souhaite maintenant lister tous les scripts Python (dont l’extension est .py) présents dans le répertoire courant
et dans ses sous-répertoires :
1 >>> for nom in Path().glob("**/*.py"):
2 ... print(f"{nom}")
3 ...
4 demo.py
5 tests/script1.py
6 tests/script2.py
Dans la chaîne de caractères "**/*.py", ** recherche tous les sous-répertoires récursivement et *.py signifie
n’importe quel nom de fichier qui se termine par l’extension .py.
Il existe de nombreuse autres méthodes associées à la classe Path du module pathlib, n’hésitez pas à consulter la
documentation 18 .
9.8 Exercices
Conseil
Pour les trois premiers exercices, utilisez l’interpréteur Python. Pour les exercices suivants, créez des scripts puis
exécutez-les dans un shell.
9.8.2 Cosinus
Calculez le cosinus de π /2 en utilisant le module math avec la fonction cos() et la constante pi.
Consultez pour cela la documentation 20 de la fonction math.cos() et la documentation 21 de la constante math.pi.
Conseil
• Jetez un oeil à la rubrique sur la comparaison de floats abordée dans le chapitre 6 Tests.
• Les constantes π et e sont obtenues par math.pi et math.e.
$ python belote.py
La main est ['7', 'A', '9', 'A', 'R', 'd', 'V', 'D'].
7 --> 0 points
A --> 11 points
9 --> 0 points
A --> 11 points
R --> 4 points
d --> 10 points
V --> 2 points
D --> 3 points
Le nombre total de points de la main est 41.
Conseil
Pour générer la séquence d’ADN, utilisez la fonction random.choice() abordée dans l’exercice précédent.
aire du cercle π
p= =
aire du carré 4
Soit n, le nombre de points effectivement dans le cercle, il vient alors
n π
p= = ,
N 4
d’où
n
π = 4× .
N
Déterminez une approximation de π par cette méthode. Pour cela, pour N itérations :
1. Choisissez aléatoirement les coordonnées x et y d’un point entre -1 et 1. Utilisez la fonction uniform() du module
random.
2. Calculez la distance entre le centre du cercle et ce point.
3. Déterminez si cette distance est inférieure au rayon du cercle, c’est-à-dire si le point est dans le cercle ou pas.
4. Si le point est effectivement dans le cercle, incrémentez le compteur n.
Finalement calculez le rapport entre n et N et proposez une estimation de π . Quelle valeur de π obtenez-vous pour
100 itérations ? 1000 itérations ? 10 000 itérations ? Comparez les valeurs obtenues à la valeur de π fournie par le module
math.
On rappelle que la distance d entre deux points A et B de coordonnées respectives (xA , yA ) et (xB , yB ) se calcule
comme :
√
d = (xB − xA )2 + (yB − yA )2
25. https://docs.python.org/fr/3/library/random.html#random.uniform
Fonctions
88
10.2. Définition Chapitre 10. Fonctions
10.2 Définition
Pour définir une fonction, Python utilise le mot-clé def. Si on souhaite que la fonction renvoie quelque chose, il faut
utiliser le mot-clé return. Par exemple :
1 >>> def carre(x):
2 ... return x**2
3 ...
4 >>> print(carre(2))
5 4
Notez que la syntaxe de def utilise les deux-points comme les boucles for et while ainsi que les tests if : un bloc
d’instructions est donc attendu. De même que pour les boucles et les tests, l’indentation de ce bloc d’instructions (qu’on
appelle le corps de la fonction) est obligatoire.
Dans l’exemple précédent, nous avons passé un argument à la fonction carre(), qui nous a renvoyé (ou retourné)
une valeur que nous avons immédiatement affichée à l’écran avec l’instruction print(). Que veut dire valeur renvoyée ?
Et bien cela signifie que cette dernière est récupérable dans une variable :
1 >>> res = carre(2)
2 >>> print(res)
3 4
Ici, le résultat renvoyé par la fonction est stocké dans la variable res. Notez qu’une fonction ne prend pas forcément
un argument et ne renvoie pas forcément une valeur, par exemple :
Dans ce cas, la fonction hello() se contente d’afficher la chaîne de caractères "bonjour" à l’écran. Elle ne prend
aucun argument et ne renvoie rien. Par conséquent, cela n’a pas de sens de vouloir récupérer dans une variable le résultat
renvoyé par une telle fonction. Si on essaie tout de même, Python affecte la valeur None qui signifie rien en anglais :
1 >>> var = hello()
2 bonjour
3 >>> print(var)
4 None
Ceci n’est pas une faute car Python n’émet pas d’erreur, toutefois cela ne présente, la plupart du temps, guère
d’intérêt.
L’opérateur * reconnaît plusieurs types (entiers, floats, chaînes de caractères, listes). Notre fonction fois() est donc
capable d’effectuer des tâches différentes ! Même si Python autorise cela, méfiez-vous tout de même de cette grande
flexibilité qui pourrait conduire à des surprises dans vos futurs programmes. En général, il est plus judicieux que chaque
argument ait un type précis (entiers, floats, chaînes de caractères, etc.) et pas l’un ou l’autre.
En réalité Python ne renvoie qu’un seul objet, mais celui-ci peut être séquentiel, c’est-à-dire contenir lui-même
d’autres objets. Dans notre exemple, Python renvoie un objet de type tuple, type que nous avons vu dans le chapitre
8 Dictionnaires et tuples (souvenez-vous, il s’agit d’une sorte de liste avec des propriétés différentes). Notre fonction
pourrait tout autant renvoyer une liste :
Renvoyer un tuple ou une liste de deux éléments (ou plus) est très pratique en conjonction avec l’affectation multiple,
par exemple :
1 >>> z1, z2 = carre_cube2(3)
2 >>> z1
3 9
4 >>> z2
5 27
Cela permet de récupérer plusieurs valeurs renvoyées par une fonction et de les affecter à la volée à des variables
différentes.
Une fonction peut aussi renvoyer un booléen :
1 def est_pair(x):
2 if x % 2 == 0:
3 return True
4 else:
5 return False
6
7 # Programme principal.
8 for chiffre in range(1, 5):
9 if est_pair(chiffre):
10 print(f"{chiffre} est pair")
Comme la fonction renvoie un booléen, on peut utiliser la notation if est_pair(chiffre): qui équivaut à if
est_pair(chiffre) == True:. Il est courant d’appeler une fonction qui renvoie un booléen est_quelquechose()
car on comprend que ça pose la question si c’est vrai ou faux. En anglais, on trouvera la notation is_even(). Nous
reverrons ces notions dans le chapitre 13 Plus sur les fonctions.
On constate que passer un seul argument à une fonction qui en attend deux conduit à une erreur.
Définition
Lorsqu’on définit une fonction def fct(x, y): les arguments x et y sont appelés arguments positionnels (en
anglais, positional arguments). Il est strictement obligatoire de les préciser lors de l’appel de la fonction. De plus, il est
nécessaire de respecter le même ordre lors de l’appel que dans la définition de la fonction. Dans l’exemple ci-dessus, 2
correspondra à x et 3 correspondra à y. Finalement, tout dépendra de leur position, d’où leur qualification de positionnel.
Mais il est aussi possible de passer un ou plusieurs argument(s) de manière facultative et de leur attribuer une valeur
par défaut :
Définition
Un argument défini avec une syntaxe def fct(arg=val): est appelé argument par mot-clé (en anglais, keyword
argument). Le passage d’un tel argument lors de l’appel de la fonction est facultatif. Ce type d’argument ne doit pas être
confondu avec les arguments positionnels présentés ci-dessus, dont la syntaxe est def fct(arg):.
On observe que pour l’instant, les arguments par mot-clé sont pris dans l’ordre dans lesquels on les passe lors de
l’appel. Comment faire si l’on souhaitait préciser l’argument par mot-clé z et garder les valeurs de x et y par défaut ?
Simplement en précisant le nom de l’argument lors de l’appel :
1 >>> fct(z=10)
2 (0, 0, 10)
Python permet même de rentrer les arguments par mot-clé dans un ordre arbitraire :
1 >>> fct(z=10, x=3, y=80)
2 (3, 80, 10)
3 >>> fct(z=10, y=80)
4 (0, 80, 10)
Que se passe-t-il lorsque nous avons un mélange d’arguments positionnels et par mot-clé ? Et bien les arguments
positionnels doivent toujours être placés avant les arguments par mot-clé :
1 >>> def fct(a, b, x=0, y=0, z=0):
2 ... return a, b, x, y, z
3 ...
4 >>> fct(1, 1)
5 (1, 1, 0, 0, 0)
6 >>> fct(1, 1, z=5)
7 (1, 1, 0, 0, 5)
8 >>> fct(1, 1, z=5, y=32)
9 (1, 1, 0, 32, 5)
On peut toujours passer les arguments par mot-clé dans un ordre arbitraire à partir du moment où on précise leur
nom. Par contre, si les deux arguments positionnels a et b ne sont pas passés à la fonction, Python renvoie une erreur.
1 >>> fct(z=0)
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 TypeError: fct() missing 2 required positional arguments: 'a' and 'b'
Conseil
Préciser le nom des arguments par mot-clé lors de l’appel d’une fonction est une pratique que nous vous recommandons.
Cela les distingue clairement des arguments positionnels.
L’utilisation d’arguments par mot-clé est habituelle en Python. Elle permet de modifier le comportement par défaut
de nombreuses fonctions. Par exemple, si on souhaite que la fonction print() n’affiche pas un retour à la ligne, on peut
utiliser l’argument end :
1 >>> print("Message ", end="")
2 Message >>>
Nous verrons, dans le chapitre 25 Fenêtres graphiques et Tkinter (en ligne), que l’utilisation d’arguments par mot-clé
est systématique lorsqu’on crée un objet graphique (une fenêtre, un bouton, etc.).
Pour la suite des explications, nous allons utiliser l’excellent site Python Tutor 1 qui permet de visualiser l’état des
variables au fur et à mesure de l’exécution d’un code Python. Avant de poursuivre, nous vous conseillons de prendre 5
minutes pour tester ce site.
Regardons maintenant ce qui se passe dans le code ci-dessus, étape par étape :
• Étape 1 : Python est prêt à lire la première ligne de code.
1. http://www.pythontutor.com
• Étape 2 : Python met en mémoire la fonction carre(). Notez qu’il ne l’exécute pas ! La fonction est mise dans
un espace de la mémoire nommé Global frame, il s’agit de l’espace du programme principal. Dans cet espace
seront stockées toutes les variables globales créées dans le programme. Python est maintenant prêt à exécuter le
programme principal.
• Étape 3 : Python lit et met en mémoire la variable var. Celle-ci étant créée dans le programme principal, il s’agira
d’une variable globale. Ainsi, elle sera également stockée dans le Global frame.
• Étape 4 : La fonction carre() est appelée et on lui passe en argument l’entier var. La fonction s’exécute et
un nouveau cadre est créé dans lequel Python Tutor va indiquer toutes les variables locales à la fonction. Notez
bien que la variable passée en argument, qui s’appelle x dans la fonction, est créée en tant que variable locale. On
remarquera aussi que les variables globales situées dans le Global frame sont toujours là.
• Étape 5 : Python est maintenant prêt à exécuter chaque ligne de code de la fonction.
• Étape 6 : La variable y est créée dans la fonction. Celle-ci est donc stockée en tant que variable locale à la fonction.
• Étape 7 : Python s’apprête à renvoyer la variable locale y au programme principal. Python Tutor nous indique le
contenu de la valeur renvoyée.
• Étape 8 : Python quitte la fonction et la valeur renvoyée par celle-ci est affectée à la variable globale resultat.
Notez bien que lorsque Python quitte la fonction, l’espace des variables alloué à la fonction est détruit. Ainsi,
toutes les variables créées dans la fonction n’existent plus. On comprend pourquoi elles portent le nom de locales
puisqu’elles n’existent que lorsque la fonction est exécutée.
Nous espérons que cet exemple guidé facilitera la compréhension des concepts de variables locales et globales. Cela
viendra aussi avec la pratique. Nous irons un peu plus loin sur les fonctions dans le chapitre 13 Plus sur les fonctions.
D’ici là, essayez de vous entraîner au maximum avec les fonctions. C’est un concept ardu, mais il est impératif de le
maîtriser.
Enfin, comme vous avez pu le constater, Python Tutor nous a grandement aidé à comprendre ce qui se passait.
N’hésitez pas à l’utiliser sur des exemples ponctuels, ce site vous aidera à visualiser ce qui se passe lorsqu’un code ne fait
pas ce que vous attendez.
Malheureusement, il y a une erreur dans la formule de conversion. En effet, la formule exacte est :
5
temp_celsius = (temp_fahrenheit − 32) ×
9
Il faut alors reprendre les lignes 2, 5 et 8 précédentes et les corriger. Cela n’est pas efficace, surtout si le même code
est utilisé à différents endroits dans le programme.
En écrivant qu’une seule fois la formule de conversion dans une fonction, on applique le principe DRY :
1 >>> def convert_fahrenheit_to_celsius(temperature):
2 ... return (temperature - 32) * (5/9)
3 ...
4 >>> temp_in_fahrenheit = 60
5 >>> convert_fahrenheit_to_celsius(temp_in_fahrenheit)
6 15.555555555555557
7 >>> temp_in_fahrenheit = 80
8 >>> convert_fahrenheit_to_celsius(temp_in_fahrenheit)
9 26.666666666666668
10 >>> temp_in_fahrenheit = 100
11 >>> convert_fahrenheit_to_celsius(temp_in_fahrenheit)
12 37.77777777777778
Et s’il y a une erreur dans la formule, il suffira de ne la corriger qu’une seule fois, dans la fonction convert_fahrenheit_to_celsius
().
10.8 Exercices
Conseil
Pour le premier exercice, utilisez Python Tutor. Pour les exercices suivants, créez des scripts puis exécutez-les dans
un shell.
Testez ensuite cette portion de code avec Python Tutor, en cherchant à bien comprendre chaque étape. Avez-vous
réussi à prédire la sortie correctement ?
Remarque
Une remarque concernant l’utilisation des f-strings que nous avions abordées dans le chapitre 3 Affichage. On découvre
ici une autre possibilité des f-strings dans l’instruction f"{nb2}! = {calc_factorielle(nb2)}" : il est en effet
possible d’appeler entre les accolades une fonction (ici {calc_factorielle(nb2)}) ! Ainsi, il n’est pas nécessaire de
créer une variable intermédiaire dans laquelle on stocke ce que retourne la fonction.
10.8.2 Puissance
Créez une fonction calc_puissance(x, y) qui renvoie xy en utilisant l’opérateur **. Pour rappel :
1 >>> 2**2
2 4
3 >>> 2**3
4 8
5 >>> 2**4
6 16
Dans le programme principal, calculez et affichez à l’écran 2i avec i variant de 0 à 20 inclus. On souhaite que le
résultat soit présenté avec le formatage suivant :
2^ 0 = 1
2^ 1 = 2
2^ 2 = 4
[...]
2^20 = 1048576
10.8.3 Pyramide
Reprenez l’exercice du chapitre 5 Boucles et comparaisons qui dessine une pyramide.
Dans un script pyra.py, créez une fonction gen_pyramide() à laquelle vous passez un nombre entier N et qui
renvoie une pyramide de N lignes sous forme de chaîne de caractères. Le programme principal demandera à l’utilisateur
le nombre de lignes souhaitées (utilisez pour cela la fonction input()) et affichera la pyramide à l’écran.
10.8.6 Distance 3D
Créez une fonction calc_distance_3D() qui calcule la distance euclidienne en√trois dimensions entre deux atomes.
Testez votre fonction sur les 2 points A(0,0,0) et B(1,1,1). Trouvez-vous bien 3 ?
On rappelle que la distance euclidienne d entre deux points A et B de coordonnées cartésiennes respectives (xA , yA , zA )
et (xB , yB , zB ) se calcule comme suit :
√
d= (xB − xA )2 + (yB − yA )2 + (zB − zA )2
Avec la fonction random.uniform(), les bornes passées en argument sont incluses, c’est-à-dire qu’ici, le nombre
aléatoire renvoyé est dans l’intervalle [1, 10].
Créez une autre fonction calc_stat() qui prend en argument une liste de floats et qui renvoie une liste de trois
éléments contenant respectivement le minimum, le maximum et la moyenne de la liste.
Dans le programme principal, générez 20 listes aléatoires de 100 floats compris entre 0 et 100 et affichez le minimum
(min()), le maximum (max()) et la moyenne pour chacune d’entre elles. La moyenne pourra être calculée avec les
fonctions sum() et len().
Pour chacune des 20 listes, affichez les statistiques (valeur minimale, valeur maximale et moyenne) avec deux chiffres
après la virgule :
Liste 1 : min = 0.17 ; max = 99.72 ; moyenne = 57.38
Liste 2 : min = 1.25 ; max = 99.99 ; moyenne = 47.41
[...]
Liste 19 : min = 1.05 ; max = 99.36 ; moyenne = 49.43
Liste 20 : min = 1.33 ; max = 97.63 ; moyenne = 46.53
Les écarts sur les statistiques entre les différentes listes sont-ils importants ? Relancez votre script avec des listes de
1000 éléments, puis 10 000 éléments. Les écarts changent-ils quand le nombre d’éléments par liste augmente ?
En reprenant votre fonction de calcul de distance euclidienne en trois dimensions calc_distance_3D(), faites-en
une version pour deux dimensions que vous appellerez calc_distance_2D().
Créez une autre fonction calc_dist2ori(), à laquelle vous passez en argument deux listes de floats list_x et
list_y représentant les coordonnées d’une fonction mathématique (par exemple x et sin(x)). Cette fonction renverra
une liste de floats représentant la distance entre chaque point de la fonction et l’origine (de coordonnées (0, 0)).
La figure 10.2 montre un exemple sur quelques points de la fonction sin(x) (courbe en trait épais). Chaque trait
pointillé représente la distance que l’on cherche à calculer entre les points de la courbe et l’origine du repère de coordonnées
(0, 0).
Votre programme générera un fichier sin2ori.dat qui contiendra deux colonnes : la première représente les x, la
seconde la distance entre chaque point de la fonction sin(x) à l’origine.
Enfin, pour visualiser votre résultat, ajoutez le code suivant tout à la fin de votre script :
Remarque
Le module matplotlib sera expliqué en détail dans le chapitre 21 Module matplotlib.
11.1 Préambule
Nous avons déjà abordé les chaînes de caractères dans les chapitres 2 Variables et 3 Affichage. Ici nous allons un peu
plus loin, notamment avec les méthodes associées aux chaînes de caractères 1 .
Nous pouvons donc utiliser certaines propriétés des listes comme les tranches :
1 >>> animaux = "girafe tigre"
2 >>> animaux[0:4]
3 'gira'
4 >>> animaux[9:]
5 'gre'
6 >>> animaux[:-2]
7 'girafe tig'
8 >>> animaux[1:-2:2]
9 'iaetg'
Mais a contrario des listes, les chaînes de caractères présentent toutefois une différence notable, ce sont des listes
non modifiables. Une fois une chaîne de caractères définie, vous ne pouvez plus modifier un de ses éléments. Le cas
échéant, Python renvoie un message d’erreur :
1. https://docs.python.org/fr/3/library/string.html
102
11.3. Caractères spéciaux Chapitre 11. Plus sur les chaînes de caractères
Par conséquent, si vous voulez modifier une chaîne de caractères, vous devez en construire une nouvelle. Pour cela,
n’oubliez pas que les opérateurs de concaténation (+) et de duplication (*) (introduits dans le chapitre 2 Variables)
peuvent vous aider. Vous pouvez également générer une liste, qui elle est modifiable, puis revenir à une chaîne de
caractères (voir plus bas).
Vous pouvez aussi utiliser astucieusement des guillemets doubles ou simples pour déclarer votre chaîne de caractères :
1 >>> print("Un brin d'ADN")
2 Un brin d'ADN
3 >>> print('Python est un "super" langage de programmation')
4 Python est un "super" langage de programmation
Quand on souhaite écrire un texte sur plusieurs lignes, il est très commode d’utiliser les guillemets triples qui conservent
le formatage (notamment les retours à la ligne) :
1 >>> x = """souris
2 ... chat
3 ... abeille"""
4 >>> x
5 'souris\nchat\nabeille'
6 >>> print(x)
7 souris
8 chat
9 abeille
Attention, les caractères spéciaux n’apparaissent intérprétés que lorsqu’ils sont utilisés avec la fonction print(). Par
exemple, le \n n’apparait comme un retour à la ligne que lorsqu’il est dans une chaîne de caractères passée à la fonction
print() :
1 >>> "bla\nbla"
2 'bla\nbla'
3 >>> print("bla\nbla")
4 bla
5 bla
Que signifie le f que l’on accole aux guillemets de la chaîne de caractères ? Celui-ci est appelé « préfixe de chaîne de
caractères » ou stringprefix.
Remarque
Un stringprefix modifie la manière dont Python va interpréter ladite string. Celui-ci doit être systématiquement «
collé » à la chaîne de caractères, c’est-à-dire sans espace entre les deux.
Il existe différents stringprefixes en Python, nous vous montrons ici les deux qui nous apparaissent les plus importants.
• Le préfixe r mis pour raw string, qui force la non-interprétation des caractères spéciaux :
1 >>> s = "Voici un retour à la ligne\nEt là une autre ligne"
2 >>> s
3 'Voici un retour à la ligne\nEt là une autre ligne'
4 >>> print(s)
5 Voici un retour à la ligne
6 Et là une autre ligne
7 >>> s = r"Voici un retour à la ligne\nEt là une autre ligne"
8 >>> s
9 'Voici un retour à la ligne\\nEt là une autre ligne'
10 >>> print(s)
11 Voici un retour à la ligne\nEt là une autre ligne
L’ajout du r va forcer Python à ne pas interpréter le \n comme un retour à la ligne, mais comme un backslash littéral
suivi d’un n. Quand on demande à l’interpréteur d’afficher cette chaîne de caractères, celui-ci met deux backslashes
pour signifier qu’il s’agit d’un backslash littéral (le premier échappe le second). Finalement, l’utilisation de la syntaxe
r"Voici un retour à la ligne\nEt là une autre ligne" renvoie une chaîne de caractères normale, puisqu’on
voit ensuite que le r a disparu lorsqu’on demande à Python d’afficher le contenu de la variable s. Comme dans var = 2
+ 2, d’abord Python évalue 2 + 2. Puis ce résultat est affecté à la variable var. Enfin, on notera que seule l’utilisation
du print() mène à l’interprétation des caractères spéciaux comme \n, comme expliqué dans la rubrique précédente.
Les caractères spéciaux non interprétés dans les raw strings sont de manière générale tout ce dont le backslash modifie
la signification, par exemple un \n, un \t, etc.
• Le préfixe f mis pour formatted string, qui met en place l’écriture formatée comme vue au chapitre 3 Affichage :
1 >>> animal = "renard"
2 >>> animal2 = "poulain"
3 >>> s = f"Le {animal} est un animal gentil\nLe {animal2} aussi"
4 >>> s
5 'Le renard est un animal gentil\nLe poulain aussi'
6 >>> print(s)
7 Le renard est un animal gentil
8 Le poulain aussi
9 >>> s = "Le {animal} est un animal gentil\nLe {animal2} aussi"
10 >>> s
11 'Le {animal} est un animal gentil\nLe {animal2} aussi'
12 >>> print(s)
13 Le {animal} est un animal gentil
14 Le {animal2} aussi
La f-string remplace le contenu des variables situées entre les accolades et interprète le \n comme un retour à la
ligne. Pour rappel, consultez le chapitre 3 si vous souhaitez plus de détails sur le fonctionnement des f-strings.
Conseil
Il existe de nombreux autres détails concernant les préfixes qui vont au-delà de ce cours. Pour en savoir plus, vous
pouvez consulter la documentations officielle 2 .
2. https://docs.python.org/fr/3/reference/lexical_analysis.html#grammar-token-stringprefix
Les méthodes .lower() et .upper() renvoient un texte en minuscule et en majuscule respectivement. On remarque
que l’utilisation de ces méthodes n’altère pas la chaîne de caractères de départ, mais renvoie une chaîne de caractères
transformée.
Pour mettre en majuscule la première lettre seulement, vous pouvez faire :
1 >>> x[0].upper() + x[1:]
2 'Girafe'
Il existe une méthode associée aux chaînes de caractères qui est particulièrement pratique, la méthode .split() :
1 >>> animaux = "girafe tigre singe souris"
2 >>> animaux.split()
3 ['girafe', 'tigre', 'singe', 'souris']
4 >>> for animal in animaux.split():
5 ... print(animal)
6 ...
7 girafe
8 tigre
9 singe
10 souris
La méthode .split() découpe une chaîne de caractères en plusieurs éléments appelés champs, en utilisant comme
séparateur n’importe quelle combinaison « d’espace(s) blanc(s) ».
Définition
Un espace blanc 4 (whitespace en anglais) correspond aux caractères qui sont invisibles à l’œil, mais qui occupent de
l’espace dans un texte. Les espaces blancs les plus classiques sont l’espace, la tabulation et le retour à la ligne.
Attention, dans cet exemple, le séparateur est un seul caractères « : » (et non pas une combinaison de un ou plusieurs
:) conduisant ainsi à une chaîne vide entre singe et souris.
Il est également intéressant d’indiquer à .split() le nombre de fois qu’on souhaite découper la chaîne de caractères
avec l’argument maxsplit :
1 >>> animaux = "girafe tigre singe souris"
2 >>> animaux.split(maxsplit=1)
3 ['girafe', 'tigre singe souris']
4 >>> animaux.split(maxsplit=2)
5 ['girafe', 'tigre', 'singe souris']
3. https://docs.python.org/fr/3/library/string.html
4. https://en.wikipedia.org/wiki/Whitespace_character
La méthode .find(), quant à elle, recherche une chaîne de caractères passée en argument :
1 >>> animal = "girafe"
2 >>> animal.find("i")
3 1
4 >>> animal.find("afe")
5 3
6 >>> animal.find("z")
7 -1
8 >>> animal.find("tig")
9 -1
Si l’élément recherché est trouvé, alors l’indice du début de l’élément dans la chaîne de caractères est renvoyé. Si
l’élément n’est pas trouvé, alors la valeur -1 est renvoyée.
Si l’élément recherché est trouvé plusieurs fois, seul l’indice de la première occurrence est renvoyé :
1 >>> animaux = "girafe tigre"
2 >>> animaux.find("i")
3 1
On trouve aussi la méthode .replace() qui substitue une chaîne de caractères par une autre :
1 >>> animaux = "girafe tigre"
2 >>> animaux.replace("tigre", "singe")
3 'girafe singe'
4 >>> animaux.replace("i", "o")
5 'gorafe togre'
La méthode .count() compte le nombre d’occurrences d’une chaîne de caractères passée en argument :
1 >>> animaux = "girafe tigre"
2 >>> animaux.count("i")
3 2
4 >>> animaux.count("z")
5 0
6 >>> animaux.count("tigre")
7 1
La méthode .startswith() vérifie si une chaîne de caractères commence par une autre chaîne de caractères :
1 >>> chaine = "Bonjour monsieur le capitaine !"
2 >>> chaine.startswith("Bonjour")
3 True
4 >>> chaine.startswith("Au revoir")
5 False
Cette méthode est particulièrement utile lorsqu’on lit un fichier et que l’on veut récupérer certaines lignes commençant
par un mot-clé. Par exemple dans un fichier PDB, les lignes contenant les coordonnées des atomes commencent par le
mot-clé ATOM.
Enfin, la méthode .strip() permet de « nettoyer les bords » d’une chaîne de caractères :
1 >>> chaine = " Comment enlever les espaces au début et à la fin ? "
2 >>> chaine.strip()
3 'Comment enlever les espaces au début et à la fin ?'
La méthode .strip() enlève les espaces situés sur les bords de la chaîne de caractère mais pas ceux situés entre des
caractères visibles. En réalité, cette méthode enlève n’importe quel combinaison « d’espace(s) blanc(s) » sur les bords,
par exemple :
1 >>> chaine = " \tfonctionne avec les tabulations et les retours à la ligne\n"
2 >>> chaine.strip()
3 'fonctionne avec les tabulations et les retours à la ligne'
Cette méthode est utile pour se débarrasser des retours à la ligne quand on lit un fichier.
On souhaite extraire les valeurs 3.4 et 17.2 pour ensuite les additionner.
D’abord, on découpe la chaîne de caractères avec la méthode .split() :
1 >>> liste1 = chaine1.split()
2 >>> liste1
3 ['3.4', '17.2', 'atom']
4 >>> nb1, nb2, nom = liste1
5 >>> nb1
6 '3.4'
7 >>> nb2
8 '17.2'
On obtient alors une liste de chaînes de caractères liste1. Avec l’affectation multiple, on récupère les nombres
souhaités dans nb1 et nb2, mais ils sont toujours sous forme de chaîne de caractères. Il faut ensuite les convertir en floats
pour pouvoir les additionner :
1 >>> float(nb1) + float(nb2)
2 20.599999999999998
Remarque
Retenez bien l’utilisation des instructions précédentes pour extraire des valeurs numériques d’une chaîne de caractères.
Elles sont régulièrement employées pour analyser des données extraites d’un fichier.
Conseil
Si vous êtes débutant, vous pouvez sauter cette rubrique.
La fonction map() permet d’appliquer une fonction à plusieurs éléments d’un objet itérable. Par exemple, si on a une
chaîne de caractères avec trois entiers séparés par des espaces, on peut extraire et convertir les trois nombres en entier
en une seule ligne. La fonction map() produit un objet de type map qui est itérable et transformable en liste :
1 >>> ligne = "67 946 -45"
2 >>> ligne.split()
3 ['67', '946', '-45']
4 >>> map(int, ligne.split())
5 <map object at 0x7fa34e573b20>
6 >>> for entier in map(int, ligne.split()):
7 ... print(entier)
8 ...
9 67
10 946
11 -45
12 >>> list(map(int, ligne.split()))
13 [67, 946, -45]
Remarque
La fonction map() prend deux arguments. Le second est un objet itérable, souvent une liste comme dans notre
exemple. Le premier argument est le nom d’une fonction qu’on souhaite appliquer à chaque élément de la liste, mais sans
les parenthèses (ici int et non pas int()). Une fonction passée en argument d’une autre fonction est appelée fonction
de rappel 5 ou callback en anglais. Nous reverrons cette notion dans le chapitre 25 Fenêtres graphiques et Tkinter (en
ligne).
La fonction map() est particulièrement utile lorsqu’on lit un fichier de valeurs numériques. Par exemple, si on a un
fichier data.dat contenant trois colonnes de nombres, map() en conjonction avec .split() permet de séparer les trois
nombres puis de les convertir en float en une seule ligne de code :
1 with open("data.dat", "r") as filin:
2 for line in filin:
3 x, y, z = map(float, line.split())
4 print(x + y + z)
Sans map(), il aurait fallu une ligne pour séparer les données x, y, z = line.split() et une autre pour les
transformer en float x, y, z = float(x), float(y), float(z).
Enfin, on peut utiliser map() avec ses propres fonctions :
1 >>> def calc_cube(x):
2 ... return x**3
3 ...
4 >>> list(map(calc_cube, [1, 2, 3, 4]))
5 [1, 8, 27, 64]
Notez que la chaîne testée peut-être présente à n’importe quelle position dans l’autre chaîne. Par ailleurs, le test est
vrai si elle est présente une ou plusieurs fois.
La variation avec l’opérateur booléen not permet de vérifier qu’une chaîne n’est pas présente dans une autre chaîne :
1 >>> not "toto" in chaine
2 True
3 >>> not "fer" in chaine
4 False
5. https://fr.wikipedia.org/wiki/Fonction_de_rappel
Les éléments de la liste initiale sont concaténés les uns à la suite des autres et intercalés par un séparateur, qui peut
être n’importe quelle chaîne de caractères. Ici, on a utilisé un tiret, un espace et rien (une chaîne de caractères vide).
Attention, la méthode .join() ne s’applique qu’à une liste de chaînes de caractères :
1 >>> maliste = ["A", 5, "G"]
2 >>> " ".join(maliste)
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 TypeError: sequence item 1: expected str instance, int found
On espère qu’après ce petit tour d’horizon vous serez convaincu de la richesse des méthodes associées aux chaînes de
caractères. Pour avoir une liste exhaustive de l’ensemble des méthodes associées à une variable particulière, vous pouvez
utiliser la fonction dir() :
1 >>> animaux = "girafe tigre"
2 >>> dir(animaux)
3 ['__add__', '__class__', '__contains__', '__delattr__', '__dir__',
4 ...,
5 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition',
6 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip',
7 'swapcase', 'title', 'translate', 'upper', 'zfill']
Pour l’instant, vous pouvez ignorer les méthodes qui commencent et qui se terminent par deux tirets bas (underscores)
__. Nous n’avons pas mis l’ensemble de la sortie de cette commande dir() pour ne pas surcharger le texte, mais n’hésitez
pas à la tester dans l’interpréteur.
Vous pouvez également accéder à l’aide et à la documentation d’une méthode particulière avec help(), par exemple
pour la méthode .split() :
>>> help(animaux.split)
Help on built-in function split:
split(...)
S.split([sep [,maxsplit]]) -> list of strings
Attention à ne pas mettre les parenthèses à la suite du nom de la méthode. L’instruction correcte est help(animaux
.split) et non pas help(animaux.split()).
On a créé deux variables intermédiaires message1 et message2 pour stocker les chaînes de caractères modifiées par
les méthodes .title() et .replace().
Il est possible de faire la même chose en une seule ligne, en utilisant le chaînage de méthodes ou method chaining :
1 >>> message = "salut patrick salut pierre"
2 >>> message.title().replace("Salut", "Bonjour").split()
3 ['Bonjour', 'Patrick', 'Bonjour', 'Pierre']
On peut aussi utiliser des parenthèses pour couper une ligne de code en plusieurs lignes :
1 >>> message = "salut patrick salut pierre"
2 >>> (message
3 ... .title()
4 ... .replace("Salut", "Bonjour")
5 ... .split()
6 ... )
7 ['Bonjour', 'Patrick', 'Bonjour', 'Pierre']
L’utilisation de parenthèses permet aussi de couper une chaîne de caractères en plusieurs lignes :
1 >>> ma_chaine = (
2 ... "voici une chaine de caractères "
3 ... "très longue "
4 ... "sur plusieurs lignes")
5 >>> ma_chaine
6 'voici une chaine de caractères très longue sur plusieurs lignes'
11.11 Exercices
Conseil
Pour ces exercices, créez des scripts puis exécutez-les dans un shell.
où WWW et XXX sont des entiers et YYYYYYYYYY et ZZZZZZZZZZ sont des bases.
Conseil
Vous trouverez des explications sur le format FASTA et des exemples de code dans l’annexe A Quelques formats de
données en biologie.
6. https://python.sdv.u-paris.fr/data-files/UBI4_SCerevisiae.fasta
7. https://fr.wikipedia.org/wiki/Distance_de_Hamming
8. https://python.sdv.u-paris.fr/data-files/notes.csv
Créez un programme qui lit chaque ligne du fichier et construit une liste de dictionnaire du style [{"nom": "Jason",
"geo": 17, "sport": 3, "anglais": 1}, ...]. Utilisez si possible la fonction map() pour convertir les nombres
lus dans le fichier en entiers. Réalisez ensuite une boucle sur cette liste de dictionnaires, et affichez le nom de l’étudiant,
sa note en sport et sa note en anglais. Affichez ensuite la moyenne des notes de sport et de géographie pour tous les
étudiants.
11.11.6 Conversion des acides aminés du code à trois lettres au code à une lettre
Créez une fonction convert_3_lettres_1_lettre() qui prend en argument une chaîne de caractères avec des
acides aminés en code à trois lettres et renvoie une chaîne de caractères avec les acides aminés en code à une lettre.
Vous pourrez tenter d’utiliser le method chaining dans cette fonction.
Utilisez cette fonction pour convertir la séquence protéique ALA GLY GLU ARG TRP TYR SER GLY ALA TRP.
Rappel de la nomenclature des acides aminés :
Acide aminé Code 3-lettres Code 1-lettre Acide aminé Code 3-lettres Code 1-lettre
Alanine Ala A Leucine Leu L
Arginine Arg R Lysine Lys K
Asparagine Asn N Méthionine Met M
Aspartate Asp D Phénylalanine Phe F
Cystéine Cys C Proline Pro P
Glutamate Glu E Sérine Ser S
Glutamine Gln Q Thréonine Thr T
Glycine Gly G Tryptophane Trp W
Histidine His H Tyrosine Tyr Y
Isoleucine Ile I Valine Val V
11.11.7 Palindrome
Un palindrome est un mot ou une phrase dont l’ordre des lettres reste le même si on le lit de gauche à droite ou de
droite à gauche. Par exemple, « ressasser » et « engage le jeu que je le gagne » sont des palindromes.
Créez la fonction est_palindrome() qui prend en argument une chaîne de caractères et qui renvoie un booléen
(True si l’argument est un palindrome, False si ce n’est pas le cas). Dans le programme principal, affichez xxx est
un palindrome si la fonction est_palindrome() renvoie True sinon xxx n'est pas un palindrome. Pensez à
vous débarrasser au préalable des majuscules, des signes de ponctuations et des espaces.
Testez ensuite si les expressions suivantes sont des palindromes :
• Radar
• Never odd or even
• Karine alla en Iran
• Un roc si biscornu
• Et la marine ira vers Malte
• Deer Madam, Reed
• rotator
• Was it a car or a cat I saw?
Conseil
Pour le nettoyage de la chaîne de caractères (retrait des majuscules, signes de ponctations et espaces), essayer d’utiliser
le method chaining.
Créez la fonction est_composable(), qui prend en argument un mot (sous la forme d’une chaîne de caractères) et
une séquence de lettres (aussi comme une chaîne de caractères), et qui renvoie True si le mot est composable à partir
de la séquence, sinon False.
Dans le programme principal, créez une liste de tuples contenant les couples mot / séquence, de la forme [('mot1',
'sequence1'), ('mot2', 'sequence2'), ...]. Utilisez ensuite une boucle sur tous les couples mot / séquence, et
appelez à chaque itération la fonction est_composable(). Affichez enfin Le mot xxx est composable à partir
de yyy si le mot xxx est composable à partir de la séquence de lettres (yyy). Affichez Le mot xxx n'est pas
composable à partir de yyy si ce n’est pas le cas.
Testez cette fonction avec les mots et les séquences suivantes :
Mot Séquence
python aophrtkny
python aeiouyhpq
coucou uocuoceokzezh
fonction nhwfnitvkloco
11.11.10 Lecture d’une séquence à partir d’un fichier GenBank (exercice +++)
On cherche à récupérer la séquence d’ADN du chromosome I de la levure Saccharomyces cerevisiae contenu dans le
fichier au format GenBank NC_001133.gbk 10 .
Le format GenBank est présenté en détail dans l’annexe A Quelques formats de données en biologie. Pour cet exercice,
vous devez savoir que la séquence démarre après la ligne commençant par le mot ORIGIN et se termine avant la ligne
commençant par les caractères // :
ORIGIN
1 ccacaccaca cccacacacc cacacaccac accacacacc acaccacacc cacacacaca
61 catcctaaca ctaccctaac acagccctaa tctaaccctg gccaacctgt ctctcaactt
[...]
230101 tgttagtgtt agtattaggg tgtggtgtgt gggtgtggtg tgggtgtggg tgtgggtgtg
230161 ggtgtgggtg tgggtgtggt gtggtgtgtg ggtgtggtgt gggtgtggtg tgtgtggg
//
Pour extraire la séquence d’ADN, nous vous proposons d’utiliser un algorithme de « drapeau », c’est-à-dire une variable
qui sera à True lorsqu’on lira les lignes contenant la séquence et à False pour les autres lignes.
9. http://fr.wikipedia.org/wiki/Pangramme
10. https://python.sdv.u-paris.fr/data-files/NC_001133.gbk
Créez une fonction lit_genbank() qui prend comme argument le nom d’un fichier GenBank sous la forme d’une
chaîne de caractères, lit la séquence dans le fichier GenBank et la renvoie sous la forme d’une chaîne de caractères.
Utilisez ensuite cette fonction pour récupérer la séquence d’ADN dans la variable sequence dans le programme
principal. Le script affichera :
NC_001133.gbk
La séquence contient XXX bases
10 premières bases : YYYYYYYYYY
10 dernières bases : ZZZZZZZZZZ
Conseil
Vous trouverez des explications sur le format PDB et des exemples de code pour lire ce type de fichier en Python
dans l’annexe A Quelques formats de données en biologie.
11.11.12 Calcul des distances entre les carbones alpha consécutifs d’une structure de pro-
téine (exercice +++)
En utilisant la fonction trouve_calpha() précédente, calculez la distance interatomique entre les carbones alpha
des deux premiers résidus (avec deux chiffres après la virgule).
Rappel : la distance euclidienne d entre deux points A et B de coordonnées cartésiennes respectives (xA , yA , zA ) et
(xB , yB , zB ) se calcule comme suit :
√
d= (xB − xA )2 + (yB − yA )2 + (zB − zA )2
Créez ensuite la fonction calcule_distance() qui prend en argument la liste renvoyée par la fonction trouve_calpha
(), qui calcule les distances interatomiques entre carbones alpha consécutifs et affiche ces distances sous la forme :
numero_calpha_1 numero_calpha_2 distance
Les numéros des carbones alpha seront affichés sur deux caractères. La distance sera affichée avec deux chiffres après
la virgule. Voici un exemple avec les premiers carbones alpha :
11. https://files.rcsb.org/download/1BTA.pdb
12. http://www.rcsb.org/pdb/explore.do?structureId=1BTA
1 2 3.80
2 3 3.80
3 4 3.83
4 5 3.82
Modifiez maintenant la fonction calcule_distance() pour qu’elle affiche à la fin la moyenne des distances.
La distance inter-carbone alpha dans les protéines est très stable et de l’ordre de 3,8 angströms. Observez avec attention
les valeurs que vous avez calculées pour la protéine barstar. Repérez une valeur surprenante. Essayez de l’expliquer.
Conseil
Vous trouverez des explications sur le format PDB et des exemples de code pour lire ce type de fichier en Python
dans l’annexe A Quelques formats de données en biologie.
Lorsque la ligne contient le mot complement le gène est situé sur le brin complémentaire, sinon il est situé sur le brin
direct. Votre code devra récupérer le premier et le second nombre indiquant respectivement la position du début et de
fin du gène. Attention à bien les convertir en entier afin de pouvoir calculer la longueur du gène. Notez que les caractères
> et < doivent être ignorés, et que les .. servent à séparer la position de début et de fin.
On souhaite obtenir une sortie de la forme :
gène 1 complémentaire -> 362 bases
gène 2 direct -> 227 bases
gène 3 complémentaire -> 1781 bases
[...]
gène 99 direct -> 611 bases
gène 100 direct -> 485 bases
gène 101 direct -> 1403 bases
Conseil
Vous trouverez des explications sur le format GenBank dans l’annexe A Quelques formats de données en biologie.
13. https://python.sdv.u-paris.fr/data-files/NC_001133.gbk
Nous avons vu les listes dès le chapitre 4 et les avons largement utilisées depuis le début de ce cours. Dans ce chapitre,
nous allons plus loin avec les méthodes associées aux listes, ainsi que d’autres caractéristiques très puissantes telles que
les tests d’appartenance ou les listes de compréhension.
12.1.1 .append()
La méthode .append(), que l’on a déjà vu au chapitre 4 Listes, ajoute un élément à la fin d’une liste :
1 >>> liste1 = [1, 2, 3]
2 >>> liste1.append(5)
3 >>> liste1
4 [1, 2, 3, 5]
Conseil
Préférez la version avec .append() qui est plus compacte et facile à lire.
12.1.2 .insert()
La méthode .insert() insère un objet dans une liste à un indice déterminé :
116
12.1. Méthodes associées aux listes Chapitre 12. Plus sur les listes
12.1.3 del
L’instruction del supprime un élément d’une liste à un indice déterminé :
1 >>> liste1 = [1, 2, 3]
2 >>> del liste1[1]
3 >>> liste1
4 [1, 3]
Remarque
Contrairement aux méthodes associées aux listes présentées dans cette rubrique, del est une instruction générale de
Python, utilisable pour d’autres objets que des listes. Celle-ci ne prend pas de parenthèse.
12.1.4 .remove()
La méthode .remove() supprime un élément d’une liste à partir de sa valeur :
1 >>> liste1 = [1, 2, 3]
2 >>> liste1.remove(3)
3 >>> liste1
4 [1, 2]
S’il y a plusieurs fois la même valeur dans la liste, seule la première est retirée. Il faut appeler la méthode .remove()
autant de fois que nécessaire pour retirer toutes les occurences d’un même élément :
1 >>> liste1 = [1, 2, 3, 4, 3]
2 >>> liste1.remove(3)
3 >>> liste1
4 [1, 2, 4, 3]
5 >>> liste1.remove(3)
6 >>> liste1
7 [1, 2, 4]
12.1.5 .sort()
La méthode .sort() trie les éléments d’une liste du plus petit au plus grand :
1 >>> liste1 = [3, 1, 2]
2 >>> liste1.sort()
3 >>> liste1
4 [1, 2, 3]
L’argument reverse=True spécifie le tri inverse, c’est-à-dire du plus grand au plus petit élément :
1 >>> liste1 = [3, 1, 2]
2 >>> liste1.sort(reverse=True)
3 >>> liste1
4 [3, 2, 1]
12.1.6 sorted()
La fonction sorted() trie également une liste. Contrairement à la méthode précédente .sort(), cette fonction
renvoie la liste triée et ne modifie pas la liste initiale :
1 >>> liste1 = [3, 1, 2]
2 >>> sorted(liste1)
3 [1, 2, 3]
4 >>> liste1
5 [3, 1, 2]
12.1.7 .reverse()
La méthode .reverse() inverse une liste :
1 >>> liste1 = [3, 1, 2]
2 >>> liste1.reverse()
3 >>> liste1
4 [2, 1, 3]
12.1.8 .count()
La méthode .count() compte le nombre d’éléments (passés en argument) dans une liste :
1 >>> liste1 = [1, 2, 4, 3, 1, 1]
2 >>> liste1.count(1)
3 3
4 >>> liste1.count(4)
5 1
6 >>> liste1.count(23)
7 0
Remarque
Pour exprimer la même idée, la documentation parle de modification de la liste « sur place » (in place en anglais) :
1 >>> liste1 = [1, 2, 3]
2 >>> help(liste1.reverse)
3 Help on built-in function reverse:
4
5 reverse() method of builtins.list instance
6 Reverse *IN PLACE*.
Cela signifie que la liste est modifiée « sur place », c’est-à-dire dans la méthode au moment où elle s’exécute. La liste
étant modifiée « en dur » dans la méthode, cette dernière ne renvoie donc rien. L’explication du mécanisme sous-jacent
vous sera donnée dans la rubrique 13.4 Portée des listes du chapitre 13 Plus sur les fonctions.
Par ailleurs, certaines méthodes ou instructions des listes décalent les indices d’une liste (par exemple .insert(),
del, etc.).
Enfin, pour obtenir une liste exhaustive des méthodes disponibles pour les listes, utilisez la fonction dir(liste1)
(liste1 étant une liste).
Remarquez que dans cet exemple, vous pouvez aussi utiliser directement la fonction list() qui prend n’importe quel
objet séquentiel (liste, chaîne de caractères, etc.) et qui renvoie une liste :
1 >>> seq = "CAAAGGTAACGC"
2 >>> list(seq)
3 ['C', 'A', 'A', 'A', 'G', 'G', 'T', 'A', 'A', 'C', 'G', 'C']
Cette méthode est certes plus simple, mais il arrive parfois qu’on doive utiliser des boucles tout de même, comme
lorsqu’on lit un fichier. Nous vous rappellons que l’instruction list(seq) convertit un objet de type chaîne de caractères
en un objet de type liste (il s’agit donc d’une opération de casting). De même que list(range(10)) convertit un objet
de type range en un objet de type list.
La variation avec not permet, a contrario, de vérifier qu’un élément n’est pas dans une liste.
Conseil
Si vous êtes débutant, vous pouvez sauter cette rubrique.
Lignes 3 et 6. On passe en argument deux listes à zip() qui génère un nouvel objet de type zip. Comme pour les
objets de type map vu au chapitre 11 Plus sur les chaînes de caractères, les objets zip sont itérables.
Lignes 7 à 12. Lorsqu’on itère sur un objet zip, la variable d’itération est un tuple. À la première itération, on a un
tuple avec le premier élément de chaque liste utilisée pour générer l’objet zip, à la deuxième itération, ce sera le deuxième
élément, et ainsi de suite.
Lignes 13 à 18. Avec l’affectation multiple, on peut affecter à la volée les éléments à des variables différentes, comme
on l’a fait avec la fonction enumerate() (chapitre 5 Boucles) et la méthode .items() des dictionnaires (chapitre 8
Dictionnaires et tuples).
Un objet zip est aussi utile pour générer facilement une liste de tuples.
1 >>> list(zip(animaux, couleurs))
2 [('poulain', 'alezan'), ('renard', 'roux'), ('python', 'vert')]
Si une des listes passée en argument n’a pas la même longueur, l’objet zip s’arrête sur la liste la plus courte :
1 >>> animaux = ["poulain", "renard", "python", "orque"]
2 >>> couleurs = ["alezan", "roux", "vert"]
3 >>> list(zip(animaux, couleurs))
4 [('poulain', 'alezan'), ('renard', 'roux'), ('python', 'vert')]
On peut empêcher ce comportement avec l’argument par mot-clé strict, qui renvoie une erreur si les listes n’ont
pas la même longueur :
1 >>> list(zip(animaux, couleurs, strict=True))
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 ValueError: zip() argument 2 is shorter than argument 1
Enfin, il est possible de créer des objets zip avec autant de listes que l’on veut :
1 >>> animaux = ["poulain", "renard", "python"]
2 >>> couleurs = ["alezan", "roux", "vert"]
3 >>> numero = [1, 2, 3]
4 >>> list(zip(numero, animaux, couleurs))
5 [(1, 'poulain', 'alezan'), (2, 'renard', 'roux'), (3, 'python', 'vert')]
Remarque
La fonction zip() fonctionne sur n’importe quel objet itérable : listes, tuples, dictionnaires, objets range, etc.
Conseil
Pour les débutants, vous pouvez sauter cette remarque.
Un objet zip() comme présenté plus haut est ce qu’on appelle un itérateur. Cela implique un mode de fonctionnement
particulier, notamment le fait qu’on ne peut l’utiliser qu’une fois lorsqu’on l’a créé. Vous trouverez plus d’explications sur
la définition et le fonctionnement d’un itérateur dans le chapitre 26 Remarques complémentaires.
Vous voyez que la modification de liste1 modifie liste2 aussi ! Pour comprendre ce qu’il se passe, nous allons de
nouveau utiliser le site Python Tutor avec cet exemple (Figure 12.1) :
Techniquement, Python utilise des pointeurs (comme dans le langage de programmation C) vers les mêmes objets.
Python Tutor l’illustre avec des flèches qui partent des variables liste1 et liste2 et qui pointent vers la même liste.
Donc, si on modifie la liste liste1, la liste liste2 est modifiée de la même manière. Rappelez-vous de ceci dans vos
futurs programmes, car cela pourrait avoir des effets désastreux !
Pour éviter ce problème, il va falloir créer une copie explicite de la liste initiale. Observez cet exemple :
1 >>> liste1 = [1, 2, 3]
2 >>> liste2 = liste1[:]
3 >>> liste1[1] = -15
4 >>> liste2
5 [1, 2, 3]
L’instruction liste1[:] a créé une copie « à la volée » de la liste liste1. Vous pouvez utiliser aussi la fonction
list(), qui renvoie explicitement une liste :
1 >>> liste1 = [1, 2, 3]
2 >>> liste2 = list(liste1)
3 >>> liste1[1] = -15
4 >>> liste2
5 [1, 2, 3]
Si on regarde à nouveau dans Python Tutor (Figure 12.2), on voit clairement que l’utilisation d’une tranche [:] ou
de la fonction list() crée des copies explicites. Chaque flèche pointe vers une liste différente, indépendante des autres.
Figure 12.2 – Copie de liste avec une tranche [:] et la fonction list().
Attention, les deux astuces précédentes ne fonctionnent que pour les listes à une dimension, autrement dit les listes
qui ne contiennent pas elles-mêmes d’autres listes. Voyez par exemple :
1 >>> liste1 = [[1, 2], [3, 4]]
2 >>> liste1
3 [[1, 2], [3, 4]]
4 >>> liste2 = liste1[:]
5 >>> liste1[1][1] = 55
6 >>> liste1
7 [[1, 2], [3, 55]]
8 >>> liste2
9 [[1, 2], [3, 55]]
et
1 >>> liste2 = list(liste1)
2 >>> liste1[1][1] = 77
3 >>> liste1
4 [[1, 2], [3, 77]]
5 >>> liste2
6 [[1, 2], [3, 77]]
La méthode de copie qui fonctionne à tous les coups consiste à appeler la fonction deepcopy() du module copy :
1 >>> import copy
2 >>> liste1 = [[1, 2], [3, 4]]
3 >>> liste1
4 [[1, 2], [3, 4]]
5 >>> liste2 = copy.deepcopy(liste1)
6 >>> liste1[1][1] = 99
7 >>> liste1
8 [[1, 2], [3, 99]]
9 >>> liste2
10 [[1, 2], [3, 4]]
Vous constatez qu’il est modifié dans chaque sous-liste ! À l’aide de Python Tutor on voit que Python crée une
référence vers la même sous-liste (Figure 12.3) :
Comme disent les auteurs dans la documentation officielle 1 : Note that items in the sequence are not copied ; they
are referenced multiple times. This often haunts new Python programmers. Pour éviter le problème, on peut utiliser une
boucle :
1 >>> liste1 = []
2 >>> for i in range(5):
3 ... liste1.append([0, 0, 0])
4 ...
5 >>> liste1
6 [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
7 >>> liste1[2][0] = -12
8 >>> liste1
9 [[0, 0, 0], [0, 0, 0], [-12, 0, 0], [0, 0, 0], [0, 0, 0]]
On verra dans la rubrique suivante une manière très compacte de faire cela avec les listes de compréhension.
Attention
Même si une liste de listes peut représenter un tableau de nombres, il ne faut pas la voir comme un objet mathématique
de type matrice 2 . En effet, le concept de lignes et colonnes n’est pas défini clairement, on ne peut pas faire d’opérations
matricielles simplement, etc. On verra dans le chapitre 20 Module Numpy qu’il existe des objets appelés arrays qui sont
faits pour ça.
Conseil
Si vous êtes débutant, vous pouvez sauter cette rubrique.
En Python, la notion de liste de compréhension (ou compréhension de listes) représente une manière originale et très
puissante de générer des listes. La syntaxe de base consiste au moins en une boucle for au sein de crochets précédés
d’une variable (qui peut être la variable d’itération ou pas) :
1. https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
2. https://fr.wikipedia.org/wiki/Matrice
3. http://www.python.org/dev/peps/pep-0202/
4. http://fr.wikipedia.org/wiki/Comprehension_de_liste
Conseil
Pour plus de lisiblité, il est possible de répartir la liste de compréhension sur plusieurs lignes.
La variable d’itération idx_a reste disponible en dehors de la boucle for. Par contre, la variable d’itération idx_b
n’est pas disponible en dehors de la liste de compréhension, car elle est créée « à la volée » par Python puis éliminée une
fois l’instruction exécutée.
Conseil
Si vous êtes débutant, vous pouvez sauter cette rubrique.
Un peu plus haut nous avons évoqué la méthode .sort() qui trie une liste sur place, ainsi que la fonction sorted()
qui renvoie une nouvelle liste triée. Nous avons également vu qu’elles supportaient l’argument par mot-clé reverse pour
trier dans le sens inverse (décroissant ou anti-ASCII). Il existe un autre argument par mot-clé nommé key permettant un
tri avec des règles alternatives que nous pouvons customiser. On doit passer à key une fonction callback (nous avions
déjà croisé cette notion avec la fonction map() dans le chapitre 11 Plus sur les chaînes de caractères, pour une définition
voir le chapitre 25 Fenêtres graphiques et Tkinter (en ligne)), c’est-à-dire, un nom de fonction sans les parenthèses. Par
exemple, si on passe la callback len comme ça :
5. http://www.rcsb.org/pdb/explore.do?structureId=1BTA
Python trie la liste mots en considérant la longeur de chaque élément, donc ici le nombre de lettres de chaque chaîne
de caractères. Si plusieurs mots ont la même longueur (bar et bam dans l’exemple suivant), sorted() les laisse dans
l’ordre de la liste initiale.
1 >>> mots = ["bar", "babar", "bam", "ba", "bababar"]
2 >>> sorted(mots, key=len)
3 ['ba', 'bar', 'bam', 'babar', 'bababar']
Là où key va se révéler puissant est quand nous allons lui passer une fonction « maison ». Voici une exemple :
1 >>> def compte_b(chaine):
2 ... return chaine.count("b")
3 ...
4 >>> compte_b("babar")
5 2
6 >>> mots = ["bar", "babar", "bam", "ba", "bababar"]
7 >>> sorted(mots, key=compte_b)
8 ['bar', 'bam', 'ba', 'babar', 'bababar']
• Lignes 1 à 5. Comme son nom l’indique, la fonction compte_b() compte les lettres b dans une chaîne de
caractères.
• Lignes 7 et 8. En donnant compte_b (notez l’absence de parenthèses) à l’argument key, Python trie en fonction
du nombre de lettres b dans chaque mot ! Comme pour len, si plusieurs mots ont un nombre de lettres b identiques,
il conserve l’ordre de la liste initiale.
Remarque
L’argument key fonctionne de la même manière entre sorted() et la méthode .sort() qui trie sur place. Cet
argument existe aussi avec les fonctions min() et max(). Par exemple :
1 >>> mots = ["bar", "babar", "bam", "ba", "bababar"]
2 >>> min(mots, key=len)
3 'ba'
4 >>> max(mots, key=len)
5 'bababar'
Python renverra le premier élément avec min() ou le dernier élément avec max() après un tri sur la longueur de
chaque mot.
12.9 Exercices
Conseil
Pour ces exercices, créez des scripts puis exécutez-les dans un shell.
12.9.4 Doublons
Soit la liste de nombres liste1 = [5, 1, 1, 2, 5, 6, 3, 4, 4, 4, 2]. À partir de liste1, créez une nouvelle
liste sans les doublons, triez-la et affichez-la.
Conseil
Utilisez la fonction random.choises() avec les paramètres k et weights. Le paramètre k spécifie le nombre de
tirages aléatoires à réaliser et le paramètre weights indique les probabilités de tirage.
Par exemple, pour réaliser 10 tirages aléatoires entre les lettres A et B avec 80% de A et 20% de B, on utilise la
fonction random.choices() de la manière suivante :
1 >>> import random
2 >>> random.choices("AB", k=10, weights=[80, 20])
3 ['A', 'A', 'A', 'A', 'A', 'A', 'A', 'B', 'A', 'B']
Déduisez comment une ligne est construite à partir de la précédente. Par exemple, à partir de la ligne 2 (1 1),
construisez la ligne suivante (ligne 3 : 1 2 1) et ainsi de suite.
Implémentez cette construction en Python. Généralisez à l’aide d’une boucle.
Écrivez dans un fichier pascal.out les 10 premières lignes du triangle de Pascal.
Avant d’aborder ce chapitre, nous vous conseillons de relire le chapitre 10 Fonctions et de bien en assimiler toutes les
notions (et aussi d’en faire les exercices). Nous avons vu dans ce chapitre 10 le concept incontournable que représentent
les fonctions. Nous avons également introduit la notion de variables locales et globales.
Dans ce chapitre, nous allons aller un peu plus loin sur la visibilité de ces variables dans et hors des fonctions, et
aussi voir ce qui se passe lorsque ces variables sont des listes. Attention, la plupart des lignes de code ci-dessous sont
données à titre d’exemple pour bien comprendre ce qui se passe, mais nombre d’entre elles sont des aberrations en terme
de programmation. Nous ferons un récapitulatif des bonnes pratiques à la fin du chapitre. Enfin, nous vous conseillons
de tester tous les exemples ci-dessous avec le site Python Tutor 1 afin de suivre l’état des variables lors de l’exécution des
exemples.
Nous appelons la fonction calc_somme_nb_pairs() depuis le programme principal, puis à l’intérieur de celle-ci nous
1. http://www.pythontutor.com/
129
Chapitre 13. Plus sur les fonctions 13.2. Fonctions récursives
appelons l’autre fonction est_pair(). Regardons ce que Python Tutor nous montre lorsque la fonction calc_somme_nb_pairs
() est exécutée dans la Figure 13.1.
L’espace mémoire alloué à est_pair() est grisé, indiquant que cette fonction est en cours d’exécution. La fonction
appelante calc_somme_nb_pairs() est toujours là (sur un fond blanc) car son exécution n’est pas terminée. Elle est
en quelque sorte figée dans le même état qu’avant l’appel de est_pair(), et on pourra ainsi noter que ses variables
locales (debut, fin) sont toujours là. De manière générale, les variables locales d’une fonction ne seront détruites que
lorsque l’exécution de celle-ci sera terminée. Dans notre exemple, les variables locales de calc_somme_nb_pairs() ne
seront détruites que lorsque la boucle sera terminée et que la variable somme sera retournée au programme principal.
Enfin, notez bien que la fonction calc_somme_nb_pairs() appelle la fonction est_pair() à chaque itération de la
boucle.
Ainsi, le programmeur est libre de faire tous les appels qu’il souhaite. Une fonction peut appeler une autre fonction,
cette dernière peut appeler une autre fonction et ainsi de suite (et autant de fois qu’on le veut). Une fonction peut même
s’appeler elle-même, cela s’appelle une fonction récursive (voir la rubrique suivante). Attention toutefois à retrouver vos
petits si vous vous perdez dans les appels successifs !
Conseil
Dans la fonction est_pair() on teste si le nombre est pair et on renvoie True, sinon on renvoie False. Cette
fonction pourrait être écrite de manière plus compacte :
1 def est_pair(x):
2 return x % 2
Comme l’expression x % 2 renvoie un booléen directement, elle revient au même que le if / else ci-dessus. C’est
bien sûr cette dernière notation plus compacte que nous vous recommandons.
Conseil
Une fonction récursive est une fonction qui s’appelle elle-même. Les fonctions récursives permettent d’obtenir une
efficacité redoutable dans la résolution de certains algorithmes, comme le tri rapide 2 (en anglais, quicksort).
Oublions la recherche d’efficacité pour l’instant et concentrons-nous sur l’exemple de la fonction mathématique
factorielle. Nous vous rappelons que la factorielle s’écrit avec un ! et se définit de la manière suivante :
3! = 3 × 2 × 1 = 6
4! = 4 × 3 × 2 × 1 = 30
...
n! = n × n − 1 × . . . × 2 × 1
Pas si facile à comprendre, n’est-ce pas ? À nouveau, aidons nous de Python Tutor pour visualiser ce qui se passe
dans la figure 13.2 (nous vous conseillons bien sûr de tester vous-même cet exemple) :
Ligne 8, on appelle la fonction calc_factorielle() en passant comme argument l’entier 4. Dans la fonction, la
variable locale qui récupère cet argument est nb. Au sein de la fonction, celle-ci se rappelle elle-même (ligne 5), mais cette
fois-ci en passant la valeur 3. Au prochain appel, ce sera avec la valeur 2, puis finalement 1. Dans ce dernier cas, le test
2. https://fr.wikipedia.org/wiki/Tri_rapide
if nb == 1: est vrai et l’instruction return 1 sera exécutée. À ce moment précis de l’exécution, les appels successifs
forment une sorte de pile (voir la figure 13.2). La valeur 1 sera ainsi renvoyée au niveau de l’appel précédent, puis le
résultat 2 × 1 = 2 (où 2 correspond à nb et 1 provient de calc_factorielle(nb - 1), soit 1) va être renvoyé à l’appel
précédent, puis 3 × 2 = 6 (où 3 correspond à nb et 2 provient de calc_factorielle(nb - 1), soit 2) va être renvoyé
à l’appel précédent, pour finir par 4 × 6 = 24 (où 4 correspond à nb et 6 provient de calc_factorielle(nb - 1), soit
6), soit la valeur de 4!. Les appels successifs vont donc se « dépiler » et nous reviendrons dans le programme principal.
Même si les fonctions récursives peuvent être ardues à comprendre, notre propos est ici de vous illustrer qu’une
fonction qui en appelle une autre (ici il s’agit d’elle-même) reste « figée » dans le même état, jusqu’à ce que la fonction
appelée lui renvoie une valeur.
Lorsque Python exécute le code de la fonction, il connaît le contenu de la variable x. Par contre, de retour dans le
module principal (dans ce cas, il s’agit de l’interpréteur Python), il ne la connaît plus, d’où le message d’erreur.
De même, une variable passée en argument est considérée comme locale lorsqu’on arrive dans la fonction :
1 >>> def ma_fonction(x):
2 ... print(f"x vaut {x} dans la fonction")
3 ...
4 >>> ma_fonction(2)
5 x vaut 2 dans la fonction
6 >>> print(x)
7 Traceback (most recent call last):
8 File "<stdin>", line 1, in <module>
9 NameError: name 'x' is not defined
Lorsqu’une variable est déclarée dans le programme principal, elle est visible dans celui-ci ainsi que dans toutes les
fonctions. On a vu qu’on parlait de variable globale :
1 >>> def ma_fonction():
2 ... print(x)
3 ...
4 >>> x = 3
5 >>> ma_fonction()
6 3
7 >>> print(x)
8 3
Dans ce cas, la variable x est visible dans le module principal et dans toutes les fonctions du module. Toutefois,
Python ne permet pas la modification d’une variable globale dans une fonction :
1 >>> def ma_fonction():
2 ... x = x + 1
3 ...
4 >>> x = 1
5 >>> ma_fonction()
6 Traceback (most recent call last):
7 File "<stdin>", line 1, in <module>
8 File "<stdin>", line 2, in ma_fonction
9 UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
L’erreur renvoyée montre que Python pense que x est une variable locale qui n’a pas été encore assignée. Si on veut
vraiment modifier une variable globale dans une fonction, il faut utiliser le mot-clé global :
1 >>> def ma_fonction():
2 ... global x
3 ... x = x + 1
4 ...
5 >>> x = 1
6 >>> ma_fonction()
7 >>> x
8 2
Dans ce dernier cas, le mot-clé global a forcé la variable x à être globale plutôt que locale au sein de la fonction.
Attention
Les exemples de cette partie représentent des absurdités en termes de programmation. Ils sont donnés à titre indicatif
pour comprendre ce qui se passe, mais il ne faut surtout pas s’en inspirer !
Soyez extrêmement attentifs avec les types modifiables (tels que les listes) car vous pouvez les changer au sein d’une
fonction :
1 >>> def ma_fonction():
2 ... liste1[1] = -127
3 ...
4 >>> liste1 = [1,2,3]
5 >>> ma_fonction()
6 >>> liste1
7 [1, -127, 3]
De même, si vous passez une liste en argument, elle est modifiable au sein de la fonction :
1 >>> def ma_fonction(liste_tmp):
2 ... liste_tmp[1] = -15
3 ...
4 >>> liste1 = [1,2,3]
5 >>> ma_fonction(liste1)
6 >>> liste1
7 [1, -15, 3]
Pour bien comprendre l’origine de ce comportement, utilisons à nouveau le site Python Tutor 3 . La figure 13.3 vous
montre le mécanisme à l’oeuvre lorsqu’on passe une liste à une fonction.
L’instruction pass dans la fonction est une instruction Python qui ne fait rien. Elle est là car une fonction ne peut
être vide et doit contenir au moins une instruction Python valide.
On voit très clairement que la variable liste1 passée en argument lors de l’appel de la fonction d’une part, et la
variable locale liste_tmp au sein de la fonction d’autre part, pointent vers le même objet dans la mémoire. Ainsi,
si on modifie liste_tmp, on modifie aussi liste1. C’est exactement le même mécanisme que pour la copie de listes
(cf. rubrique 11.4 Copie de listes du chapitre 12 Plus sur les listes).
Si vous voulez éviter les problèmes de modification malencontreuse d’une liste dans une fonction, utilisez des tuples (ils
ont présentés dans le chapitre 8 Dictionnaires et tuples), Python renverra une erreur car ces derniers sont non modifiables.
Une autre solution pour éviter la modification d’une liste, lorsqu’elle est passée comme argument à une fonction, est
de la passer explicitement (comme nous l’avons fait pour la copie de liste) afin qu’elle reste intacte dans le programme
principal :
3. http://www.pythontutor.com/
Dans ces deux derniers exemples, une copie de y est créée à la volée lorsqu’on appelle la fonction, ainsi la liste y du
module principal reste intacte.
D’autres suggestions sur l’envoi de liste dans une fonction vous sont données dans la rubrique Recommandations
ci-dessous.
Dans la fonction, x a pris la valeur qui lui était définie localement en priorité sur la valeur définie dans le module
principal.
Conseil
Même si Python accepte qu’une variable ait le même nom que ses propres fonctions ou variables internes, évitez
De manière générale, la règle LGI découle de la manière dont Python gère ce que l’on appelle « les espaces de noms ».
C’est cette gestion qui définit la portée (visibilité) de chaque variable. Nous en parlerons plus longuement dans le chapitre
24 Avoir plus la classe avec les objets (en ligne).
13.6 Recommandations
13.6.1 Évitez les variables globales
Dans ce chapitre nous avons joué avec les fonctions (et les listes) afin de vous montrer comment Python réagissait.
Toutefois, notez bien que l’utilisation de variables globales est à bannir définitivement de votre pratique de la
programmation.
Parfois on veut faire vite et on crée une variable globale visible partout dans le programme (donc dans toutes les
fonctions), car « Ça va plus vite, c’est plus simple ». C’est un très mauvais calcul, ne serait-ce que parce que vos fonctions
ne seront pas réutilisables dans un autre contexte si elles utilisent des variables globales ! Ensuite, arriverez-vous à vous
relire dans six mois ? Quelqu’un d’autre pourrait-il comprendre votre programme ? Il existe de nombreuses autres raisons 4
que nous ne développerons pas ici, mais libre à vous de consulter de la documentation externe.
Heureusement, Python est orienté objet et permet « d’encapsuler » des variables dans des objets et de s’affranchir
définitivement des variables globales (nous verrons cela dans le chapitre 23 Avoir la classe avec les objets). En attendant,
et si vous ne souhaitez pas aller plus loin sur les notions d’objet (on peut tout à fait « pythonner » sans cela), retenez la
chose suivante sur les fonctions et les variables globales :
Conseil
Plutôt que d’utiliser des variables globales, passez vos variables explicitement aux fonctions comme des argument(s).
La ligne 8 indique que la liste liste_notes passée à la fonction est écrasée par la liste renvoyée par la fonction.
Le code suivant produirait la même sortie :
1 def ajoute_un(liste):
2 for indice in range(len(liste)):
3 liste[indice] += 1
4
5 # Programme principal.
6 liste_notes = [10, 8, 16, 7, 15]
7 ajoute_un(liste_notes)
8 print(liste_notes)
Cela reste toutefois moins intuitif, car il n’est pas évident de comprendre que la liste est modifiée dans la fonction
en lisant la ligne 7. Dans un tel cas, il serait essentiel d’indiquer dans la documentation de la fonction que la liste est
4. http://wiki.c2.com/?GlobalVariablesAreBad
modifiée « sur place » (in place en anglais) dans la fonction. Vous verrez dans le chapitre 15 Création de modules
comment documenter vos fonctions.
Conseil
Pour les raisons évoquées ci-dessus, nous vous conseillons de privilégier la première version :
1 liste_notes = ajoute_un(liste_notes)
13.6.3 Conclusion
Vous connaissez maintenant les fonctions sous tous leurs angles. Comme indiqué en introduction du chapitre 10, elles
sont incontournables et tout programmeur se doit de les maîtriser. Voici les derniers conseils que nous pouvons vous
donner :
• Lorsque vous débutez un nouveau projet de programmation, posez-vous la question : « Comment pourrais-je
décomposer en blocs chaque tâche à effectuer, chaque bloc pouvant être une fonction ? ». Et n’oubliez pas que si
une fonction s’avère trop complexe, vous pouvez la décomposer en d’autres fonctions.
• Au risque de nous répéter, forcez-vous à utiliser des fonctions en permanence. Pratiquez, pratiquez… et pratiquez
encore !
13.7 Exercices
Conseil
Pour le second exercice, créez un script puis exécutez-le dans un shell.
13.7.1.1 Code 1
1 def hello(prenom):
2 print(f"Bonjour {prenom}")
3
4
5 # Programme principal.
6 hello("Patrick")
7 print(x)
13.7.1.2 Code 2
1 def hello(prenom):
2 print(f"Bonjour {prenom}")
3
4
5 # Programme principal.
6 x = 10
7 hello("Patrick")
8 print(x)
13.7.1.3 Code 3
1 def hello(prenom):
2 print(f"Bonjour {prenom}")
3 print(x)
4
5
6 # Programme principal.
7 x = 10
8 hello("Patrick")
9 print(x)
13.7.1.4 Code 4
1 def hello(prenom):
2 x = 42
3 print(f"Bonjour {prenom}")
4 print(x)
5
6
7 # Programme principal.
8 x = 10
9 hello("Patrick")
10 print(x)
Conteneurs
Dans ce chapitre, nous allons aborder la notion de conteneur, revenir sur certaines propriétés avancées des dictionnaires
et tuples, et enfin aborder les types set et frozenset. Pour les débutants, ce chapitre aborde des notions relativement
avancées. Avant de vous lancer, nous vous conseillons vivement de bien maitriser les chapitres 4 Listes et 12 Plus sur les
listes, ainsi que le chapitre 8 Dictionnaires et tuples, d’avoir effectué un maximum d’exercices, et de vous sentir à l’aise
avec toutes les notions abordées jusque là.
14.1 Généralités
14.1.1 Définition et propriétés
Définition
Un conteneur (container en anglais) est un nom générique pour définir un objet Python qui contient une collection
d’autres objets.
Les conteneurs que nous connaissons depuis le début de ce cours sont les listes, les chaînes de caractères, les diction-
naires et les tuples. Même si on ne l’a pas vu explicitement, les objets de type range sont également des conteneurs.
Dans la suite de cette rubrique, nous allons examiner les différentes propriétés des conteneurs. À la fin de ce chapitre,
nous ferons un tableau récapitulatif de ces propriétés.
Examinons d’abord les propriétés qui caractérisent tous les types de conteneur.
• Capacité à supporter le test d’appartenance. Souvenez-vous, il permet de vérifier si un élément était présent dans
une liste. Cela fonctionne donc aussi sur les chaînes de caractères ou tout autre conteneur :
1 >>> liste1 = [4, 5, 6]
2 >>> 4 in liste1
3 True
4 >>> "to" in "toto"
5 True
138
14.1. Généralités Chapitre 14. Conteneurs
• Indexable (subscriptable en anglais) : on peut retrouver un élément par son indice (c’est-à-dire sa position dans le
conteneur) ou plusieurs éléments avec une tranche ; en général, tout conteneur indexable est ordonné.
• Itérable (iterable en anglais) : on peut faire une boucle dessus.
Certains conteneurs sont appelés objets séquentiels ou séquence.
Définition
Un objet séquentiel ou séquence est un conteneur itérable, ordonné et indexable. Les objets séquentiels sont les
listes, les chaînes de caractères, les objets de type range, ainsi que les tuples.
Une autre propriété importante que l’on a déjà croisée, et qui nous servira dans ce chapitre, concerne la possibilité
ou non de modifier un objet.
• Un objet est dit non modifiable lorsqu’on ne peut pas le modifier, ou lorsqu’on ne peut pas en modifier un de
ses éléments si c’est un conteneur. On parle aussi d’objet immuable 1 (immutable object en anglais). Cela signifie
qu’une fois créé, Python ne permet plus de le modifier par la suite.
Qu’en est-il des objets que nous connaissons ? Les listes sont modifiables, on peut modifier un ou plusieurs de ses
éléments et ajouter ou retirer un élément. Les dictionnaires sont modifiables : pour une clé donnée, on peut changer la
valeur correspondante et ajouter ou retirer un couple clé/valeur. Tous les autres types que nous avons vus précédemment
sont quant à eux non modifiables : les chaînes de caractères ou strings, les tuples, les objets de type range, mais également
des objets qui ne sont pas des conteneurs comme les entiers, les floats et les booléens.
On comprend bien l’immutabilité des strings comme vu au chapitre 11 Plus sur les chaînes de caractères, mais c’est
moins évident pour les entiers, floats ou booléens. Nous allons démontrer cela, mais avant nous avons besoin de définir
la notion d’identifiant d’un objet.
Définition
L’identifiant d’un objet est un nombre entier qui est garanti constant pendant toute la durée de vie de l’objet. Cet
identifiant est en général unique pour chaque objet. Toutefois, pour des raisons d’optimisation, Python crée parfois le
même identifiant pour deux objets non modifiables différents qui ont la même valeur. L’identifiant peut être assimilé
à l’adresse mémoire de l’objet qui, elle aussi, est unique. En Python, on utilise la fonction interne id() qui prend en
argument un objet et renvoie son identifiant.
Maintenant que l’identifiant est défini, regardons l’exemple suivant qui montre l’immutabilité des entiers :
1 >>> var = 4
2 >>> id(var)
3 140318876873440
4 >>> var = 5
5 >>> id(var)
6 140318876873472
Ligne 1 on définit l’entier var puis on regarde son identifiant. Ligne 4, on pourrait penser que l’on modifie var.
Toutefois, on voit que son identifiant ligne 6 est différent de la ligne 3. En fait, l’affectation ligne 4 var = 5 écrase
l’ancienne variable var et en crée une nouvelle, ce n’est pas la valeur de var qui a été changée puisque l’identifiant n’est
plus le même. Le même raisonnement peut être tenu pour les autres types numériques comme les floats et booléens. Si
on regarde maintenant ce qu’il se passe pour une liste :
1 >>> liste1 = [1, 2, 3]
2 >>> id(liste1)
3 140318850324832
4 >>> liste1[1] = -15
5 >>> id(liste1)
6 140318850324832
7 >>> liste1.append(5)
8 >>> id(liste1)
9 140318850324832
1. https://fr.wikipedia.org/wiki/Objet_immuable
La liste liste1 a été modifiée ligne 4 (changement de l’élément d’indice 1) et ligne 7 (ajout d’un élément). Pour
autant, l’identifiant de cette liste est resté identique tout du long. Ceci démontre la mutabilité des listes : quelle que soit
la manière dont on modifie une liste, celle-ci garde le même identifiant.
• Une dernière propriété importante est la capacité d’un conteneur (ou tout autre objet Python) à être hachable.
Définition
Un objet Python est dit hachable (hashable en anglais) s’il est possible de calculer une valeur de hachage sur celui-ci
avec la fonction interne hash(). En programmation, la valeur de hachage peut être vue comme une empreinte numérique
de l’objet. Elle est obtenue en passant l’objet dans une fonction de hachage et dépend du contenu de l’objet. En Python,
cette empreinte est, comme dans la plupart des langages de programmation, un entier. Au sein d’une même session
Python, deux objets hachables qui ont un contenu identique auront strictement la même valeur de hachage.
Attention
La valeur de hachage d’un objet renvoyée par la fonction hash() n’a pas le même sens que son identifiant renvoyé
par la fonction id(). La valeur de hachage est obtenue en « moulinant » le contenu de l’objet dans une fonction de
hachage. L’identifiant est quant à lui attribué par Python à la création de l’objet. Il est constant tout le long de la durée
de vie de l’objet, un peu comme une carte d’identité. Tout objet a un identifiant, mais il doit être hachable pour avoir
une valeur de hachage.
Pourquoi évoquer cette propriété de hachabilité ? D’abord, parce qu’elle est étroitement liée à l’immutabilité. En effet,
un objet non modifiable est la plupart du temps hachable. Cela permet de l’identifier en fonction de son contenu. Par
ailleurs, l’hachabilité est une implémentation qui permet un accès rapide aux éléments des conteneurs de type dictionnaire
ou set (cf. rubriques suivantes).
Les objets hachables sont les chaînes de caractères, les entiers, les floats, les booléens, les objets de type range, les
tuples (sous certaines conditions) et les frozensets ; par contre, les listes, les sets et les dictionnaires sont non hachables.
Les sets et frozensets seront vus plus bas dans ce chapitre.
Voici un exemple :
1 >>> hash("Plouf")
2 5085648805260210718
3 >>> hash(5)
4 5
5 >>> hash(3.14)
6 322818021289917443
7 >>> hash([1, 2, 3])
8 Traceback (most recent call last):
9 File "<stdin>", line 1, in <module>
10 TypeError: unhashable type: 'list'
Les valeurs de hachage renvoyées par la fonction hash() de Python sont systématiquement des entiers. Par contre,
Python renvoie une erreur pour une liste, car elle est non hachable.
2. https://fr.wikipedia.org/wiki/Fonction_de_hachage
La tentative de modification d’un élément ligne 12 conduit à la même erreur que lorsqu’on essaie de modifier un
caractère d’une chaîne de caractères. Comme pour la plupart des objets Python non modifiables, les objets de type range
sont hachables.
3. https://fr.wikipedia.org/wiki/Table_de_hachage
Vous voyez l’énorme avantage, d’utiliser comme clé le numéro de résidu. Avec une liste ou une chaîne de caractère,
l’indiçage commence à zéro. Ainsi, il faudrait utiliser les indices 2 et 6 pour retrouver respectivement les acides aminés 5
et 9 :
1 >>> sequence = ['S', 'E', 'Q', 'P', 'E', 'P', 'T']
2 >>> sequence[2]
3 'Q'
4 >>> sequence[6]
5 'T'
Pour les listes, on utilise l’indice entre crochet pour détruire l’élément, par exemple del liste[2]. Ici, on utilise la
clé.
L’argument key=dico.get indique explicitement qu’il faut réaliser le tri par les valeurs du dictionnaire. On retrouve
la méthode .get() vue au chapitre 8 Dictionnaires et tuples, mais sans les parenthèses : key=dico.get, mais pas key
=dico.get(). Une fonction ou méthode passée en argument sans les parenthèses est appelée callback, nous reverrons
cela en détail dans le chapitre 25 Fenêtres graphiques et Tkinter (en ligne).
Attention, ce sont les clés du dictionnaire qui sont renvoyées, pas les valeurs. Ces clés sont cependant renvoyées dans
un ordre qui permet d’obtenir les clés triées par ordre croissant :
1 >>> dico = {"a": 15, "b": 5, "c":20}
2 >>> for key in sorted(dico, key=dico.get):
3 ... print(key, dico[key])
4 ...
5 b 5
6 a 15
7 c 20
Remarque
Lorsqu’on trie un dictionnaire par ses valeurs, il faut être sûr que cela soit possible. Ce n’est pas le cas lorsqu’on a un
mélange de valeurs numériques et chaînes de caractères :
On obtient ici une erreur, car Python ne sait pas comparer une chaîne de caractères (singe) avec des valeurs
numériques (70 et 1.75).
Si un des sous-éléments a plus de deux éléments (ou moins), Python renvoie une erreur :
1 >>> dict([("girafe", 2), ("singe", 3, 4)])
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 ValueError: dictionary update sequence element #1 has length 3; 2 is required
Attention
Une manière intuitive utilise simplement des arguments par mot-clés, qui deviendront des clés sous forme de chaîne
de caractères :
1 >>> dict(un=1, deux=2, trois=3)
2 {'un': 1, 'deux': 2, 'trois': 3}
Nous vous déconseillons toutefois cette manière de faire, car on ne peut pas mettre d’arguments par mot-clé variables,
on doit les écrire explicitement.
Une dernière manière puissante pour générer des dictionnaires combine les fonctions dict() et zip(). On se souvient
que la fonction zip() peut générer une liste de tuples :
Attention à ne passer que deux listes à la fonction zip(), sinon Python renvoie une erreur :
1 >>> dict(zip([1, 2, 3], animaux, couleurs))
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 ValueError: dictionary update sequence element #0 has length 3; 2 is required
14.3.1 Immutabilité
Nous avions vu que les tuples étaient immuables :
1 >>> tuple1 = (1, 2, 3)
2 >>> tuple1[2] = 15
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 TypeError: 'tuple' object does not support item assignment
Ce message est similaire à celui que nous avions rencontré quand on essayait de modifier une chaîne de caractères
(voir chapitre 11 Plus sur les chaînes de caractères). De manière générale, Python renverra un message TypeError: '
[...]' does not support item assignment lorsqu’on essaie de modifier un élément d’un objet non modifiable. Si
vous voulez ajouter un élément (ou le modifier), vous devez créer un nouveau tuple :
1 >>> tuple1 = (1, 2, 3)
2 >>> tuple1
3 (1, 2, 3)
4 >>> id(tuple1)
5 139971081704464
6 >>> tuple1 = tuple1 + (2,)
7 >>> tuple1
8 (1, 2, 3, 2)
9 >>> id(tuple1)
10 139971081700368
La fonction id() montre que le tuple créé ligne 6 est bien différent de celui créé ligne 4, bien qu’ils aient le même
nom. Comme on a vu plus haut, ceci est dû à l’opérateur d’affectation utilisé ligne 6 (tuple1 = tuple1 + (2,)) qui
crée un nouvel objet distinct de celui de la ligne 1. Cet exemple montre que les tuples sont peu adaptés lorsqu’on a besoin
d’ajouter, retirer, modifier des éléments. La création d’un nouveau tuple à chaque étape s’avère lourde et il n’y a aucune
méthode pour faire cela, puisque les tuples sont non modifiables.
Conseil
Pour ce genre de tâche, les listes sont clairement mieux adaptées que les tuples.
La syntaxe x, y = ma_fonction() permet de récupérer les deux valeurs renvoyées par la fonction et de les affecter
à la volée dans deux variables différentes. Cela évite l’opération laborieuse de récupérer d’abord le tuple, puis de créer les
variables en utilisant l’indiçage :
1 >>> resultat = ma_fonction()
2 >>> resultat
3 (3, 14)
4 >>> x = resultat[0]
5 >>> y = resultat[1]
6 >>> print(x, y)
7 3 14
Conseil
Lorsqu’une fonction renvoie plusieurs valeurs sous forme de tuple, privilégiez toujours la forme x, y = ma_fonction
().
Cela envoie le message à la personne qui lit le code « je ne m’intéresse pas aux valeurs récupérées dans les variables
_ ». Notez que l’on peut utiliser une ou plusieurs variables underscore(s). Dans l’exemple ci-dessus, la 2e et la 4e variable
renvoyées par la fonction seront ignorées dans la suite du code. Cela présente le mérite d’éviter de polluer l’attention de
la personne qui lit le code.
Remarque
Dans l’interpréteur interactif, la variable _ a une signification différente. Elle prend automatiquement la dernière valeur
affichée :
1 >>> 3
2 3
3 >>> _
4 3
5 >>> "mésange"
6 'mésange'
7 >>> _
8 'mésange'
Remarque
Le caractère underscore (_) est couramment utilisé dans les noms de variable pour séparer les mots et être explicite,
par exemple seq_ADN ou liste_listes_residus. On verra dans le chapitre 16 Bonnes pratiques en programmation
Python que ce style de nommage est appelé snake_case. Toutefois, il faut éviter d’utiliser les underscores en début et/ou
en fin de nom de variable (leading et trailing underscores en anglais), par exemple : _var, var_, __var, __var__. On
verra au chapitre 23 Avoir la classe avec les objets que ces underscores ont aussi une signification particulière.
Si on modifie un élément de la liste liste1 (ligne 5) ou bien qu’on ajoute un élément à tuple1[0] (ligne 6), Python
s’exécute et ne renvoie pas de message d’erreur. Or nous avions dit qu’un tuple était non modifiable… Comment cela
est-il possible ? Commençons d’abord par regarder comment les objets sont agencés avec Python Tutor.
La liste liste1 pointe vers le même objet que l’élément du tuple d’indice 0. Comme pour la copie de liste (par
exemple liste_b = liste_a), ceci est attendu car, par défaut, Python crée une copie par référence (voir le chapitre
12 Plus sur les listes). Ainsi, qu’on raisonne en tant que premier élément du tuple ou bien en tant que liste liste1, on
pointe vers la même liste. Or, rappelez-vous, nous avons expliqué au début de ce chapitre que lorsqu’on modifiait un
élément d’une liste, celle-ci gardait le même identifiant. C’est toujours le cas ici, même si celle-ci se trouve dans un tuple.
Regardons cela :
1 >>> liste1 = [1, 2, 3]
2 >>> tuple1 = (liste1, "Plouf")
3 >>> tuple1
4 ([1, 2, 3], 'Plouf')
5 >>> id(liste1)
6 139971081980816
7 >>> id(tuple1[0])
8 139971081980816
Nous confirmons ici le schéma de Python Tutor, c’est bien la même liste que l’on considère liste1 ou tuple1[0]
puisqu’on a le même identifiant. Maintenant, on modifie cette liste via la variable liste1 ou tuple1[0] :
1 >>> liste1[2] = -15
2 >>> tuple1[0].append(-632)
3 >>> tuple1
4 ([1, 2, -15, -632], 'Plouf')
5 >>> id(liste1)
6 139971081980816
7 >>> id(tuple1[0])
8 139971081980816
Malgré la modification de cette liste, l’identifiant n’a toujours pas changé puisque la fonction id() nous renvoie la
même valeur depuis le début. Même si la liste a été modifiée « de l’intérieur », Python considère que c’est toujours la
même liste, puisqu’elle n’a pas changé d’identifiant. Si au contraire on essaie de remplacer cette sous-liste par autre
chose, Python renvoie une erreur :
1 >>> tuple1[0] = "Plif"
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 TypeError: 'tuple' object does not support item assignment
Cette erreur s’explique par le fait que le nouvel objet "Plif" n’a pas le même identifiant que la sous-liste initiale. En
fait, l’immutabilité selon Python signifie qu’un objet créé doit toujours garder le même identifiant. Cela est valable pour
tout objet non modifiable, comme un élément d’un tuple, un caractère dans une chaîne de caractères, etc.
Conseil
Cette digression avait pour objectif de vous faire comprendre ce qu’il se passe lorsqu’on met une liste dans un tuple.
Toutefois, pouvoir modifier une liste en tant qu’élément d’un tuple va à l’encontre de l’intérêt d’un objet non modifiable.
Dans la mesure du possible, nous vous déconseillons de créer des listes dans des tuples afin d’éviter les déconvenues.
Les tuples tuple1 et tuple2 sont hachables car ils ne contiennent que des éléments hachables. Par contre, tuple3
ne l’est pas, car un de ses éléments est une liste.
Conseil
Mettre une ou plusieurs liste(s) dans un tuple le rend non hachable. Ceci le rend inutilisable comme clé de dictionnaire
ou, on le verra ci-après, comme élément d’un set ou d’un frozenset. Donc, à nouveau, ne mettez pas de listes dans vos
tuples !
Remarquez que la répétition du chiffre 5 dans la définition du set ligne 1 produit finalement un seul chiffre 5, car
chaque élément ne peut être présent qu’une seule fois. Comme pour les dictionnaires (jusqu’à la version 3.6), les sets
sont non ordonnés. La manière dont Python les affiche n’a pas de sens en tant que tel et peut être différente de celle
utilisée lors de leur création.
Les sets ne peuvent contenir que des objets hachables. On a déjà eu le cas avec les clés de dictionnaire. Ceci optimise
l’accès à chaque élément du set. Pour rappel, les objets hachables que nous connaissons sont les chaînes de caractères,
les tuples, les entiers, les floats, les booléens et les frozensets (voir plus bas). Les objets non hachables que l’on connait
sont les listes, les sets et les dictionnaires. Si on essaie tout de même de mettre une liste dans un set, Python renvoie
une erreur :
1 >>> set1 = {3, 4, "Plouf", (1, 3)}
2 >>> set1
3 {(1, 3), 3, 4, 'Plouf'}
4 >>> set2 = {3.14, [1, 2]}
5 Traceback (most recent call last):
6 File "<stdin>", line 1, in <module>
7 TypeError: unhashable type: 'list'
À quoi différencie-t-on un set d’un dictionnaire alors que les deux utilisent des accolades ? Le set sera défini seulement
par des valeurs {valeur_1, valeur_2, ...} alors que le dictionnaire aura toujours des couples clé/valeur {clé_1:
valeur_1, clé_2: valeur_2, ...}.
La fonction interne à Python set() convertit un objet itérable passé en argument en un nouveau set (opération de
casting) :
1 >>> set([1, 2, 4, 1])
2 {1, 2, 4}
3 >>> set((2, 2, 2, 1))
4 {1, 2}
5 >>> set(range(5))
6 {0, 1, 2, 3, 4}
7 >>> set({"clé_1": 1, "clé_2": 2})
8 {'clé_1', 'clé_2'}
9 >>> set(["ti", "to", "to"])
10 {'ti', 'to'}
11 >>> set("Maître Corbeau et Maître Renard")
12 {'e', 'd', 'M', 'r', 'n', 't', 'a', 'C', 'î', ' ', 'o', 'u', 'R', 'b'}
Nous avons dit plus haut que les sets ne sont ni ordonnés ni indexables, il est donc impossible de récupérer un élément
par sa position. Il est également impossible de modifier un de ses éléments par l’indexation.
1 >>> set1 = set([1, 2, 4, 1])
2 >>> set1[1]
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 TypeError: 'set' object is not subscriptable
6 >>> set1[1] = 5
7 Traceback (most recent call last):
8 File "<stdin>", line 1, in <module>
9 TypeError: 'set' object does not support item assignment
Les sets ne peuvent être modifiés que par des méthodes spécifiques :
1 >>> set1 = set(range(5))
2 >>> set1
3 {0, 1, 2, 3, 4}
4 >>> set1.add(4)
5 >>> set1
6 {0, 1, 2, 3, 4}
7 >>> set1.add(472)
8 >>> set1
9 {0, 1, 2, 3, 4, 472}
10 >>> set1.discard(0)
11 >>> set1
12 {1, 2, 3, 4, 472}
La méthode .add() ajoute au set l’élément passé en argument. Toutefois, si l’élément est déjà présent dans le set,
il n’est pas ajouté puisqu’on a au plus une copie de chaque élément. La méthode .discard() retire du set l’élément
passé en argument. Si l’élément n’est pas présent dans le set, il ne se passe rien, le set reste intact. Comme les sets ne
sont pas ordonnés ni indexables, il n’y a pas de méthode pour insérer un élément à une position précise, contrairement
aux listes. Dernier point sur ces méthodes, elles modifient le set sur place (in place, en anglais) et ne renvoient rien, à
l’instar des méthodes des listes (.append(), .remove(), etc.).
Enfin, les sets ne supportent pas les opérateurs + et *.
14.4.2 Utilité
Les conteneurs de type set sont très utiles pour rechercher les éléments uniques d’une suite d’éléments. Cela revient
à éliminer tous les doublons. Par exemple :
On peut bien sûr transformer dans l’autre sens un set en liste. Cela permet par exemple d’éliminer les doublons de la
liste initiale, tout en récupérant une liste à la fin :
On peut faire des choses très puissantes. Par exemple, un compteur de lettres en combinaison avec une liste de
compréhension, le tout en une ligne !
Les sets permettent aussi l’évaluation d’union ou d’intersection mathématiques en conjonction avec les opérateurs,
respectivement | et & :
Notez qu’il existe les méthodes .union() et .intersection permettant de réaliser ces opérations d’union et
d’intersection :
L’instruction set1.difference(set2) renvoie sous la forme d’un nouveau set les éléments de set1 qui ne sont
pas dans set2. Et inversement pour set2.difference(set1) :
1 >>> set1.difference(set2)
2 {4}
3 >>> set2.difference(set1)
4 {0, 2}
La méthode .issubset() indique si un set est inclus dans un autre set. La méthode isdisjoint() indique si un
set est disjoint d’un autre set, c’est-à-dire, s’ils n’ont aucun élément en commun indiquant que leur intersection est nulle.
Il existe de nombreuses autres méthodes que nous n’abordons pas ici, mais qui peuvent être consultées sur la docu-
mentation officielle de Python 4 .
14.4.3 Frozensets
Les frozensets sont des sets non modifiables et hachables. Ainsi, un set peut contenir des frozensets mais pas l’inverse.
À quoi servent-ils ? Comme la différence entre tuple et liste, l’immutabilité des frozensets donne l’assurance de ne pas
pouvoir les modifier par erreur. Pour créer un frozenset on utilise la fonction interne frozenset(), qui prend en argument
un objet itérable et le convertit (opération de casting) :
1 >>> frozen1 = frozenset([3, 3, 5, 1, 3, 4, 1, 1, 4, 4])
2 >>> frozen2 = frozenset([3, 0, 5, 3, 3, 1, 1, 1, 2, 2])
3 >>> frozen1
4 frozenset({1, 3, 4, 5})
5 >>> frozen2
6 frozenset({0, 1, 2, 3, 5})
7 >>> frozen1.add(5)
8 Traceback (most recent call last):
9 File "<stdin>", line 1, in <module>
10 AttributeError: 'frozenset' object has no attribute 'add'
11 >>> frozen1.union(frozen2)
12 frozenset({0, 1, 2, 3, 4, 5})
13 >>> frozen1.intersection(frozen2)
14 frozenset({1, 3, 5})
Les frozensets ne possèdent bien sûr pas les méthodes de modification des sets (.add(), .discard(), etc.) puisqu’ils
sont non modifiables. Par contre, ils possèdent toutes les méthodes de comparaisons de sets (.union(), .intersection
(), etc.).
Conseil
Pour aller plus loin sur les sets et les frozensets, voici deux articles sur les sites programiz 5 et towardsdatascience 6 .
4. https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset
5. https://www.programiz.com/python-programming/set
6. https://towardsdatascience.com/python-sets-and-set-theory-2ace093d1607
La méthode .items() vue dans le chapitre 8 Dictionnaires et tuples est particulièrement bien adaptée pour créer un
dictionnaire de compréhension, car elle permet d’itérer en même temps sur les clés et valeurs d’un dictionnaire.
Avec un dictionnaire de compréhension, on peut rapidement compter le nombre de chaque base dans une séquence
d’ADN :
1 >>> sequence = "atctcgatcgatcgcgctagctagctcgccatacgtacgactacgt"
2 >>> {base:seq.count(base) for base in set(sequence)}
3 {'a': 10, 'g': 10, 't': 11, 'c': 15}
De manière générale, tout objet sur lequel on peut faire une double itération du type for var1, var2 in obj est
utilisable pour créer un dictionnaire de compréhension. Si vous souhaitez aller plus loin, vous pouvez consulter cet article
sur le site Datacamp 7 .
Il est également possible de générer des sets de compréhension sur le même modèle que les listes de compréhension :
1 >>> {i for i in range(10)}
2 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
3 >>> {i**2 for i in range(10)}
4 {0, 1, 64, 4, 36, 9, 16, 49, 81, 25}
5 >>>
6 >>> animaux = (("singe", 3), ("girafe", 4), ("rhinocéros", 2))
7 >>> {ani for ani, _ in animaux}
8 {'girafe', 'singe', 'rhinocéros'}
7. https://www.datacamp.com/community/tutorials/python-dictionary-comprehension
8. https://docs.python.org/fr/3/library/collections.html
9. https://docs.python.org/fr/3/library/collections.html#collections.OrderedDict
3.6 de Python, ces dictionnaires ordonnés avaient un intérêt, car l’ordre des dictionnaires normaux était arbitraire.
Désormais, les dictionnaires normaux se comportent presque en tout point comme les dictionnaires ordonnés.
• Les defaultdicts 10 , qui génèrent des valeurs par défaut quand on demande une clé qui n’existe pas (cela évite que
Python génère une erreur).
• Les compteurs 11 , dont un exemple est présenté ci-dessous.
• Les namedtuples 12 , que nous évoquerons au chapitre 24 Avoir plus la classe avec les objets (en ligne).
L’objet collection.Counter() est particulièrement intéressant et simple à utiliser. Il crée des compteurs à partir
d’objets itérables, par exemple :
1 >>> import collections
2 >>> compo_seq = collections.Counter("aatctccgatcgatcgatcgatgatc")
3 >>> compo_seq
4 Counter({'a': 7, 't': 7, 'c': 7, 'g': 5})
5 >>> type(compo_seq)
6 <class 'collections.Counter'>
7 >>> compo_seq["a"]
8 7
9 >>> compo_seq["n"]
10 0
Dans cet exemple, Python a automatiquement compté chaque caractère a, t, g et c de la chaîne de caractères passée
en argument. Cela crée un objet de type Counter qui se comporte ensuite comme un dictionnaire, à une exception près :
si on appelle une clé qui n’existe pas dans l’itérable initiale (comme le n ci-dessus), la valeur renvoyée est 0.
14.8 Exercices
Conseil
Pour ces exercices, créez des scripts puis exécutez-les dans un shell.
Mots de 2 lettres
AC : 1
CC : 3
CT : 8
[...]
Mots de 3 lettres
ACC : 1
CCT : 2
CTA : 5
[...]
Conseil
13. https://python.sdv.u-paris.fr/data-files/NC_001133.fna
14. https://python.sdv.u-paris.fr/data-files/NC_001133.fna
15. https://python.sdv.u-paris.fr/data-files/NC_000913.fna
16. https://files.rcsb.org/download/1BTA.pdb
17. http://www.rcsb.org/pdb/explore.do?structureId=1BTA
Vous trouverez des explications sur le format PDB et des exemples de code pour lire ce type de fichier en Python
dans l’annexe A Quelques formats de données en biologie.
1 n
Gx = ∑ CAi,x
n i=1
1 n
Gy = ∑ CAi,y
n i=1
1 n
Gz = ∑ CAi,z
n i=1
Créez une fonction calcule_barycentre(), qui prend comme argument une liste de dictionnaires dont les clés
(resid, x, y et z) sont celles de l’exercice précédent et qui renvoie les coordonnées du barycentre sous la forme d’une
liste de floats.
Utilisez la fonction trouve_calpha() de l’exercice précédent et la fonction
calcule_barycentre() pour afficher, avec deux chiffres significatifs, les coordonnées du barycentre des carbones alpha
de la barstar.
L’objectif de cet exercice est de déterminer quelles sont les protéines humaines qui sont des kinases. Chaque liste de
protéines contenant plusieurs milliers d’éléments, il n’est pas possible de la faire à la main. Vous aller utiliser Python et
les sets pour cela.
1. Créez un script compare_proteins.py.
2. Dans ce script, créez une fonction read_protein_file() qui prend en argument le nom d’un fichier de protéines
sous la forme d’une chaîne de caractères et qui renvoie un set contenant la liste des identifiants des protéines
contenues dans le fichier passé en argument.
18. https://files.rcsb.org/download/1BTA.pdb
19. http://www.rcsb.org/pdb/explore.do?structureId=1BTA
20. https://python.sdv.u-paris.fr/data-files/human_proteins.txt
21. https://python.sdv.u-paris.fr/data-files/kinase_proteins.txt
22. https://fr.wikipedia.org/wiki/Kinase
Création de modules
Les chaînes de caractères entre triple guillemets en tête du module et en tête de chaque fonction sont facultatives
mais elles jouent néanmoins un rôle essentiel dans la documentation du code.
158
15.3. Utilisation de son propre module Chapitre 15. Création de modules
Remarque
Une constante est, par définition, une variable dont la valeur n’est pas modifiée. Par convention, en Python, le nom
des constantes est écrit en majuscules (comme DATE dans notre exemple).
Remarque
Avec Mac OS X et Linux, il faut taper la commande suivante depuis un shell Bash pour modifier la variable d’envi-
ronnement PYTHONPATH :
export PYTHONPATH=$PYTHONPATH:/chemin/vers/mon/super/module
Avec Windows, mais depuis un shell PowerShell, il faut taper la commande suivante :
$env:PYTHONPATH += ";C:\chemin\vers\mon\super\module"
Une fois cette manipulation effectuée, vous pouvez contrôler que le chemin vers le répertoire contenant vos modules
a bien été ajouté à la variable d’environnement PYTHONPATH :
• sous Mac OS X et Linux : echo $PYTHONPATH
• sous Windows : echo $env:PYTHONPATH
Le chargement du module se fait avec la commande import message. Notez que le fichier est bien enregistré avec
une extension .py, pourtant on ne la précise pas lorsqu’on importe le module. Ensuite, on peut utiliser les fonctions
comme avec un module classique :
1 >>> import message
2 >>> message.hello("Joe")
3 'Hello Joe'
4 >>> message.ciao("Bill")
5 'Ciao Bill'
6 >>> message.bonjour("Monsieur")
7 'Bonjour Monsieur'
8 >>> message.DATE
9 '2024-01-05'
Remarque
La première fois qu’un module est importé, Python crée un répertoire nommé __pycache__ contenant un fichier
avec une extension .pyc qui contient le bytecode 1 , c’est-à-dire le code précompilé du module.
1. https://docs.python.org/fr/3/glossary.html#term-bytecode
Remarque
Pour quitter l’aide, pressez la touche Q.
Vous remarquez que Python a généré automatiquement cette page d’aide, tout comme il est capable de le faire pour
les modules internes à Python (random, math, etc.) et ce grâce aux docstrings. Notez que l’on peut aussi appeler l’aide
pour une seule fonction :
1 >>> help(message.ciao)
2
3 Help on function ciao in module message:
4
5 ciao(nom)
6 Dit Ciao.
En résumé, les docstrings sont destinés aux utilisateurs du module. Leur but est différent des commentaires qui, eux,
sont destinés à celui qui lit le code (pour en comprendre les subtilités). Une bonne docstring de fonction doit contenir
tout ce dont un utilisateur a besoin pour utiliser cette fonction. Une liste minimale et non exhaustive serait :
• ce que fait la fonction,
• ce qu’elle prend en argument,
• ce qu’elle renvoie.
Pour en savoir plus sur les docstrings et comment les écrire, nous vous recommandons de lire le chapitre 16 Bonnes
pratiques en programmation Python.
Cela s’explique par l’absence de programme principal, c’est-à-dire de lignes de code que l’interpréteur exécute lorsqu’on
lance le script.
À l’inverse, que se passe-t-il si on importe un script en tant que module alors qu’il contient un programme principal
avec des lignes de code ? Prenons par exemple le script message2.py suivant :
1 """Script de test."""
2
3
4 def bonjour(nom):
5 """Dit Bonjour."""
6 return f"Bonjour {nom}"
7
8
9 # Programme principal.
10 print(bonjour("Joe"))
Ceci n’est pas le comportement voulu pour un module, car on n’attend pas d’affichage particulier lors de son charge-
ment. Par exemple la commande import math n’affiche rien dans l’interpréteur.
Afin de pouvoir utiliser un code Python en tant que module ou en tant que script, nous vous conseillons la structure
suivante :
1 """Script de test."""
2
3
4 def bonjour(nom):
5 """Dit Bonjour."""
6 return f"Bonjour {nom}"
7
8
9 if __name__ == "__main__":
10 print(bonjour("Joe"))
• Si le programme message2.py est importé en tant que module, le résultat du test if sera alors False et le bloc
d’instructions correspondant ne sera pas exécuté :
1 >>> import message2
2 >>>
Ce comportement est possible grâce à la gestion des espaces de noms par Python (pour plus de détail, consultez le
chapitre 24 Avoir plus la classe avec les objets (en ligne)). Au delà de la commodité de pouvoir utiliser votre script en
tant que programme ou en tant que module, cela présente l’avantage de signaler clairement où se situe le programme
principal quand on lit le code.
Conseil
15.7 Exercice
Conseil
Pour cet exercice, créez un script puis exécutez-le dans un shell.
Conseil
• Dans cet exercice, on supposera que toutes les séquences sont manipulées comme des chaînes de caractères en
majuscules.
• Pour les fonctions seq_alea() et comp_inv(), n’hésitez pas à jeter un œil aux exercices correspondants dans le
chapitre 12 Plus sur les listes.
• Voici un exemple de fichier FASTA adn.fasta 2 pour tester la fonction lit_fasta().
2. https://python.sdv.u-paris.fr/data-files/adn.fasta
Comme vous l’avez constaté dans tous les chapitres précédents, la syntaxe de Python est très permissive. Afin
d’uniformiser l’écriture de code en Python, la communauté des développeurs Python recommande un certain nombre de
règles afin qu’un code soit lisible. Lisible par quelqu’un d’autre, mais également, et surtout, par soi-même. Essayez de
relire un code que vous avez écrit « rapidement » il y a un mois, six mois ou un an. Si le code ne fait que quelques lignes,
il se peut que vous vous y retrouviez, mais s’il fait plusieurs dizaines, voire centaines de lignes, vous serez perdus.
Dans ce contexte, le créateur de Python, Guido van Rossum, part d’un constat simple : « code is read much more
often than it is written » (« le code est plus souvent lu qu’écrit »). Avec l’expérience, vous vous rendrez compte que cela
est parfaitement vrai. Alors, plus de temps à perdre, voyons en quoi consistent ces bonnes pratiques.
Plusieurs choses sont nécessaires pour écrire un code lisible : la syntaxe, l’organisation du code, le découpage en
fonctions (et possiblement en classes, que nous verrons dans le chapitre 23 Avoir la classe avec les objets), mais souvent,
aussi, le bon sens. Pour cela, les « PEP » peuvent nous aider.
Définition
Afin d’améliorer le langage Python, la communauté qui développe Python publie régulièrement des Python Enhance-
ment Proposal 1 (PEP), suivi d’un numéro. Il s’agit de propositions concrètes pour améliorer le code, ajouter de nouvelles
fonctionnalités, mais aussi des recommandations sur la manière d’utiliser Python, bien écrire du code, etc.
On va aborder dans ce chapitre sans doute la plus célèbre des PEP, à savoir la PEP 8, qui est incontournable lorsque
l’on veut écrire du code Python correctement.
Définition
On parle de code pythonique lorsque ce dernier respecte les règles d’écriture définies par la communauté Python,
mais aussi les règles d’usage du langage.
1. https://www.python.org/dev/peps/
163
Chapitre 16. Bonnes pratiques en programmation Python 16.1. De la bonne syntaxe avec la PEP 8
16.1.1 Indentation
On a vu que l’indentation est obligatoire en Python pour séparer les blocs d’instructions. Cela vient d’un constat
simple : l’indentation améliore la lisibilité d’un code. La PEP 8 recommande d’utiliser quatre espaces pour chaque niveau
d’indentation. Nous vous recommandons de suivre impérativement cette règle.
Attention
Afin de toujours utiliser cette règle des quatre espaces pour l’indentation, il est essentiel de régler correctement votre
éditeur de texte. Consultez pour cela l’annexe Installation de Python disponible en ligne 3 . Avant d’écrire la moindre ligne
de code, faites en sorte que lorsque vous pressez la touche tabulation, cela ajoute quatre espaces (et non pas un caractère
tabulation).
2. https://www.python.org/dev/peps/pep-0008/
3. https://python.sdv.u-paris.fr/livre-dunod
c’est-à-dire en minuscules avec un caractère « souligné » (« tiret du bas », ou underscore en anglais) pour séparer les
différents « mots » dans le nom.
Les constantes sont écrites en majuscules :
1 MA_CONSTANTE
2 VITESSE_LUMIERE
Les noms de classes (voir le chapitre 23 Avoir la classe avec les objets) et les exceptions (voir le chapitre 26 Remarques
complémentaires (en ligne)) sont de la forme :
1 MaClasse
2 MyException
Remarque
• Le style recommandé pour nommer les variables et les fonctions en Python est appelé snake_case. Il est différent
du CamelCase utilisé pour les noms des classes et des exceptions.
• La variable _ est habituellement employée pour stocker des valeurs qui ne seront pas utilisées par la suite. Par
exemple, dans le cas d’une affectation multiple, on peut utiliser _ pour stocker une valeur qui ne nous intéresse pas
(voir chapitre 14 Conteneurs).
Pensez à donner à vos variables des noms qui ont du sens. Évitez autant que possible les a1, a2, i, truc, toto…
Les noms de variables à un caractère sont néanmoins autorisés pour les indices dans les boucles :
1 >>> ma_liste = [1, 3, 5, 7, 9, 11]
2 >>> for i in range(len(ma_liste)):
3 ... print(ma_liste[i])
Bien sûr, une écriture plus « pythonique » de l’exemple précédent permet de se débarrasser de l’indice i :
1 >>> ma_liste = [1, 3, 5, 7, 9, 11]
2 >>> for entier in ma_liste:
3 ... print(entier)
Enfin, des noms de variable à une lettre peuvent être utilisés lorsque cela a un sens mathématique (par exemple, les
noms x, y et z évoquent des coordonnées cartésiennes).
Ni juste avant la parenthèse ouvrante d’une fonction ou le crochet ouvrant d’une liste ou d’un dictionnaire :
1 # Code recommandé :
2 ma_liste[1]
3 mon_dico{"clé"}
4 ma_fonction(argument)
5 # Code non recommandé :
6 ma_liste [1]
7 mon_dico {"clé"}
8 ma_fonction (argument)
Par contre, pour les tranches de listes, on ne met pas d’espace autour du :
1 ma_liste = [1, 3, 5, 7, 9, 1]
2 # Code recommandé :
3 ma_liste[1:3]
4 ma_liste[1:4:2]
5 ma_liste[::2]
6 # Code non recommandé :
7 ma_liste[1 : 3]
8 ma_liste[1: 4:2 ]
9 ma_liste[ : :2]
Enfin, on n’ajoute pas plusieurs espaces autour du = ou des autres opérateurs pour faire joli :
1 # Code recommandé :
2 x1 = 1
3 x2 = 3
4 x_old = 5
5 # Code non recommandé :
6 x1 = 1
7 x2 = 3
8 x_old = 5
À l’intérieur de parenthèses, on peut revenir à la ligne sans utiliser le caractère \. C’est particulièrement utile pour
préciser les arguments d’une fonction ou d’une méthode, lors de sa création ou lors de son utilisation :
1 >>> def ma_fonction(argument_1, argument_2,
2 ... argument_3, argument_4):
3 ... return argument_1 + argument_2
4 ...
5 >>> ma_fonction("texte très long", "tigre",
6 ... "singe", "souris")
7 'texte très longtigre'
Les parenthèses sont également très pratiques, pour répartir sur plusieurs lignes une chaîne de caractères qui sera
Notez qu’il n’y a pas d’opérateur + pour concaténer les trois chaînes de caractères, et que celles-ci ne sont pas séparées
par des virgules. À partir du moment où elles sont entre parenthèses, Python les concatène automatiquement.
On peut aussi utiliser les parenthèses pour évaluer un expression trop longue :
1 >>> ma_variable = 3
2 >>> if (ma_variable > 1 and ma_variable < 10
3 ... and ma_variable % 2 == 1 and ma_variable % 3 == 0):
4 ... print(f"ma variable vaut {ma_variable}")
5 ...
6 ma variable vaut 3
Remarque
Les parenthèses sont aussi très utiles lorsqu’on a besoin d’enchaîner des méthodes les unes à la suite des autres. Cette
technique du method chaining a été introduite dans le chapitre 11 Plus sur les chaînes de caractères et sera très utilisée
dans le chapitre 22 Module Pandas.
Enfin, il est possible de créer des listes ou des dictionnaires sur plusieurs lignes, en sautant une ligne après une virgule :
1 >>> ma_liste = [1, 2, 3,
2 ... 4, 5, 6,
3 ... 7, 8, 9]
4 >>> mon_dico = {"clé1": 13,
5 ... "clé2": 42,
6 ... "clé3": -10}
16.1.7 Commentaires
Les commentaires débutent toujours par le symbole # suivi d’un espace. Ils fournissent des explications sur l’utilité
du code et permettent de comprendre son fonctionnement.
Les commentaires sont sur le même niveau d’indentation que le code qu’ils commentent. Les commentaires sont
constitués de phrases complètes, avec une majuscule au début (sauf si le premier mot est une variable qui s’écrit sans
majuscule) et un point à la fin.
La PEP 8 recommande d’écrire les commentaires en anglais, sauf si vous êtes absolument certains que votre code ne
sera lu que par des francophones. Dans la mesure où vous allez souvent développer des programmes scientifiques, nous
vous conseillons d’écrire vos commentaires en anglais.
Soyez également cohérent entre la langue utilisée pour les commentaires et la langue utilisée pour nommer les variables.
Pour un programme scientifique, les commentaires et les noms de variables sont en anglais. Ainsi ma_liste deviendra
my_list et ma_fonction deviendra my_function (par exemple).
Les commentaires qui suivent le code sur la même ligne sont à éviter le plus possible et doivent être séparés du code
par au moins deux espaces :
1 var_x = number / total * 100 # My useful comment.
Remarque
La PEP 8 ne fournit pas de recommandation 4 quant à l’usage de guillemets simples ou de guillemets doubles pour
déclarer une chaîne de caractères.
1 >>> var_1 = "Ma chaîne de caractères"
2 >>> var_1
3 'Ma chaîne de caractères'
4 >>> var_2 = 'Ma chaîne de caractères'
5 >>> var_2
6 'Ma chaîne de caractères'
7 >>> var_1 == var_2
8 True
Vous constatez dans l’exemple ci-dessus que, pour Python, les guillemets simples et doubles sont équivalents. Nous
vous conseillons cependant d’utiliser les guillemets doubles car ceux-ci sont, de notre point de vue, plus lisibles.
Lorsque vous avez besoin de décrire plus en détail un module, une fonction, une classe ou une méthode, utilisez une
docstring sur plusieurs lignes :
1 """Docstring de plusieurs lignes, la première ligne est un résumé.
2
3 Après avoir sauté une ligne, on décrit les détails de cette docstring.
4 On termine la docstring avec les triples guillemets
5 sur la ligne suivante.
6 """
Remarque
La PEP 257 recommande d’écrire des docstrings avec trois doubles guillemets, c’est-à-dire :
"""Ceci est une docstring recommandée."""
mais pas :
'''Ceci n'est pas une docstring recommandée.'''
Comme indiqué dans le chapitre 15 Création de modules, n’oubliez pas que les docstrings sont destinées aux utilisateurs
des modules, fonctions, méthodes et classes que vous avez développés. Les éléments essentiels pour les fonctions et les
méthodes sont :
1. ce que fait la fonction ou la méthode,
2. ce qu’elle prend en argument,
3. ce qu’elle renvoie.
4. https://peps.python.org/pep-0008/#string-quotes
5. https://www.python.org/dev/peps/pep-0257/
Pour les modules et les classes, on ajoute également des informations générales sur leur fonctionnement.
Pour autant, la PEP 257 ne dit pas explicitement comment organiser les docstrings pour les fonctions et les méthodes.
Pour répondre à ce besoin, deux solutions ont émergées :
• la solution Google avec le Google Style Python Docstrings 6 ,
• la solution NumPy avec le NumPy Style Python Docstrings 7 . NumPy est un module complémentaire à Python,
très utilisé en analyse de données et dont on parlera dans le chapitre 20.
• Lignes 6 et 7. La section Parameters précise les paramètres de la fonction. Les tirets sur la ligne 7 soulignent le
nom de la section pour la rendre visible.
• Lignes 8 et 9. On indique le nom et le type du paramètre, séparés par le caractère deux-points. Le type n’est pas
obligatoire. En dessous, on indique une description du paramètre en question. La description est indentée.
• Lignes 10 à 12. Même chose pour le second paramètre. La description du paramètre peut s’étaler sur plusieurs
lignes.
• Lignes 14 et 15. La section Returns indique ce qui est renvoyé par la fonction (le cas échéant).
• Lignes 16 et 17. La mention du type renvoyé est obligatoire. En dessous, on indique une description de ce qui est
renvoyé par la fonction. Cette description est aussi indentée.
Attention
L’être humain a une fâcheuse tendance à la procrastination (le fameux « Bah je le ferai demain…») et écrire de la
documentation peut être un sérieux motif de procrastination. Soyez vigilant sur ce point, et rédigez vos docstrings au
moment où vous écrivez vos modules, fonctions, classes ou méthodes. Passer une journée (voire plusieurs) à écrire les
docstrings d’un gros projet est particulièrement pénible. Croyez-nous !
6. https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
7. https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html
Définition
Les outils pycodestyle, pydocstyle et pylint sont des linters, c’est-à-dire des programmes qui vont chercher les
sources potentielles d’erreurs dans un code informatique. Ces erreurs peuvent être des erreurs de style (PEP 8 et 257) ou
des erreurs logiques (manipulation d’une variable, chargement de module).
Voici le contenu du script script_quality_not_ok.py 8 que nous allons analyser par la suite :
1 """Un script de multiplication.
2 """
3
4 import os
5
6 def Multiplie_nombres(nombre1,nombre2 ):
7 """Multiplication de deux nombres entiers
8 Cette fonction ne sert pas à grand chose.
9
10 Parameters
11 ----------
12 nombre1 : int
13 Le premier nombre entier.
14 nombre2 : int
15 Le second nombre entier.
16 Très utile.
17
18 Returns
19 -------
20 int
21 Le produit des deux nombres.
22
23 """
24 return nombre1 *nombre2
25
26
27 if __name__ == "__main__":
28 print(f"2 x 3 = {Multiplie_nombres(2, 3)}")
29 print (f"4 x 5 = {Multiplie_nombres(4, 5)}")
8. https://python.sdv.u-paris.fr/data-files/script_quality_not_ok.py
• Ligne 4. Il y un espace de trop après le second argument nombre2 dans la définition de la fonction Multiplie_nombres
() à la ligne 6 (colonne 38) du script.
• Ligne 5. Il manque un espace après l’opérateur * à la ligne 26 (colonne 21) du script.
• Ligne 6. Il y a un espace de trop entre print et ( à la ligne 31 (colonne 10) du script.
Assez curieusement, pycodestyle n’a pas détecté que le nom de la fonction Multiplie_nombres() ne respecte
pas la convention de nommage (pas de majuscule).
Ensuite, l’outil pydocstyle va vérifier la conformité avec la PEP 257 et s’intéresser particulièrement aux docstrings :
$ pydocstyle script_quality_not_ok.py
script_quality_not_ok.py:1 at module level:
D200: One-line docstring should fit on one line with quotes (found 2)
script_quality_not_ok.py:7 in public function `Multiplie_nombres`:
D205: 1 blank line required between summary line and description (found 0)
script_quality_not_ok.py:7 in public function `Multiplie_nombres`:
D400: First line should end with a period (not 's')
• Lignes 2 et 3. pydocstyle indique que la docstring à la ligne 1 du script est sur deux lignes, alors qu’elle devrait
être sur une seule ligne.
• Lignes 4 et 5. Dans la docstring de la fonction Multiplie_nombres() (ligne 7 du script), il manque une ligne
vide entre la ligne résumé et la description plus complète.
• Lignes 6 et 7. Dans la docstring de la fonction Multiplie_nombres() (ligne 7 du script), il manque un point
à la fin de la première ligne.
Les outils pycodestyle et pydocstyle vont simplement vérifier la conformité aux PEP 8 et 257. L’outil pylint
va lui aussi vérifier une partie de ces règles mais il va également essayer de comprendre le contexte du code et proposer
des éléments d’amélioration. Par exemple :
$ pylint script_quality_not_ok.py
************* Module script_quality_not_ok
script_quality_not_ok.py:6:0: C0103: Function name "Multiplie_nombres"
doesn't conform to snake_case naming style (invalid-name)
script_quality_not_ok.py:4:0: W0611: Unused import os (unused-import)
------------------------------------------------------------------
Your code has been rated at 6.67/10
• Lignes 3 et 4. pylint indique que nom de la fonction Multiplie_nombres() ne respecte pas la convention
PEP 8 (ligne 6 du script).
• Ligne 5. Le module os est chargé mais pas utilisé (ligne 4 du script).
• Ligne 8. pylint produit également une note sur 10. Ne soyez pas surpris si cette note est très basse (voire
négative) la première fois que vous analysez votre script avec pylint. Cet outil fournit de nombreuses suggestions
d’amélioration et la note attribuée à votre script devrait rapidement augmenter. Pour autant, la note de 10 est
parfois difficile à obtenir. Ne soyez pas trop exigeant.
Une version améliorée du script précédent est disponible en ligne 9 .
All done!
1 file reformatted.
9. https://python.sdv.u-paris.fr/data-files/script_quality_ok.py
Le script script_quality_not_ok.py a été modifié pour être conforme à la PEP 8, ce qu’on peut vérifier avec
pycodestyle :
$ pycodestyle script_quality_not_ok.py
black peut modifier votre code de manière significative. Il est donc recommandé de l’utiliser avec l’option --diff
au préalable pour afficher les modifications apportées. Par exemple, avec le programme script_quality_not_ok.py
qui n’aurait pas été modifié :
import os
-def Multiplie_nombres(nombre1,nombre2 ):
+
+def Multiplie_nombres(nombre1, nombre2):
[...]
Conseil
black est très pratique. N’hésitez pas à l’utiliser pour formater automatiquement votre code.
Attention
• black ne fait qu’une entorse à la PEP 8 : il autorise des longueurs de lignes jusqu’à 88 caractères. Si vous souhaitez
respecter strictement la PEP 8, utilisez l’option --line-length 79.
• black se limite à la PEP 8. Il ne vérifie pas la conformité avec la PEP 257 ni la qualité du code (imports inutiles,
etc.). Utilisez toujours pydocstyle et pylint en complément.
Il est important de toujours structurer son code de la même manière. Ainsi, on sait tout de suite où trouver l’information
et un autre programmeur pourra s’y retrouver. Voici un exemple de code avec les différents éléments dans le bon ordre :
• Lignes 1 à 9. Cette docstring décrit globalement le script. Cette docstring (ainsi que les autres) seront visibles si
on importe le script en tant que module, puis en invoquant la commande help() (voir chapitre 15 Création de
modules).
• Lignes 11 à 15. On définit ici un certain nombre de variables avec des doubles underscores donnant quelques
informations sur la version du script, les auteurs, etc. Il s’agit de métadonnées que la commande help() pourra
afficher. Ces métadonnées sont utiles lorsque le code est distribué à la communauté.
• Lignes 17 à 22. Importation des modules. D’abord les modules internes à Python (fournis en standard), puis les
modules externes (ceux qu’il faut installer en plus), puis les modules créés localement. Un module par ligne.
• Ligne 24. Définition des constantes. Le nom des constantes est en majuscule.
• Lignes 27 à 39. Définition des fonctions. Avant chaque fonction, on laisse deux lignes vides.
• Lignes 42 à 44. On écrit le programme principal. Le test ligne 42 n’est vrai que si le script est utilisé en tant que
programme.
damment du reste. Pensez à écrire les docstrings en même temps que vous écrivez vos fonctions.
• Documentez-vous. L’algorithme dont vous avez besoin existe-t-il déjà dans un autre module ? De quels outils
mathématiques avez-vous besoin dans votre algorithme ?
• Quand l’algorithme est complexe, commentez votre code pour expliquer votre raisonnement. Utiliser des fonctions
(ou méthodes) encore plus petites peut aussi être une solution.
• Utilisez des noms de variables explicites, qui signifient quelque chose. En lisant votre code, on doit comprendre
ce que vous faites. Choisir des noms de variables pertinents permet aussi de réduire les commentaires.
• Quand vous construisez une structure de données complexe (par exemple une liste de dictionnaires contenant
d’autres objets), documentez l’organisation de cette structure de données avec un exemple simple.
• Si vous créez ou manipulez une entité cohérente avec des propriétés propres, essayez de construire une classe.
Reportez-vous, pour cela, au chapitre 23 Avoir la classe avec les objets.
• Testez votre code sur un petit jeu de données, pour comprendre rapidement ce qui se passe et corriger d’éven-
tuelles erreurs. Par exemple, une séquence d’ADN de 1 000 bases est plus facile à manipuler que le génome humain
(3 × 109 bases) !
• Lorsque votre programme « plante », lisez le message d’erreur. Python tente de vous expliquer ce qui ne va pas.
Le numéro de la ligne qui pose problème est aussi indiqué.
• Discutez avec des gens. Faites tester votre programme par d’autres. Les instructions d’utilisation sont-elles
claires ?
• Enfin, si vous distribuez votre code :
— Rédigez une documentation claire.
— Testez votre programme (jetez un œil aux tests unitaires 10 ).
— Précisez une licence d’utilisation (voir le site Choose an open source license 11 ).
10. https://fr.wikipedia.org/wiki/Test_unitaire
11. https://choosealicense.com/
12. https://realpython.com/python-code-quality/
13. https://openclassrooms.com/fr/courses/4425111-perfectionnez-vous-en-python/4464230-assimilez-les-bonnes-pratiqu
14. https://realpython.com/python-program-structure/
Le module re permet d’utiliser des expressions régulières avec Python. Les expressions régulières sont aussi appelées
en anglais regular expressions, ou en plus court regex. Dans la suite de ce chapitre, nous utiliserons souvent le mot regex
pour désigner une expression régulière. Les expressions régulières sont puissantes et incontournables en bioinformatique,
surtout lorsque vous souhaitez récupérer des informations dans de gros fichiers.
Cette action de recherche de données dans un fichier est appelée généralement parsing (qui signifie littéralement «
analyse syntaxique »). Le parsing fait partie du travail quotidien du bioinformaticien, il est sans arrêt en train de « fouiller
» dans des fichiers pour en extraire des informations d’intérêt, par exemple récupérer les coordonnées 3D des atomes
d’une protéine dans un fichier PDB, ou encore extraire les gènes d’un fichier GenBank.
Dans ce chapitre, nous ne ferons que quelques rappels sur les expressions régulières. Pour une documentation plus
complète, référez-vous à la page d’aide des expressions régulières 1 sur le site officiel de Python.
Ici, egrep affiche toutes les lignes du fichier du virus de l’herpès (herp_virus.gbk) dans lesquelles la regex ^DEF
(c’est-à-dire le mot DEF en début de ligne) est retrouvée.
1. https://docs.python.org/fr/3/library/re.html
176
17.1. Définition et syntaxe Chapitre 17. Expressions régulières et parsing
Remarque
Il est intéressant de faire un point sur le vocabulaire utilisé en anglais et en français. En général, on utilise le verbe
to match pour indiquer qu’une regex « a fonctionné ». Bien qu’il n’y ait pas de traduction littérale en français, on peut
utiliser les verbes « retrouver » ou « correspondre ». Par exemple, on pourra traduire l’expression « The regex matches
the line » par « La regex est retrouvée dans la ligne » ou encore « La regex correspond dans la ligne ».
Après avoir introduit le vocabulaire des regex, voici quelques éléments de syntaxe des métacaractères :
^ Début de chaîne de caractères ou de ligne.
Exemple : la regex ^ATG est retrouvée dans la chaîne de caractères ATGCGT mais pas dans la chaîne CCATGTT.
$ Fin de chaîne de caractères ou de ligne.
Exemple : la regex ATG$ est retrouvée dans la chaîne de caractères TGCATG mais pas dans la chaîne CCATGTT.
. N’importe quel caractère (mais un caractère quand même).
Exemple : la regex A.G est retrouvée dans ATG, AtG, A4G, mais aussi dans A-G ou dans A G.
[ABC] Le caractère A ou B ou C (un seul caractère).
Exemple : la regex T[ABC]G est retrouvée dans TAG, TBG ou TCG, mais pas dans TG.
[A-Z] N’importe quelle lettre majuscule.
Exemple : la regex C[A-Z]T est retrouvée dans CAT, CBT, CCT…
[a-z] N’importe quelle lettre minuscule.
[0-9] N’importe quel chiffre.
[A-Za-z0-9] N’importe quel caractère alphanumérique.
[^AB] N’importe quel caractère sauf A et B.
Exemple : la regex CG[^AB]T est retrouvée dans CG9T, CGCT… mais pas dans CGAT ni dans CGBT.
\ Caractère d’échappement (pour protéger certains caractères).
Exemple : la regex \+ désigne le caractère + littéral. La regex A\.G est retrouvée dans A.G et non pas dans A suivi
de n’importe quel caractère, suivi de G.
* 0 à n fois le caractère précédent ou l’expression entre parenthèses précédente.
Exemple : la regex A(CG)*T est retrouvée dans AT, ACGT, ACGCGT…
+ 1 à n fois le caractère précédent ou l’expression entre parenthèses précédente.
Exemple : la regex A(CG)+T est retrouvée dans ACGT, ACGCGT… mais pas dans AT.
? 0 à 1 fois le caractère précédent ou l’expression entre parenthèses précédente.
Exemple : la regex A(CG)?T est retrouvée dans AT ou ACGT.
{n} n fois le caractère précédent ou l’expression entre parenthèses précédente.
Exemple : la regex A(CG){2}T est retrouvée dans ACGCGT mais pas dans ACGT, ACGCGCGT ou ACGCG.
{n,m} n à m fois le caractère précédent ou l’expression entre parenthèses précédente.
Exemple : la regex A(C){2,4}T est retrouvée dans ACCT, ACCCT et ACCCCT mais pas dans ACT, ACCCCCT ou ACCC.
{n,} Au moins n fois le caractère précédent ou l’expression entre parenthèses précédente.
Exemple : la regex A(C){2,}T est retrouvée dans ACCT, ACCCT et ACCCCT mais pas à ACT ou ACCC.
{,m} Au plus m fois le caractère précédent ou l’expression entre parenthèses précédente.
Exemple : la regex A(C){,2}T est retrouvée dans AT, ACT et ACCT mais pas dans ACCCT ou ACC.
(CG|TT) Les chaînes de caractères CG ou TT.
Exemple : la regex A(CG|TT)C est retrouvée dans ACGC ou ATTC.
Enfin, il existe des caractères spéciaux qui sont bien commodes et qui peuvent être utilisés en tant que métacaractères :
\d remplace n’importe quel chiffre (d signifie digit), équivalent à [0-9].
\w remplace n’importe quel caractère alphanumérique et le caractère souligné (underscore) (w signifie word character),
équivalent à [0-9A-Za-z_].
\s remplace n’importe quel « espace blanc » (whitespace) (s signifie space), équivalent à [ \t\n\r\f]. La notion
d’espace blanc a été abordée dans le chapitre 11 Plus sur les chaînes de caractères. Les espaces blancs les plus
classiques sont l’espace , la tabulation \t, le retour à la ligne \n, mais il en existe d’autres comme \r et \f que nous
ne développerons pas ici. \s est très pratique pour détecter une combinaison d’espace(s) et/ou de tabulation(s).
Comme vous le constatez, les métacaractères sont nombreux et leur signification est parfois difficile à maîtriser. Faites
particulièrement attention aux métacaractères ., + et * qui, combinés ensemble, peuvent donner des résultats ambigus.
Attention
Il est important de savoir par ailleurs que les regex sont « avides » (greedy en anglais) lorsqu’on utilise les métaca-
ractères + et *. Cela signifie que la regex cherchera à « s’étendre » au maximum. Par exemple, si on utilise la regex A+
pour faire une recherche dans la chaîne TTTAAAAAAAAGC, tous les A de cette chaîne (huit en tout) seront concernés, bien
que AA, AAA, etc. « fonctionnent » également avec cette regex.
17.3 Le module re
17.3.1 La fonction search()
Dans le module re, la fonction search() est incontournable. Elle permet de rechercher un motif, c’est-à-dire une
regex, au sein d’une chaîne de caractères avec une syntaxe de la forme search(motif, chaine). Si motif est retrouvé
dans chaine, Python renvoie un objet du type SRE_Match.
Sans entrer dans les détails propres au langage orienté objet, si on utilise un objet du type SRE_Match dans un test,
il sera considéré comme vrai. Par exemple, si on recherche le motif tigre dans la chaîne de caractères "girafe tigre
singe" :
1 >>> import re
2 >>> animaux = "girafe tigre singe"
3 >>> re.search("tigre", animaux)
4 <_sre.SRE_Match object at 0x7fefdaefe2a0>
5 >>> if re.search("tigre", animaux):
6 ... print("OK")
7 ...
8 OK
Attention
Le motif que vous utilisez comme premier argument de la fonction search() sera interprété en tant que regex. Ainsi,
^DEF correspondra au mot DEF en début de chaîne et pas au caractère littéral ^suivi du mot DEF.
2. https://regexone.com/
3. https://regexr.com/
4. https://extendsclass.com/regex-tester.html#python
5. https://pythex.org/
6. https://www.regular-expressions.info
Il existe également la fonction fullmatch(), qui renvoie un objet du type SRE_Match si et seulement si l’expression
régulière correspond exactement à la chaîne de caractères.
1 >>> animaux = "tigre "
2 >>> re.fullmatch("tigre", animaux)
3 >>> animaux = "tigre"
4 >>> re.fullmatch("tigre", animaux)
5 <_sre.SRE_Match object; span=(0, 5), match='tigre'>
De manière générale, nous vous recommandons l’usage de la fonction search(). Si vous souhaitez avoir une cor-
respondance avec le début de la chaîne de caractères comme dans la fonction match(), vous pouvez toujours utiliser
l’accroche de début de ligne ^. Si vous voulez une correspondance exacte, comme dans la fonction fullmatch(), vous
pouvez utiliser les métacaractères ^ et $, par exemple ^tigre$.
17.3.4 Groupes
L’intérêt de l’objet de type SRE_Match renvoyé par Python lorsqu’une regex trouve une correspondance dans une
chaîne de caractères est de pouvoir ensuite récupérer certaines zones précises :
1 >>> regex = re.compile("([0-9]+)\.([0-9]+)")
Dans cet exemple, on recherche un nombre décimal, c’est-à-dire une chaîne de caractères :
• qui débute par un ou plusieurs chiffres [0-9]+,
• suivi d’un point \. (le point a d’habitude une signification de métacaractère, donc il faut l’échapper avec \ pour
qu’il retrouve sa signification de point),
• et qui se termine encore par un ou plusieurs chiffres [0-9]+.
Les parenthèses dans la regex créent des groupes ([0-9]+ deux fois) qui seront récupérés ultérieurement par la
méthode .group().
La totalité de la correspondance est donnée par .group(0), le premier élément entre parenthèses est donné par
.group(1) et le second par .group(2).
Les méthodes .start() et .end() donnent respectivement la position de début et de fin de la zone qui correspond
à la regex. Notez que la méthode .search() ne renvoie que la première zone qui correspond à l’expression régulière,
même s’il en existe plusieurs :
1 >>> resultat = regex.search("pi vaut 3.14 et e vaut 2.72")
2 >>> resultat.group(0)
3 '3.14'
L’utilisation des groupes entre parenthèses est également possible, ceux-ci sont alors renvoyés sous la forme de tuples :
1 >>> regex = re.compile("([0-9]+)\.([0-9]+)")
2 >>> resultat = regex.findall("pi vaut 3.14 et e vaut 2.72")
3 >>> resultat
4 [('3', '14'), ('2', '72')]
Encore plus puissant, il est possible d’utiliser dans le remplacement des groupes qui ont été « capturés » avec des
parenthèses :
1 >>> regex = re.compile("([0-9]+)\.([0-9]+)")
2 >>> phrase = "pi vaut 3.14 et e vaut 2.72"
3 >>> regex.sub("approximativement \\1", phrase)
4 'pi vaut approximativement 3 et e vaut vaut approximativement 2'
5 >>> regex.sub("approximativement \\1 (puis .\\2)",phrase)
6 'pi vaut approximativement 3 (puis .14) et e vaut approximativement 2 (puis .72)'
Si vous avez capturé des groupes, il suffit d’utiliser \\1, \\2 (etc.) pour utiliser les groupes correspondants dans la
chaîne de caractères substituée. On notera que la syntaxe générale pour récupérer des groupes dans les outils qui gèrent
les regex est \1, \2, etc. Toutefois, Python nous oblige à mettre un deuxième backslash car il y a ici deux niveaux : un
premier niveau Python où on veut mettre un backslash littéral (donc \\), puis un second niveau regex dans lequel on
veut retrouver \1. Si cela est confus, retenez seulement qu’il faut mettre un \\ devant le numéro de groupe.
Enfin, sachez que la réutilisation d’un groupe précédemment capturé est aussi utilisable lors d’une utilisation classique
de regex. Par exemple :
1 >>> re.search("(pan)\\1", "bambi et panpan")
2 <_sre.SRE_Match object; span=(9, 15), match='panpan'>
3 >>> re.search("(pan)\\1", "le pistolet a fait pan !")
4 >>>
Dans la regex (pan)\\1, on capture d’abord le groupe (pan) grâce aux parenthèses (il s’agit du groupe 1, puisque
c’est le premier jeu de parenthèses), immédiatement suivi du même groupe grâce au \\1. Dans cet exemple, on capture
donc le mot panpan (lignes 1 et 2). Si, par contre, on a une seule occurrence du mot pan, cette regex ne fonctionne
pas, ce qui est le cas ligne 3.
Bien sûr, si on avait eu un deuxième groupe, on aurait pu le réutiliser avec \\2, un troisième groupe avec \\3, etc.
Nous espérons vous avoir convaincu de la puissance du module re et des expressions régulières. Alors, plus de temps
à perdre, à vos regex !
17.4 Exercices
Conseil
Pour ces exercices, créez des scripts puis exécutez-les dans un shell.
Conseil
• Utilisez des regex pour trouver les lignes demandées.
• Des explications sur le format GenBank et des exemples de code sont fournies dans l’annexe A Quelques formats
de données en biologie.
7. https://python.sdv.u-paris.fr/data-files/NC_001133.gbk
Conseil
• Des explications sur le format FASTA et des exemples de code sont fournis dans l’annexe A Quelques formats de
données en biologie.
• La ligne de commentaire d’une séquence au format FASTA est de la forme
>sp|O95139|NDUB6_HUMAN NADH dehydrogenase [...]
Elle débute toujours pas le caractère >. Le numéro d’accession O95139 se situe entre le premier et le second symbole
| (symbole pipe). Attention, il faudra « échapper » ce symbole car il a une signification particulière dans une regex.
• Le numéro qui s’incrémente débutera à 1 et sera affiché sur 5 caractères, avec des 0 à sa gauche si nécessaires
(formatage {:05d}).
Écrivez un script ote_doublons.py qui lit le fichier breves_doublons.txt et qui supprime tous les doublons à
l’aide d’une regex. Le script affichera le nouveau texte à l’écran.
Conseil
Utilisez la méthode .sub().
Les notebooks Jupyter sont des cahiers électroniques qui, dans le même document, peuvent rassembler du texte,
des images, des formules mathématiques, des tableaux, des graphiques et du code informatique exécutable. Ils sont
manipulables interactivement dans un navigateur web.
Initialement développés pour les langages de programmation Julia, Python et R (d’où le nom Jupyter), les notebooks
Jupyter supportent près de 40 langages différents.
La cellule est l’élément de base d’un notebook Jupyter. Elle peut contenir du texte formaté au format Markdown ou
du code informatique qui pourra être exécuté.
Voici un exemple de notebook Jupyter (figure 18.1) :
Ce notebook est constitué de cinq cellules : deux avec du texte en Markdown (la première et la dernière) et trois avec
du code Python (légèrement grisées).
18.1 Installation
Avec la distribution Miniconda, les notebooks Jupyter s’installent avec la commande :
$ conda install -c conda-forge -y jupyterlab
Pour être exact, la commande précédente installe un peu plus que les notebooks Jupyter, mais nous verrons cela par
la suite.
18.2 JupyterLab
En 2018, le consortium Jupyter a lancé JupyterLab, qui est un environnement complet de programmation et d’analyse
de données.
Pour obtenir cette interface, lancez la commande suivante depuis un shell :
$ jupyter lab
Une nouvelle page devrait s’ouvrir dans votre navigateur web et vous devriez obtenir une interface similaire à la figure
18.2, avec à gauche un navigateur de fichiers et à droite le « Launcher », qui permet de créer un nouveau notebook
Jupyter, de lancer un terminal ou d’éditer un fichier texte, un fichier Mardown, un script Python…
L’interface proposée par JupyterLab est très riche. On peut y organiser un notebook Jupyter, un éditeur de fichier
texte, un terminal… Les possibilités sont nombreuses et nous vous invitons à explorer cette interface par vous-même.
184
18.3. Création d’un notebook Chapitre 18. Jupyter et ses notebooks
Figure 18.1 – Exemple de notebook Jupyter. Les chiffres entourés désignent les différentes cellules.
Remarque
L’extension .ipynb est l’extension de fichier des notebooks Jupyter.
Vous pouvez entrer des instructions Python dans la première cellule. Par exemple :
1 a = 2
2 b = 3
3 print(a+b)
Pour créer une nouvelle cellule, vous avez, ici encore, plusieurs possibilités :
• Cliquer sur l’icône + dans la barre de menu au dessus du notebook.
• Cliquer sur la 2e icône à partir de la droite (juste à côté de la poubelle), dans les icônes situées à l’intérieur de la
cellule, à droite.
Une nouvelle cellule vide devrait apparaître.
Vous pouvez également créer une nouvelle cellule, en positionnant votre curseur dans la première cellule, puis en
pressant simultanément les touches Alt + Entrée. Si vous utilisez cette combinaison de touches, vous remarquerez que
le numéro à gauche de la première cellule est passée de [1] à [2], car vous avez exécuté une nouvelle fois la première
cellule puis créé une nouvelle cellule.
Vous pouvez ainsi créer plusieurs cellules les unes à la suite des autres. Un objet créé dans une cellule antérieure sera
disponible dans les cellules suivantes. Par exemple, dans la figure 18.6, nous avons quatre cellules.
Dans un notebook Jupyter, il est parfaitement possible de réexécuter une cellule précédente. Par exemple la première
cellule, qui porte désormais à sa gauche la numérotation [5] (voir figure 18.7).
Attention
La possibilité d’exécuter les cellules d’un notebook Jupyter dans un ordre arbitraire peut prêter à confusion, notamment
si vous modifiez la même variable dans plusieurs cellules.
Nous vous recommandons de régulièrement relancer complètement l’exécution de toutes les cellules de votre notebook,
de la première à la dernière, en cliquant sur le menu Kernel puis Restart Kernel and Run All Cells et enfin de valider le
message Restart Kernel ? en cliquant sur le bouton Restart.
1. https://fr.wikipedia.org/wiki/Markdown
2. https://daringfireball.net/projects/markdown/syntax
Remarque
Pour quitter l’interface JupyterLab, il y a plusieurs possibilités :
• Dans le menu en haut à gauche de l’interface, cliquer sur File, puis Shut Down, puis confirmer en cliquant sur le
bouton Shut Down.
• Une méthode plus radicale est de revenir sur le shell depuis lequel JupyterLab a été lancé, puis de presser deux fois
de suite la combinaison de touches Ctrl + C.
Remarque
Dans cette rubrique, nous vous montrerons quelques exemples d’utilisation de magic commands exécutées dans un
notebook Jupyter.
1 Les cellules de code apparaitront de cette manière
2 dans un notebook Jupyter, avec des numéros de lignes à gauche.
18.6.1 %whos
La commande %whos liste tous les objets (variables, fonctions, modules…) utilisés dans un notebook.
Si une cellule précédente contenait le code :
1 a = 2
2 b = 3
3
4 def ma_fonction(x, y):
5 return x + y
6
7 resultat_1 = ma_fonction(a, 10)
8 resultat_2 = ma_fonction("Bonjour", "Jupyter")
alors l’exécution de :
1 %whos
renvoie :
Variable Type Data/Info
-----------------------------------
a int 2
b int 3
ma_fonction function <function ma_fonction at 0x7f219c2d04a0>
resultat_1 int 12
resultat_2 str BonjourJupyter
18.6.2 %history
La commande %history liste toutes les commandes Python lancées dans un notebook :
1 %history
3. https://ipython.readthedocs.io/en/stable/interactive/magics.html
a = 2
b = 3
print(a + b)
def ma_fonction(x, y):
return x + y
ma_fonction(a, 10)
ma_fonction("Bonjour", "Jupyter")
%whos
%history
18.6.3 %%time
La commande %%time (avec deux symboles %) va mesurer le temps d’exécution d’une cellule. C’est très utile pour
faire des tests de performance. On peut, par exemple, comparer les vitesses de parcours d’une liste avec une boucle for,
par les éléments ou par les indices des éléments.
Ainsi, cette cellule :
1 %%time
2 concentrations = [5.5, 7.2, 11.8, 13.6, 19.1, 21.7, 29.4]
3 somme_carres = 0.0
4 for conc in concentrations:
5 somme_carres += conc**2
renvoie :
CPU times: user 8 µs, sys: 2 µs, total: 10 µs
Wall time: 11.9 µs
et celle-ci :
1 %%time
2 concentrations = [5.5, 7.2, 11.8, 13.6, 19.1, 21.7, 29.4]
3 somme_carres = 0.0
4 for idx in range(len(concentrations)):
5 somme_carres += concentrations[idx]**2
renvoie :
CPU times: user 26 µs, sys: 5 µs, total: 31 µs
Wall time: 37.4 µs
Comme attendu, la première méthode (itération par les éléments) est plus rapide que la seconde (itération par les
indices des éléments). Les temps obtenus dépendent de la machine sur laquelle vous exécutez ces commandes. Mais, sur
une même machine, les résultats peuvent fluctuer d’une exécution à l’autre en fonction de l’activité de la machine. Ces
fluctuations seront d’autant plus importantes que le temps d’exécution est court.
18.6.4 %%timeit
Pour palier à ce problème, la magic command %%timeit va exécuter plusieurs fois la cellule et donner une estimation
du temps d’exécution moyen. Python détermine automatiquement le nombre d’itérations et le nombre de répétitions à
effectuer pour obtenir un temps global d’exécution raisonnable.
En reprenant l’exemple précédent, on obtient :
1 %%timeit
2 concentrations = [5.5, 7.2, 11.8, 13.6, 19.1, 21.7, 29.4]
3 somme_carres = 0.0
4 for conc in concentrations:
5 somme_carres += conc**2
492 ns ± 11.8 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
et
1 %%timeit
2 concentrations = [5.5, 7.2, 11.8, 13.6, 19.1, 21.7, 29.4]
3 somme_carres = 0.0
4 for idx in range(len(concentrations)):
5 somme_carres += concentrations[idx]**2
606 ns ± 21.6 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
Ici, chaque cellule sera exécutée un million de fois sur sept répétitions, soit sept millions de fois au total. Comme
nous l’avions expliqué dans le chapitre 5 Boucles et comparaisons, itérer une liste sur ses éléments est la méthode la plus
efficace (et la plus élégante).
Conseil
Les notebooks Jupyter sont particulièrement adaptés à l’analyse de données en combinaison avec les modules mat-
plotlib et pandas, qui seront abordés dans les prochains chapitres.
4. https://github.com/MaayanLab/Zika-RNAseq-Pipeline/blob/master/Zika.ipynb
5. https://f1000research.com/articles/5-1574/
Figure 18.8 – Notebook avec : (A) une cellule au format Markdown et (B) le rendu après exécution.
Module Biopython
Nous allons aborder dans ce chapitre un module incontournable en bioinformatique. En effet, le module Biopython 1
permet de manipuler des données biologiques, comme des séquences (nucléiques et protéiques) ou des structures (fichiers
PDB), et d’interroger des bases de données comme PubMed. Le tutoriel 2 est particulièrement bien fait, n’hésitez pas à
le consulter.
Dans ce chapitre, nous vous montrerons quelques exemples d’utilisation du module Biopython pour vous convaincre
de sa pertinence. Ces exemples seront exécutés dans un notebook Jupyter.
1 Les cellules de code apparaitront de cette manière
2 dans un notebook Jupyter, avec des numéros de lignes à gauche.
Attention
Le nom du module Biopython n’est pas biopython, mais Bio (avec un B majuscule).
1. http://biopython.org/
2. http://biopython.org/DIST/docs/tutorial/Tutorial.html
3. https://python.sdv.u-paris.fr/livre-dunod
194
19.3. Manipulation de séquences Chapitre 19. Module Biopython
Seq('ATATCGGCTATAGCATGC')
Seq('TATAGCCGATATCGTACG')
1 ADN.reverse_complement()
Seq('GCATGCTATAGCCGATAT')
Seq('ISAIAC')
Conseil
Dans l’annexe A Quelques formats de données en biologie, vous trouverez de nombreux exemples d’utilisation de
Biopython pour manipuler des données aux formats FASTA, GenBank et PDB.
• Ligne 3. On lance la requête (transferrin) sur le moteur de recherche pubmed. La requête est stockée dans la
variable req_esearch.
• Ligne 4. Le résultat est lu et stocké dans la variable res_esearch.
Sans être un vrai dictionnaire, la variable res_esearch en a cependant plusieurs propriétés. Voici ses clés :
1 res_esearch.keys()
La valeur associée à la clé IdList est une liste qui contient les identifiants (PMID) des articles scientifiques associés
à la requête (ici transferrin) :
1 res_esearch["IdList"]
1 len(res_esearch["IdList"])
20
Cette liste ne contient les identifiants que de 20 publications, alors que, si nous faisons cette même requête directement
sur le site de PubMed depuis un navigateur web, nous obtenons plus de 45 700 résultats.
En réalité, le nombre exact de publications (en janvier 2024) est connu :
1 res_esearch["Count"]
'45717'
Pour ne pas saturer les serveurs du NCBI, seulement 20 PMID sont renvoyés par défaut. Mais vous pouvez augmenter
cette limite en utilisant le paramètre retmax dans la fonction Entrez.esearch().
Nous pouvons maintenant récupérer des informations sur une publication précise en connaissant son PMID, par
exemple, l’article avec le PMID 22294463 5 , dont un aperçu est sur la figure 19.1.
Figure 19.1 – Aperçu de la publication Known and potential roles of transferrin in iron biology depuis le site PubMed.
La variable res_esummary n’est pas réellement une liste (son type exacte est Bio.Entrez.Parser.ListElement),
mais elle est indexable (voir chapitre 14 Conteneurs). Cette pseudo-liste n’a qu’un seul élément, qui est lui-même un
dictionnaire dont voici les clés :
1 res_esummary[0].keys()
Nous pouvons alors facilement obtenir le titre, le DOI et la date de publication (PubDate) de cet article, ainsi que le
journal (Source) dans lequel il a été publié :
1 res_esummary[0]["Title"]
1 res_esummary[0]["DOI"]
'10.1007/s10534-012-9520-3'
1 res_esummary[0]["PubDate"]
'2012 Aug'
1 res_esummary[0]["Source"]
'Biometals'
Enfin, pour récupérer le résumé de la publication précédente, nous allons utiliser la fonction Entrez.efetch() :
1 req_efetch = Entrez.efetch(
2 db="pubmed", id="22294463",
3 rettype="abstract", retmode="text")
4 req_efetch.read()
Le résultat n’est pas très lisible, car il apparait comme un seul bloc. Le caractère \n désigne un retour à la ligne.
L’instruction print() affichera le résultat de manière plus lisible :
1 req_efetch = Entrez.efetch(
2 db="pubmed", id="22294463",
3 rettype="abstract", retmode="text")
4 print(req_efetch.read())
Bartnikas TB(1).
Author information:
(1)Department of Pathology, ’Childrens Hospital, Enders 1110, 300 Longwood
Avenue, Boston, MA 02115, USA. [email protected]
Transferrin is an abundant serum metal-binding protein best known for its role
in iron delivery. The human disease congenital atransferrinemia and animal
models of this disease highlight the essential role of transferrin in
erythropoiesis and iron metabolism. Patients and mice deficient in transferrin
exhibit anemia and a paradoxical iron overload attributed to deficiency in
hepcidin, a peptide hormone synthesized largely by the liver that inhibits
dietary iron absorption and macrophage iron efflux. Studies of inherited human
disease and model organisms indicate that transferrin is an essential regulator
of hepcidin expression. In this paper, we review current literature on
transferrin deficiency and present our recent findings, including potential
overlaps between transferrin, iron and manganese in the regulation of hepcidin
expression.
DOI: 10.1007/s10534-012-9520-3
PMCID: PMC3595092
PMID: 22294463 [Indexed for MEDLINE]
Le résultat contient bien le résumé de la figure 19.1, mais aussi d’autres informations comme le titre, le DOI, la date
de publication…
19.5 Exercices
Conseil
Pour ces exercices, utilisez des notebooks Jupyter.
Conseil
Pour cet exercice, n’hésitez pas à consulter :
• Le chapitre 14 Conteneurs pour trier un dictionnaire.
• L’annexe A Quelques formats de données en biologie pour lire un fichier FASTA avec Biopython.
6. https://python.sdv.u-paris.fr/data-files/p_falciparum_500.fasta
Sur le site de PubMed 7 , cherchez combien d’articles scientifiques sont relatifs à la barstar.
Effectuez la même chose avec Python et la méthode Entrez.esearch() de Biopython.
Choisissez un des PMID renvoyé et vérifiez dans PubMed que l’article associé est bien à propos de la barstar. Pour
cela, indiquez le PMID choisi dans la barre de recherche de PubMed et cliquez sur Search. Attention, l’association n’est
pas toujours évidente. Cherchez éventuellement dans le résumé de l’article si besoin.
Est-ce que le nombre total d’articles trouvés est cohérent avec celui obtenu sur le site de PubMed ?
Récupérez les informations de la publication dont le PMID est 29701945 8 . Vous utiliserez la méthode Entrez.
esummary().
Affichez le titre, le DOI, le nom du journal (Source) et la date de publication (PubDate) de cet article. Vérifiez que
cela correspond bien à ce que vous avez lu sur PubMed.
Récupérez le résumé de la publication dont le PMID est 29701945. Vous utiliserez la méthode Entrez.efetch().
Affichez ce résumé.
En utilisant la méthode Entrez.esearch(), récupérez tous les PMID relatifs à la barstar. Pour cela, pensez à
augmenter le paramètre retmax. Vos PMID seront stockés dans la liste pmids sous forme de chaînes de caractères.
Vérifiez sur PubMed que vous avez récupéré le bon nombre d’articles.
En utilisant maintenant la méthode Entrez.esummary() dans une boucle, récupérez la date de publication de
chaque article. Stockez l’année sous forme d’un nombre entier dans la liste years. Cette étape peut prendre une dizaine
de minutes, soyez patient. Vous pouvez afficher dans votre boucle un message qui indique où vous en êtes dans la
récupération des articles.
Vérifiez que votre liste years contient bien autant d’éléments que la liste pmids.
Calculez maintenant le nombre de publications par année. Vous créerez pour cela un dictionnaire freq qui aura pour
clé les années (oui, une clé de dictionnaire peut aussi être un entier) et pour valeur le nombre de publications associées
à une année donnée.
Créez une liste x qui contient les clés du dictionnaire freq. Ordonnez les valeurs dans x avec la méthode .sort().
Créez maintenant une seconde liste y qui contient, dans l’ordre, le nombre de publications associées à chaque année. Bien
évidemment, les listes x et y doivent avoir la même taille. Au fait, en quelle année la barstar apparaît pour la première
fois dans une publication scientifique ?
Ensuite, avec le module matplotlib (que nous aborderons prochainement), vous allez pouvoir afficher la distribution
des publications en fonction des années :
7. https://www.ncbi.nlm.nih.gov/pubmed/
8. https://www.ncbi.nlm.nih.gov/pubmed/29701945
Vous pouvez également ajouter un peu de cosmétique et enregistrer le graphique sur votre disque dur :
1 import matplotlib.pyplot as plt
2
3 fig, ax = plt.subplots()
4 ax.bar(x, y)
5
6 # Étiquetage des axes.
7 ax.set_xlabel("Années")
8 ax.set_ylabel("Nombre de publications")
9
10 # Ajout du titre du graphique.
11 ax.set_title("Distribution des publications qui mentionnent la barstar")
12
13 # Enregistrement sur le disque.
14 fig.savefig("distribution_barstar_annee.png")
Module NumPy
Le module NumPy 1 est incontournable en bioinformatique. Il permet d’effectuer des calculs sur des vecteurs ou des
matrices, élément par élément, via un nouveau type d’objet appelé array.
Dans ce chapitre, nous vous montrerons quelques exemples d’utilisation du module NumPy pour vous convaincre de
sa pertinence. Ces exemples seront exécutés dans un notebook Jupyter.
1 Les cellules de code apparaitront de cette manière
2 dans un notebook Jupyter, avec des numéros de lignes à gauche.
201
Chapitre 20. Module NumPy 20.3. Objets de type array
array([1, 2, 3])
1 b = np.array(a)
2 b
array([1, 2, 3])
1 type(b)
numpy.ndarray
Nous avons converti la liste [1, 2, 3] en array. La fonction np.array() accepte aussi comme argument un tuple,
ou un objet de type range.
Par ailleurs, lorsqu’on demande à Python d’afficher le contenu d’un objet array, le mot array et les symboles ([ et
]) sont utilisés pour le distinguer d’une liste (délimitée par les caractères [ et ]) ou d’un tuple (délimité par les caractères
( et )).
Remarque
Un objet array ne contient que des données homogènes, c’est-à-dire d’un type identique. Il est possible de créer un
objet array à partir d’une liste contenant des entiers et des chaînes de caractères, mais, dans ce cas, toutes les valeurs
seront comprises par NumPy comme des chaînes de caractères :
1 a = np.array([1, 2, "tigre"])
2 a
Dans cet exemple, toutes les valeurs du array sont entre guillemets, indiquant qu’il s’agit de chaînes de caractères.
De même, il est possible de créer un objet array à partir d’une liste constituée d’entiers et de floats, mais toutes les
valeurs seront alors comprises par NumPy comme des floats :
1 b = np.array([1, 2, 3.5])
2 b
array([1. , 2. , 3.5])
Sur un modèle similaire à la fonction range(), la fonction arange() permet de construire un array à une dimension :
1 np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Comme avec range(), on peut spécifier en argument une borne de début, une borne de fin et un pas :
1 np.arange(10, 0, -1)
array([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
Un autre avantage de la fonction arange() est qu’elle génère des objets array qui contiennent des entiers ou des
floats (ce qui n’est pas possible avec range()) selon l’argument qu’on lui passe. D’abord un entier :
1 np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Puis un float :
1 np.arange(10.0)
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
La différence fondamentale entre un objet array à une dimension et une liste (ou un tuple) est que celui-ci est
considéré comme un vecteur. Par conséquent, on peut effectuer des opérations vectorielles élément par élément sur
ce type d’objet, ce qui est bien commode lorsqu’on analyse de grandes quantités de données. Regardez ces exemples :
1 v = np.arange(4)
2 v
array([0, 1, 2, 3])
array([1, 2, 3, 4])
array([0, 2, 4, 6])
Avec les listes, ces opérations n’auraient été possibles qu’en utilisant des boucles. Nous vous encourageons donc à
utiliser dorénavant les objets array lorsque vous aurez besoin de faire des opérations élément par élément.
Il est aussi possible de multiplier deux arrays entre eux. Le résultat correspond alors à la multiplication élément par
élément des deux arrays initiaux :
1 v * v
array([0, 1, 4, 9])
array([[1, 2],
[3, 4],
[5, 6]])
On peut aussi créer des tableaux à trois dimensions en passant comme argument à la fonction array() une liste de
listes de listes :
1 x = np.array([[[1, 2], [2, 3]], [[4, 5], [5, 6]]])
2 x
array([[[1, 2],
[2, 3]],
[[4, 5],
[5, 6]]])
La fonction array() peut créer des tableaux à n’importe quel nombre de dimensions. Toutefois, cela devient vite
compliqué lorsqu’on dépasse trois dimensions. Retenez qu’un objet array à une dimension peut être assimilé à un vecteur,
un array à deux dimensions à une matrice. On peut généraliser ces objets mathématiques avec un nombre arbitraires de
dimensions, on parle alors de tenseur, qui sont représentés avec NumPy en array à n dimensions. Nous nous focaliserons
dans la suite sur des arrays à une dimension (1D) ou deux dimensions (2D).
Avant de continuer, il est important de définir comment sont organisés ces arrays 2D qui représentent des matrices.
Il s’agit de tableaux de nombres qui sont organisés en lignes et en colonnes comme le montre la figure 20.1. Les indices
indiqués dans cette figure seront définis un peu plus loin dans la rubrique Indices.
array([0, 1, 2, 3])
array([[1, 2],
[3, 4],
[5, 6]])
L’attribut .ndim renvoie le nombre de dimensions de l’array. Par exemple, 1 pour un vecteur et 2 pour une matrice :
1 v.ndim
1 w.ndim
L’attribut .shape renvoie les dimensions sous forme d’un tuple. Dans le cas d’une matrice (array à deux dimensions),
la première valeur du tuple correspond au nombre de lignes et la seconde au nombre de colonnes.
1 v.shape
(4,)
1 w.shape
(3, 2)
Enfin, l’attribut .size renvoie le nombre total d’éléments contenus dans l’array :
1 v.size
1 w.size
array([0, 1, 2, 3, 4, 5])
1 a.shape
(6,)
1 b = a.reshape((2, 3))
2 b
array([[0, 1, 2],
[3, 4, 5]])
1 b.shape
(2, 3)
1 a
array([0, 1, 2, 3, 4, 5])
Notez bien que l’array initial a n’a pas été modifié et que a.reshape((2, 3)) n’est pas la même chose que
a.reshape((3, 2)) :
1 c = a.reshape((3, 2))
2 c
array([[0, 1],
[2, 3],
[4, 5]])
1 c.shape
(3, 2)
La méthode .reshape() attend que les nouvelles dimensions soient compatibles avec la dimension initiale de
l’objet array, c’est-à-dire que le nombre d’éléments contenus dans les différents arrays soit le même. Dans nos exemples
précédents, 6 = 2 × 3 = 3 × 2.
Si les nouvelles dimensions ne sont pas compatibles avec les dimensions initiales, la méthode .reshape() génère une
erreur.
1 a = np.arange(0, 6)
2 a
array([0, 1, 2, 3, 4, 5])
1 a.shape
(6,)
1 d = a.reshape((3, 4))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[36], line 1
----> 1 d = a.reshape((3, 4))
La méthode .resize(), par contre, ne déclenche pas d’erreur dans une telle situation et ajoute des 0 jusqu’à ce que
le nouvel array soit rempli, ou bien coupe la liste initiale :
1 a = np.arange(0, 6)
2 a.shape
(6,)
(3, 3)
1 a
array([[0, 1, 2],
[3, 4, 5],
[0, 0, 0]])
1 b = np.arange(0, 10)
2 b.shape
(10,)
(2, 3)
1 b
array([[0, 1, 2],
[3, 4, 5]])
Attention
• Cette modification de la forme de l’array par la méthode .resize() est faite « sur place » (in place), c’est-à-dire
que la méthode ne renvoie rien, mais l’array initial est bel et bien modifié (comme des méthodes sur les listes telles
que la méthode .reverse(), voir le chapitre 13 Plus sur les listes).
• Si l’option refcheck=False n’est pas présente, Python peut parfois renvoyer une erreur s’il existe des références
vers l’array qu’on souhaite modifier.
Enfin, il existe la fonction np.resize() qui, dans le cas d’un nouvel array plus grand que l’array initial, va répéter
l’array initial afin de remplir les cases manquantes :
1 a = np.arange(0, 6)
2 a.shape
(6,)
(3, 5)
1 c
array([[0, 1, 2, 3, 4],
[5, 0, 1, 2, 3],
[4, 5, 0, 1, 2]])
1 a
array([0, 1, 2, 3, 4, 5])
Notez que la fonction np.resize() renvoie un nouvel array mais ne modifie pas l’array initial, contrairement à la
méthode .resize(), décrite ci-dessus.
Remarque
Depuis le début de ce chapitre, nous avons toujours montré l’affichage d’un array tel quel dans un notebook Jupyter :
1 a = np.array(range(10))
2 a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
1 a2 = np.ones((3, 3))
2 a2
Nous avons déjà indiqué que Python affiche systématiquement le mot array ainsi que les parenthèses, crochets et
virgules pour séparer les éléments. Toutefois, si vous utilisez la fonction print(), l’affichage sera différent. Le mot array,
les parenthèses et les virgules disparaissent :
1 print(a)
[0 1 2 3 4 5 6 7 8 9]
1 print(a2)
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
Dans ce cas, seule la présence ou l’absence de virgules permet de savoir s’il s’agit d’un array ou d’une liste.
[2, 7, 6, 4, 0, 3, 1, 5]
3. https://numpy.org/doc/stable/reference/arrays.ndarray.html#calculation
array([[2, 7],
[6, 4],
[0, 3],
[1, 5]])
1 a.max()
La méthode .max() a bien renvoyé la valeur maximale 7. Un argument très utile existant dans toutes ces méthodes
est axis. Pour un array 2D, axis=0 signifie qu’on fera l’opération le long de l’axe 0, à savoir les lignes. C’est-à-dire que
l’opération se fait en variant les lignes. On récupère ainsi une valeur par colonne :
1 a.max(axis=0)
array([6, 7])
Dans l’array 1D récupéré, le premier élément vaut 6 (maximum de la 1ère colonne) et le second vaut 7 (maximum
de la seconde colonne).
Avec axis=1, on fait une opération similaire, mais en faisant varier les colonnes. On récupère ainsi une valeur par
ligne :
1 a.max(axis=1)
array([7, 6, 3, 5])
20.3.4 Indices
Pour récupérer un ou plusieurs élément(s) d’un objet array, vous pouvez utiliser les indices, de la même manière
qu’avec les listes :
1 a = np.arange(10)
2 a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
1 a[1]
array([5, 6, 7, 8, 9])
array([0, 2, 4, 6, 8])
Dans le cas d’un objet array à deux dimensions, vous pouvez récupérer une ligne complète (d’indice i), une colonne
complète (d’indice j) ou bien un seul élément. La figure 20.1 montre comment sont organisés les indices des lignes et
des colonnes :
1 a = np.array([[1, 2], [3, 4]])
2 a
array([[1, 2],
[3, 4]])
1 a[:,0]
array([1, 3])
1 a[0,:]
array([1, 2])
La syntaxe a[i,:] renvoie la ligne d’indice i, et a[:,j] renvoie la colonne d’indice j. Les tranches sont aussi
utilisables sur un array à deux dimensions.
1 a[1, 1]
La syntaxe a[i, j] renvoie l’élément à la ligne d’indice i et à la colonne d’indice j. Notez que NumPy suit la
convention mathématiques des matrices 4 , à savoir, qu’on définit toujours un élément par sa ligne puis par sa
colonne. En mathématiques, l’élément ai j d’une matrice A se trouve à la ime ligne et à la jme colonne :
Remarque
Pour un array 2D, si un seul indice est donné, par exemple a[i], on récupère la ligne d’indice i sous forme d’array
1D :
1 a = np.array([[1, 2], [3, 4]])
2 a
array([[1, 2],
[3, 4]])
1 a[0]
array([1, 2])
1 a[1]
array([3, 4])
Pour cette raison, la syntaxe a[i][j] est également valide pour récupérer un élément :
1 a[1, 1]
4. https://fr.wikipedia.org/wiki/Matrice_(math%C3%A9matiques)#D%C3%A9finitions
Examinez bien les phrases Coucou je suis dans [...] et essayez de comprendre pourquoi elles apparaissent. Bien
que nos trois méthodes soient définies comme def masse(), vous pouvez constater qu’elles sont appelées lorsque on
invoque citron.masse, citron.masse = 25 ou del citron.masse (à l’intérieur de la classe, ce serait self.masse,
self.masse = 25 ou del self.masse). Autrement dit, on n’utilise jamais la syntaxe .masse(). Ceci est justement
dû au fait que .masse est un objet de type property.
Conseil
Lorsque vous souhaitez créer des objets property , nous vous conseillons la syntaxe pythonique @property,@nom_attribut
.setter et @nom_attribut.deleter plutôt que celle de la rubrique précédente avec la ligne masse = property(
fget=get_masse, fset=set_masse, fdel=del_masse). Cette syntaxe améliore grandement la lisibilité.
Une méthode décorée avec @property peut être utile seule sans avoir le setter et/ou le deleter correspondant(s).
On rencontre cela lorsqu’on souhaite créer un « d’attribut dynamique » plutôt qu’avoir un appel de méthode explicite.
Regardons un exemple :
1 class ADN:
2 def __init__(self):
3 self.sequence = []
4
5 def __repr__(self):
6 return f"La séquence de mon brin d'ADN est {self.sequence}"
7
8 def ajoute_base(self, nom_base):
9 self.sequence.append(nom_base)
10
11 @property
12 def len(self):
13 return len(self.sequence)
Lorsqu’on utilise l’attribut brin_adn.len, ceci invoque finalement l’appel de l’objet property len qui, in fine, est
une méthode. Ainsi, la valeur renvoyée sera calculée à chaque fois, bien que dans la syntaxe on n’a pas une notation
.methode(), mais plutôt .attribut. Voilà pourquoi nous avons parlé d’attribut dynamique. Cela permet d’alléger la
syntaxe quand il n’y a pas spécifiquement d’arguments à passer à la méthode qui se trouve derrière cet attribut.
Conseil
Si on souhaite contrôler ce que fait le client de la classe pour certains attributs « délicats » ou « stratégiques », on peut
utiliser la classe property. Toutefois, nous vous conseillons de ne l’utiliser que lorsque cela se révèle vraiment nécessaire,
donc avec parcimonie. Le but étant de ne pas surcharger le code inutilement. Cela va dans le sens des recommandations
des développeurs de Python (comme décrit dans la PEP8).
Attention
En Python, il n’existe pas d’attributs privés comme dans d’autres langages orientés objet. L’utilisateur a accès à tous
les attributs quels qu’ils soient, même s’ils contiennent un ou plusieurs caractère(s) underscore(s) (voir ci-dessous) !
Définition
En Python les attributs non publics sont des attributs dont le nom commence par un ou deux caractère(s) underscore.
Par exemple, _attribut, ou __attribut.
La présence des underscores dans les noms d’attributs est un signe clair que le client ne doit pas y toucher. Toutefois,
cela n’est qu’une convention, et comme dit ci-dessus le client peut tout de même modifier ces attributs.
Par exemple, reprenons la classe Citron de la rubrique précédente dont l’attribut .masse est contrôlé avec un objet
property :
Malgré l’objet property, nous avons pu modifier l’attribut non public ._masse directement !
Il existe également des attributs dont le nom commence par deux caractères underscores. Nous n’avons encore jamais
croisé ce genre d’attribut. Ces derniers mettent en place le name mangling.
Définition
Le name mangling 14 , ou encore substantypage ou déformation de nom en français, correspond à un mécanisme de
changement du nom d’un attribut selon si on est à l’intérieur ou à l’extérieur d’une classe.
Regardons un exemple :
14. https://en.wikipedia.org/wiki/Name_mangling
1 class Citron:
2 def __init__(self):
3 self.__mass = 100
4
5 def get_mass(self):
6 return self.__mass
7
8
9 if __name__ == "__main__":
10 citron1 = Citron()
11 print(citron1.get_mass())
12 print(citron1.__mass)
La ligne 12 du code a donc conduit à une erreur : Python prétend ne pas connaître l’attribut .__mass. On pourrait
croire que cela constitue un mécanisme de protection des attributs. En fait il n’en est rien, car on va voir que l’attribut
est toujours accessible et modifiable. Si on modifiait le programme principal comme suit :
1 if __name__ == "__main__":
2 citron1 = Citron()
3 print(citron1.__dict__)
Ce code affiche 100 puis 200. La ligne 12 a permis d’accéder à l’attribut .__mass de la classe mère Fruit, et la
ligne 13 a permis d’accéder à l’attribut .__mass de la classe Citron.
Le name mangling n’est donc pas un mécanisme de « protection » d’un attribut, il n’a pas été conçu pour ça. Les
concepteurs de Python le disent clairement dans la PEP 8 : « Generally, double leading underscores should be used only
to avoid name conflicts with attributes in classes designed to be subclassed ».
Donc en Python, on peut tout détruire, même les attributs délicats contenant des underscores. Pourquoi Python
permet-il un tel paradoxe ? Selon le concepteur de Python, Guido van Rossum : « We’re all consenting adults here »,
nous sommes ici entre adultes, autrement dit nous savons ce que nous faisons !
Conseil
En résumé, n’essayez pas de mettre des barrières inutiles vers vos attributs. Cela va à l’encontre de la philosophie
Python. Soignez plutôt la documentation et faites confiance aux utilisateurs de votre classe !
Les classes peuvent bien sûr contenir des docstrings comme les fonctions et les modules. C’est d’ailleurs une pratique
vivement recommandée. Voici un exemple sur notre désormais familière classe Citron :
1 class Citron:
2 """Voici la classe Citron.
3
4 Il s'agit d'une classe assez impressionnante qui crée des objets
5 citrons.
6 Par défaut une instance de Citron contient l'attribut de classe
7 saveur.
8 """
9 saveur = "acide"
10
11 def __init__(self, couleur="jaune", taille="standard"):
12 """Constructeur de la classe Citron.
13
14 Ce constructeur prend deux arguments par mot-clé
15 couleur et taille."""
16 self.couleur = couleur
17 self.taille = taille
18
19 def __str__(self):
20 """Redéfinit le comportement avec print()."""
21 return f"saveur: {saveur}, couleur: {couleur}, taille: {taille}"
22
23 def affiche_coucou(self):
24 """Méthode inutile qui affiche coucou."""
25 print("Coucou !")
Python formate automatiquement l’aide comme il le fait avec les modules (voir chapitre 15 Création de modules).
Comme nous l’avons dit dans le chapitre 16 Bonnes pratiques en programmation Python, n’oubliez pas que les docstrings
sont destinées aux utilisateurs de votre classe. Elles doivent donc contenir tout ce dont un utilisateur a besoin pour
comprendre ce que fait la classe et comment l’utiliser.
Notez que si on instancie la classe citron1 = Citron() et qu’on invoque l’aide sur l’instance help(citron1), on
obtient la même page d’aide. Comme pour les modules, si on invoque l’aide pour une méthode de la classe
help(citron1.affiche_coucou), on obtient l’aide pour cette méthode seulement.
Toutes les docstrings d’une classe sont en fait stockées dans un attribut spécial nommé instance.__doc__. Cet
attribut est une chaîne de caractères contenant la docstring générale de la classe. Ceci est également vrai pour les
modules, méthodes et fonctions. Si on reprend notre exemple ci-dessus :
1 >>> citron1 = Citron()
2 >>> print(citron1.__doc__)
3 Voici la classe Citron.
4
5 Il s'agit d'une classe assez impressionnante qui crée des objets
6 citrons.
7 Par défaut une instance de Citron contient l'attribut de classe
8 saveur.
9
10 >>> print(citron1.affiche_coucou.__doc__)
11 Méthode inutile qui affiche coucou.
L’attribut .__doc__ est automatiquement créé par Python au moment de la mise en mémoire de la classe (ou module,
méthode, fonction, etc.).
• Nous vous déconseillons de mettre comme paramètre par défaut une liste vide (ou tout autre objet séquentiel
modifiable) :
1 def __init__(self, liste=[]):
2 self.liste = liste
Si vous créez des instances sans passer d’argument lors de l’instanciation, toutes ces instances pointeront vers la
même liste. Cela peut avoir des effets désastreux.
• Ne mettez pas non plus une liste vide (ou tout autre objet séquentiel modifiable) comme attribut de classe.
1 class Citron:
2 liste = []
Ici chaque instance pourra modifier la liste, ce qui n’est pas souhaitable. Souvenez-vous, la modification des attributs
de classe doit se faire par une syntaxe Citron.attribut = valeur (et non pas via les instances).
• Comme abordé dans la rubrique Différence entre les attributs de classe et d’instance, nous vous conseillons de ne
jamais modifier les attributs de classe. Vous pouvez néanmoins les utiliser comme constantes.
• Si vous avez besoin d’attributs modifiables, utilisez des attributs d’instance et initialisez-les dans la méthode .
__init__() (et nulle part ailleurs). Par exemple, si vous avez besoin d’une liste comme attribut, créez la plutôt
dans le constructeur :
1 class Citron:
2 def __init__(self):
3 self.liste = []
Ainsi, vous aurez des listes réellement indépendantes pour chaque instance.
24.7.5 Namedtuples
Imaginons que l’on souhaite stocker des éléments dans un conteneur, que l’on puisse retrouver ces éléments avec une
syntaxe conteneur.element et que ces éléments soient non modifiables. On a vu ci-dessus, les classes ne sont pas faites
pour cela, il n’est pas conseillé de les utiliser comme des conteneurs inertes, on les conçoit en général afin d’y créer aussi
des méthodes. Dans ce cas, les namedtuples 15 sont faits pour vous ! Ce type de conteneur est issu du module collections
que nous avions évoqué dans le chapitre 14 Conteneurs.
1 >>> import collections
2 >>> Citron = collections.namedtuple("Citron", "masse couleur saveur forme")
3 >>> Citron
4 <class '__main__.Citron'>
5 >>> citron = Citron(10, "jaune", "acide", "ellipsoide")
6 >>> citron
7 Citron(masse=10, couleur='jaune', saveur='acide', forme='ellipsoide')
8 >>> citron.masse
9 10
10 >>> citron.forme
11 'ellipsoide'
Lignes 2 à 4. La fonction namedtuple() renvoie une classe qui sert à créer de nouveaux objets citrons. Attention
cette classe est différente de celles que l’on a rencontrées jusqu’à maintenant, car elle hérite de la classe builtins.tuple
(on peut le voir en faisant help(Citron)). En ligne 2, on passe en argument le nom de la classe souhaitée (i.e. Citron),
puis une chaîne de caractères avec des mots séparés par des espaces qui correspondront aux attributs (on pourrait aussi
passer une liste ["masse", "couleur", "saveur", "forme"]).
Ligne 5. On instancie un nouvel objet citron.
Lignes 6 à 11. On peut retrouver les différents attributs avec une syntaxe instance.attribut.
Mais dans namedtuple, il y a tuple ! Ainsi, l’instance citron hérite de tous les attributs des tuples :
15. https://docs.python.org/fr/3/library/collections.html#collections.namedtuple
1 >>> citron[0]
2 10
3 >>> citron[3]
4 'ellipsoide'
5 >>> citron.masse = 100
6 Traceback (most recent call last):
7 File "<stdin>", line 1, in <module>
8 AttributeError: can't set attribute
9 >>> for elt in citron:
10 ... print(elt)
11 ...
12 10
13 jaune
14 acide
15 ellipsoide
Lignes 1 et 2. On crée un nouveau namedtuples avec la méthode ._replace(). Notez qu’il faut passer un (ou
plusieurs) argument(s) par mot-clé à cette méthode désignant les attributs à modifier.
Lignes 3 et 4. L’objet initial citron est intact puisqu’un namedtuples est non modifiable.
Lignes 5 à 7. En ré-affectant ce que renvoie la méthode ._replace() dans dans un objet de même nom citron,
on peut faire évoluer son contenu comme on a pu le faire avec les chaînes de caractères.
Enfin, il est possible de convertir un namedtuple en dictionnaire (ordonné) avec la méthode ._asdict() :
1 >>> citron._asdict()
2 OrderedDict([('masse', 10), ('couleur', 'jaune'), ('saveur', 'acide'), ('forme', 'ellipsoide')])
Quand utiliser les namedtuples ? Vous souvenez-vous de la différence entre les listes et les dictionnaires ? Ici, c’est
un peu la même chose entre les tuples et les namedtuples. Les namedtuples permettent de créer un code plus lisible en
remplaçant des numéros d’indice par des noms. Le fait qu’ils soient non modifiables peut aussi avoir un avantage par
rapport à l’intégrité des données. Si vous trouvez les namedtuples limités, sachez que vous pouvez créer votre propre
classe qui hérite d’un namedtuple afin de lui ajouter de nouvelles méthodes « maison ».
16. https://dbader.org/blog/writing-clean-python-with-namedtuples
1 >>> int
2 <class 'int'>
3 >>> list
4 <class 'list'>
5 >>> range
6 <class 'range'>
7 >>> property
8 <class 'property'>
Et bien, c’est parce-que ce sont bel et bien des classes ! Donc, lorsqu’on invoque par exemple liste1 = list(), on
crée finalement une instance de la classe list. Python ne met pas list en CamelCase car ce sont des classes natives
(built-in classes). En effet, les auteurs de Python ont décidé que les classes et fonctions natives sont en minuscules, et
les exceptions en CamelCase (voir ce lien 17 ).
Finalement, la création d’une instance à partir d’une classe ou l’appel d’une fonction possède la même syntaxe
mot_clé() :
1 >>> class Citron:
2 ... pass
3 ...
4 >>> Citron()
5 <__main__.Citron object at 0x7fb776308a10>
6 >>> def fct():
7 ... return "Coucou"
8 ...
9 >>> fct()
10 'Coucou'
On peut le voir aussi quand on invoque l’aide sur un de ces outils, par exemple help(int) :
Help on class int in module builtins:
class int(object)
| int([x]) -> integer
| int(x, base=10) -> integer
[...]
Par conséquent, d’un point de vue purement sémantique nous devrions parler de classe plutôt que de fonction pour
les instructions comme list(), range(), etc. Toutefois, nous avons décidé de garder le nom fonction pour ne pas
compliquer les premiers chapitres de ce cours.
24.9 Exercices
Conseil
Pour ces exercices, créez des scripts puis exécutez-les dans un shell.
On se propose de tester cela sur la molécule simple de benzene. Vous aurons besoin du fichier benzene.pdb 18 pour
réaliser cet exercice.
Après les import nécessaires, le programme contiendra une constante donnant les masses des atomes sous forme de
dictionnaire : ATOM_MASSES = {"C": 12.0, "O": 16.0, "H": 1.0}.
Créer une classe Atom en vous inspirant des exercices du chapitre 23 Avoir la classe avec les objets. Cette classe devra
instancier des objets contenant les attributs d’instance suivants :
• nom d’atome (par exemple C1)
• type d’atome (une seule lettre, déduit du nom d’atome, par exemple C)
• coordonnée x
• coordonnée y
• coordonnée z
Le nom d’atome et coordonnées cartésiennes seront passés au constructeur.
Ajouter les méthodes calc_distance(), calc_com() (center of mass). Ajouter une méthode mute_atom(name
) qui change le nom de l’atome, où name est un nouveau nom d’atome (par exemple O1). Cette méthode changera
également l’attribut d’instance décrivant le type d’atome.
Créer une classe Molecule qui construit les attributs d’instance : - Nom de la molécule - Une liste d’atomes (vide à
l’instanciation) : list_atoms - Une liste indiquant la connectivité (la liste des atomes connectés, vide à l’instanciation) :
list_connectivity
Le constructeur prendra en argument seulement le nom de la molécule.
Créer une méthode add_atom(atom) qui vérifie si l’argument passé est bien une instance de la classe Atom, et qui
ajoute atom dans la liste d’atomes.
Créer une autre méthode build_mlc_from_pdb(filename) qui prend en argument un nom de fichier pdb. La
méthode lit le fichier pdb, et pour chaque atome lu, crée une instance de la classe Atom, et ajouter celle-ci à list_atoms
.
Ajouter une méthode calc_mass() qui calcule et renvoie masse de la molécule.
Créer une méthode calc_com() qui cette fois-ci calcule et renvoie le centre de masse de la molécule entière.
Ajouter la méthode calc_connectivity() qui calcule et renvoie une liste décrivant la connectivité entre les atomes.
Deux atomes sont considérés connectés s’il y a une liaison covalente entre eux, on peut pour cela calculer la distance
entre eux qui doit être inférieure à 1.6 Å. La liste de connectivité pourra être construite dans ce style : [("C1", "H1")
, ("C1", "C2"), ...].
Chaque paire d’atome doit apparaitre une seule fois (pas de [("C1", "H1"), [("H1", "C1"), ...].
Créer une méthode spéciale affichant les caractéristiques de la molécule lorsqu’on utilise print() avec une instance
de cette classe Molecule, par exemple print(benzene). Cette méthode pourra par exemple afficher avant d’avoir créé
la molécule :
Molecule benzene
No atom for the moment
No connectivity for the moment
Ou bien, lorsque la molécule est créée et la connectivité déterminée, elle s’affichera comme ceci :
Molecule benzene
atom C1, type C, mass = 12.0 amu, coor( -2.145, 0.973, -0.003)
atom H1, type H, mass = 1.0 amu, coor( -3.103, 0.460, -0.005)
[...]
Connectivity
C1 connected to H1
C1 connected to C2
[...]
Pour lancer le programme dans un premier temps, vous pourrez instancier une molécule benzene, puis y ajouter les
atomes :
1 if __name__ == "__main__":
2 benzene = Molecule("benzene")
3 print(benzene)
4 benzene.build_mlc_from_pdb("benzene.pdb")
5 print(benzene)
18. %22https://python.sdv.u-paris.fr/data-files/benzene.pdb%22
Dans un deuxième temps, le programme principal calculera la masse et le centre de masse de benzene et les affichera.
Muter ensuite l’atome H1 en O1 et recalculer la masse et le centre de masse et les afficher.
Pour aller plus loin, vous pouvez ajouter une méthode qui calcule et affiche un graphe de la molécule avec le module
networkx 19 . La page de tutorial 20 pourra vous être utile.
Par exemple :
19. https://networkx.org/
20. https://networkx.org/documentation/latest/tutorial.html#drawing-graphs
Conseil
Dans ce chapitre, nous allons utiliser des classes, nous vous conseillons de bien relire les chapitres 23 Avoir la classe
avec les objets et 24 Avoir plus la classe avec les objets (en ligne). Par ailleurs, nous vous conseillons de relire également
la rubrique Arguments positionnels et arguments par mot-clé du chapitre 10 sur les fonctions.
Les arguments passés à la ligne de commande sont tout à fait classiques dans le monde de la bioinformatique. Toutefois,
il se peut que vous développiez un programme pour une communauté plus large, qui n’a pas forcément l’habitude d’utiliser
un shell et la ligne de commande. Une GUI permettra un usage plus large de votre programme, il est donc intéressant
de regarder comment s’y prendre. Dans notre exemple ci-dessus on pourrait par exemple développer une interface où
l’utilisateur choisirait le nom du fichier d’entrée par l’intermédiaire d’une boîte de dialogue, et de contrôler les options en
cliquant sur des boutons, ou des « listes de choix ». Une telle GUI pourrait ressembler à la figure 25.1.
Au delà de l’aspect convivial pour l’utilisateur, vous pourrez, avec une GUI, construire des fenêtres illustrant des
éléments que votre programme génère à la volée. Ainsi, vous « verrez » ce qui se passe de manière explicite et en direct !
Par exemple, si on réalise une simulation de particules, on a envie de voir un « film » des particules en mouvement, c’est-
à-dire comment ces particules bougent au fur et à mesure que les pas de simulation avancent. Une GUI vous permettra
une telle prouesse ! Enfin, sachez que certains logiciels scientifiques ont été développés avec la bibliothèque graphique Tk
(par exemple pymol, vmd, etc.). Qui sait, peut-être serez-vous le prochain développeur d’un outil incontournable ?
Il existe beaucoup de modules pour construire des applications graphiques. Par exemple : Tkinter 1 , wxpython 2 , PyQt 3 ,
1. https://wiki.python.org/moin/TkInter
2. http://www.wxpython.org/
3. https://pyqt.readthedocs.io
300
25.2. Quelques concepts liés à la programmation graphique Chapitre 25. Fenêtres graphiques et Tkinter
PyGObject 4 , etc. Nous présentons dans ce chapitre le module Tkinter qui est présent de base dans les distributions Python
(pas besoin a priori de faire d’installation de module externe). Tkinter permet de piloter la bibliothèque graphique Tk
(Tool Kit), Tkinter signifiant tk interface. On pourra noter que cette bibliothèque Tk peut être également pilotée par
d’autres langages (Tcl, perl, etc.).
Définition
Les widgets (window gadget) sont des objets graphiques permettant à l’utilisateur d’interagir avec votre programme
Python de manière conviviale. Par exemple, dans la fenêtre sur la figure 25.1, les boutons, les listes de choix, ou encore
la zone de texte sont des widgets.
L’utilisation d’une GUI va amener une nouvelle manière d’aborder le déroulement d’un programme, il s’agit de la
programmation dite « événementielle ». Jusqu’à maintenant vous avez programmé « linéairement », c’est-à-dire que
les instructions du programme principal s’enchaînaient les unes derrière les autres (avec bien sûr de possibles appels à
des fonctions). Avec une GUI, l’exécution est décidée par l’utilisateur en fonction de ses interactions avec les différents
widgets. Comme c’est l’utilisateur qui décide quand et où il clique dans l’interface, il va falloir mettre en place ce qu’on
appelle un « gestionnaire d’événements ».
Définition
Le gestionnaire d’événements est une sorte de « boucle infinie » qui est à l’affût de la moindre action de la part de
l’utilisateur. C’est lui qui effectuera une action lors de l’interaction de l’utilisateur avec chaque widget de la GUI. Ainsi,
l’exécution du programme sera réellement guidée par les actions de l’utilisateur.
La bibliothèque Tk que nous piloterons avec le module Python Tkinter propose tous les éléments cités ci-dessus (fe-
nêtre graphique, widgets, gestionnaire d’événements). Nous aurons cependant besoin d’une dernière notion : les fonctions
callback.
Définition
4. https://pygobject.readthedocs.io/en/latest/
Une fonction callback est une fonction passée en argument d’une autre fonction.
Conseil
Si vous êtes débutant, vous pouvez sauter cette rubrique.
Jusqu’à maintenant nous avons toujours appelé les fonctions ou les méthodes de cette manière :
1 var = fct(arg1, arg2)
2
3 obj.methode(arg)
où les arguments étaient des objets « classiques » (par exemple une chaîne de caractères, un entier, un float, etc.).
Sachez qu’il est possible de passer en argument une fonction à une autre fonction ! Par exemple :
1 def fct_callback(arg):
2 print(f"J'aime bien les {arg} !")
3
4
5 def une_fct(ma_callback):
6 print("Je suis au début de une_fct(), "
7 "et je vais exécuter la fonction callback :")
8 ma_callback("fraises")
9 print("une_fct() se termine.")
10
11 if __name__ == "__main__":
12 une_fct(fct_callback)
Vous voyez que dans le programme principal, lors de l’appel de une_fct(), on lui passe comme argument une autre
fonction mais sans aucune parenthèses ni argument, c’est-à-dire fct_callback tout court. En d’autres termes, cela
est différent de
une_fct(fct_callback("scoubidous")).
Dans une telle construction, fct_callback("scoubidous") serait d’abord évaluée, puis ce serait la valeur ren-
voyée par cet appel qui serait passée à une_fct() (n’essayez pas sur notre exemple car cela mènerait à une erreur !).
Que se passe-t-il en filigrane lors de l’appel une_fct(fct_callback) ? Python passe une référence vers la fonction
fct_callback (en réalité il s’agit d’un pointeur, mais tout ceci est géré par Python et est transparent pour l’utilisateur).
Vous souvenez-vous ce qui se passait avec une liste passée en argument à une fonction (voir le chapitre 13 Plus sur les
fonctions) ? C’était la même chose, une référence était envoyée plutôt qu’une copie. Python Tutor 5 nous confirme cela
(cf. figure 25.2).
Lorsqu’on est dans une_fct() on pourra utiliser bien sûr des arguments lors de l’appel de notre fonction callback
si on le souhaite. Notez enfin que dans une_fct() la fonction callback reçue en argument peut avoir un nom différent
(comme pour tout type de variable).
À quoi cela sert-il ? À première vue cette construction peut sembler ardue et inutile. Toutefois, vous verrez que dans
le module Tkinter les fonctions callback sont incontournables. En effet, on utilise cette construction pour lancer une
fonction lors de l’interaction de l’utilisateur avec un widget : par exemple, lorsque l’utilisateur clique sur un bouton et
qu’on souhaite lancer une fonction particulière suite à ce clic. Notez enfin que nous les avons déjà croisées avec :
5. http://pythontutor.com
• le tri de dictionnaire par valeur avec la syntaxe sorted(dico, key=dico.get) (voir le chapitre 8 Dictionnaires
et tuples) ;
• le tri par longueur de mots avec la syntaxe sorted(liste, key=len) (voir chapitre 12 Plus sur les listes) ;
• les objets property avec la syntaxe property(fget=get_masse, fset=set_masse) (voir le chapitre 24 Avoir
plus la classe avec les objets (en ligne)).
Ligne 2. On crée la fenêtre principale (vous la verrez apparaître !). Pour cela, on crée une instance de la classe tk.Tk
dans la variable racine. Tous les widgets que l’on créera ensuite seront des fils de cette fenêtre. On pourra d’ailleurs
noter que cette classe tk.Tk ne s’instancie en général qu’une seule fois par programme. Vous pouvez, par curiosité, lancer
une commande dir(racine) ou help(racine), vous verrez ainsi les très nombreuses méthodes et attributs associés à
un tel objet Tk.
Ligne 3. On crée un label, c’est-à-dire une zone dans la fenêtre principale où on écrit un texte. Pour cela, on a créé
une variable label qui est une instance de la classe tk.Label. Cette variable label contient donc notre widget, nous la
réutiliserons plus tard (par exemple pour placer ce widget dans la fenêtre). Notez le premier argument positionnelracine
passé à la classe tk.Label, celui-ci indique la fenêtre parente où doit être dessinée le label. Cet argument doit toujours
être passé en premier et il est vivement conseillé de le préciser. Nous avons passé un autre argument avec le nom
text pour indiquer, comme vous l’avez deviné, le texte que nous souhaitons voir dans ce label. La classe tk.Label
peut recevoir de nombreux autres arguments, en voici la liste exhaustive 6 . Dans les fonctions Tkinter qui construisent
6. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/label.html
un widget, les arguments possibles pour la mise en forme de celui-ci sont nombreux, si bien qu’ils sont toujours des
arguments par mot-clé. Si on ne précise pas un de ces arguments lors de la création du widget, l’argument prendra
alors une valeur par défaut. Cette liste des arguments par mot-clé est tellement longue qu’en général on ne les précisera
pas tous. Heureusement, Python autorise l’utilisation des arguments par mot-clé dans un ordre quelconque. Comme nous
l’avons vu dans le chapitre 10 Fonctions, souvenez vous que leur utilisation dans le désordre implique qu’il faudra toujours
préciser leur nom : par exemple vous écrirez text="blabla" et non pas "blabla" tout court.
Ligne 4. De même, on crée un bouton « Quitter » qui provoquera la fermeture de la fenêtre et donc l’arrêt de
l’application si on clique dessus. À nouveau, on passe la fenêtre parente en premier argument, le texte à écrire dans le
bouton, puis la couleur de ce texte. Le dernier argument command=racine.destroy va indiquer la fonction / méthode
à exécuter lorsque l’utilisateur clique sur le bouton. On pourra noter que l’instance de la fenêtre mère tk.Tk (que nous
avons nommée racine) possède une méthode .destroy() qui va détruire le widget sur lequel elle s’applique. Comme
on tue la fenêtre principale (que l’on peut considérer comme un widget contenant d’autres widgets), tous les widgets
fils seront détruits et donc l’application s’arrêtera. Vous voyez par ailleurs que cette méthode racine.destroy est
passée à l’argument command= sans parenthèses ni arguments : il s’agit donc d’une fonction callback comme expliqué
ci-dessus. Dans tous les widgets Tkinter, on doit passer à l’argument command=... une fonction / méthode callback.
La liste exhaustive des arguments possibles de la classe tk.Button se trouve ici 7 .
Lignes 6 et 7. Vous avez noté que lors de la création de ce label et de ce bouton, rien ne s’est passé dans la fenêtre.
C’est normal, ces deux widgets existent bien, mais il faut maintenant les placer à l’intérieur de la fenêtre. On appelle pour
ça la méthode .pack(), avec une notation objet widget.pack() : à ce moment précis, vous verrez votre label apparaître
ainsi que la fenêtre qui se redimensionne automatiquement en s’adaptant à la grandeur de votre label. L’invocation de la
même méthode pour le bouton va faire apparaître celui-ci juste en dessous du label et redimensionner la fenêtre. Vous
l’aurez compris la méthode .pack() place les widgets les uns en dessous des autres et ajuste la taille de la fenêtre. On
verra plus bas que l’on peut passer des arguments à cette méthode pour placer les widgets différemment (en haut, à
droite, à gauche).
Au final, vous devez obtenir une fenêtre comme sur la figure 25.3.
Vous voyez maintenant la même fenêtre avec les mêmes fonctionnalités par rapport à la version dans l’interpréteur
(voir la figure 25.3). Nous commentons ici les différences (dans le désordre) :
Ligne 6. Le bouton a été créé en ligne 5, mais on voit qu’il est possible de préciser une option de rendu du widget
après cette création (ici on met le texte en rouge avec l’option "fg"). La notation ressemble à celle d’un dictionnaire
avec une syntaxe générale widget["option"] = valeur.
Ligne 9. L’instruction racine.mainloop() va lancer le gestionnaire d’événements que nous avons évoqué ci-dessus.
C’est lui qui interceptera la moindre action de l’utilisateur, et qui lancera les portions de code associées à chacune de ses
actions. Bien sûr, comme nous développerons dans ce qui va suivre toutes nos applications Tkinter dans des scripts (et
non pas dans l’interpréteur), cette ligne sera systématiquement présente. Elle sera souvent à la fin du script, puisque, à
l’image de ce script, on écrit d’abord le code construisant l’interface, et on lance le gestionnaire d’événements une fois
l’interface complètement décrite, ce qui lancera au final l’application.
7. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/button.html
Ligne 10. Cette ligne ne s’exécute qu’après l’arrêt de l’application (soit en cliquant sur le bouton « Quitter », soit en
cliquant sur la croix).
Ligne 5. Pour quitter l’application, on utilise ici la méthode .quit(). Celle-ci casse la .mainloop() et arrête ainsi
le gestionnaire d’événements. Cela mène à l’arrêt de l’application. Dans le premier exemple dans l’interpréteur, on avait
utilisé la méthode .destroy() sur la fenêtre principale. Comme son nom l’indique, celle-ci détruit la fenêtre principale
et mène aussi à l’arrêt de l’application. Cette méthode aurait donc également fonctionné ici. Par contre, la méthode
.quit() n’aurait pas fonctionné dans l’interpréteur car, comme on l’a vu, la boucle .mainloop() n’y est pas présente.
Comme nous écrirons systématiquement nos applications Tkinter dans des scripts, et que la boucle .mainloop() y est
obligatoire, vous pourrez utiliser au choix .quit() ou .destroy() pour quitter l’application.
Ligne 3. On crée notre application en tant que classe. Notez que cette classe porte un nom qui commence par
une majuscule (comme recommandé dans les bonnes pratiques de la PEP8 8 , voir le chapitre 16 Bonnes pratiques en
programmation Python). L’argument passé dans les parenthèses indique que notre classe Application hérite de la
classe tk.Tk. Par ce mécanisme, nous héritons ainsi de toutes les méthodes et attributs de cette classe mère, mais nous
pouvons en outre en ajouter de nouvelles/nouveaux (on parle aussi de « redéfinition » de la classe tk.Tk) !
Ligne 4. On crée un constructeur, c’est-à-dire une méthode qui sera exécutée lors de l’instanciation de notre classe
(à la ligne 16).
Ligne 5. On appelle ici le constructeur de la classe mère tk.Tk.__init__(). Pourquoi fait-on cela ? On se souvient
dans la version linéaire de l’application, on avait utilisé une instanciation classique : racine = tk.Tk(). Ici, l’effet de
l’appel du constructeur de la classe mère permet d’instancier la fenêtre Tk dans la variable self directement. C’est-à-dire
que la prochaine fois que l’on aura besoin de cette instance (lors de la création des widgets par exemple, cf. lignes 9 et
8. https://www.python.org/dev/peps/pep-0008/
10), on utilisera directement self plutôt que racine ou tout autre nom donné à l’instance. Comme vu dans le chapitre
23 Avoir la classe avec les objets, appeler le constructeur de la classe mère est une pratique classique lorsqu’une classe
hérite d’une autre classe.
Ligne 6. On appelle la méthode self.creer_widgets() de notre classe Application. Pour rappel, le self avant
le .creer_widgets() indique qu’il s’agit d’une méthode de notre classe (et non pas d’une fonction classique).
Ligne 8. La méthode .creer_widgets() va créer des widgets dans l’application.
Ligne 9. On crée un label en instanciant la classe tk.Label(). Notez que le premier argument passé est maintenant
self (au lieu de racine précédemment) indiquant la fenêtre dans laquelle sera construit ce widget.
Ligne 10. De même on crée un widget bouton en instanciant la classe tk.Button(). Là aussi, l’appel à la méthode
.quit() se fait par self.quit puisque la fenêtre est instanciée dans la variable self. Par ailleurs, on ne met ni
parenthèses ni arguments à self.quit car il s’agit d’une fonction callback (comme dans la rubrique précédente).
Lignes 11 et 12. On place les deux widgets dans la fenêtre avec la méthode .pack().
Ligne 15. Ici on autorise le lancement de notre application Tkinter en ligne de commande (python tk_application.
py), ou bien de réutiliser notre classe en important tk_application.py en tant que module (import tk_application
) (voir le chapitre 15 Création de modules).
Ligne 16. On instancie notre application.
Ligne 17. On donne un titre dans la fenêtre de notre application. Comme on utilise de petits widgets avec la méthode
pack(), il se peut que le titre ne soit pas visible lors du lancement de l’application. Toutefois, si on « étire » la fenêtre
à la souris, le titre le deviendra. On pourra noter que cette méthode .title() est héritée de la classe mère Tk.
Ligne 18. On lance le gestionnaire d’événements.
Au final, vous obtiendrez le même rendu que précédemment (cf. figure 25.3). Alors vous pourrez-vous poser la
question, « pourquoi ai-je besoin de toute cette structure alors que le code précédent semblait plus direct ? ». La réponse
est simple, lorsqu’un projet de GUI grossit, le code devient très vite illisible s’il n’est pas organisé en classe. De plus,
la non-utilisation de classe rend quasi-obligatoire l’utilisation de variables globales, ce qui on l’a vu, est à proscrire
définitivement ! Dans la suite du chapitre, nous verrons quelques exemples qui illustrent cela (cf. la rubrique suivante).
Ligne 4. On voit qu’il faut d’abord créer le widget canvas, comme d’habitude en lui passant l’instance de la fenêtre
principale en tant qu’argument positionnel, puis les options. Notons que nous lui passons comme options la hauteur et la
largeur du canvas. Même s’il s’agit d’arguments par mot-clé, donc optionnels, c’est une bonne pratique de les préciser.
En effet, les valeurs par défaut risqueraient de nous mener à dessiner hors de la zone visible (cela ne génère pas d’erreur
mais n’a guère d’intérêt).
Ligne 6 à 8. Nous dessinons maintenant des objets graphiques à l’intérieur du canevas avec les méthodes .create_oval
() (dessine une ellipse) et .create_line() (dessine une ligne). Les arguments positionnels sont les coordonnées de
9. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/canvas.html
l’ellipse (les deux points englobant l’ellipse, cf. ce lien 10 pour la définition exacte) ou de la ligne. Ensuite, on passe comme
d’habitude des arguments par mot-clé (vous commencez à avoir l’habitude !) pour mettre en forme ces objets graphiques.
Le rendu de l’image est montré dans la figure 25.4 ainsi que le système de coordonnées associé au canvas. Comme
dans la plupart des bibliothèques graphiques, l’origine du repère du canvas (i.e. la coordonnée (0,0)) est en haut à gauche.
Les x vont de gauche à droite, et les y vont de haut en bas.
Figure 25.4 – Exemple 1 de canvas avec le système de coordonnées. Le système de coordonnées est montré en vert et
n’apparaît pas sur la vraie fenêtre Tkinter.
Attention
L’axe des y est inversé par rapport à ce que l’on représente en mathématique. Si on souhaite représenter une fonction
mathématique (ou tout autre objet dans un repère régi par un repère mathématique), il faudra faire un changement de
repère.
Voici un exemple un peu plus conséquent d’utilisation du widget canvas qui est inclus dans une classe. Il s’agit d’une
application dans laquelle il y a une zone de dessin, un bouton dessinant des cercles, un autre des lignes et un dernier
bouton qui quitte l’application (figure 25.5).
10. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/create_oval.html
1 import tkinter as tk
2 import random as rd
3
4 class AppliCanevas(tk.Tk):
5 def __init__(self):
6 tk.Tk.__init__(self)
7 self.size = 500
8 self.creer_widgets()
9
10 def creer_widgets(self):
11 # création canevas
12 self.canv = tk.Canvas(self, bg="light gray", height=self.size,
13 width=self.size)
14 self.canv.pack(side=tk.LEFT)
15 # boutons
16 self.bouton_cercles = tk.Button(self, text="Cercle !",
17 command=self.dessine_cercles)
18 self.bouton_cercles.pack(side=tk.TOP)
19 self.bouton_lignes = tk.Button(self, text="Lignes !",
20 command=self.dessine_lignes)
21 self.bouton_lignes.pack()
22 self.bouton_quitter = tk.Button(self, text="Quitter",
23 command=self.quit)
24 self.bouton_quitter.pack(side=tk.BOTTOM)
25
26 def rd_col(self):
27 return rd.choice(("black", "red", "green", "blue", "yellow", "magenta",
28 "cyan", "white", "purple"))
29
30 def dessine_cercles(self):
31 for i in range(20):
32 x, y = [rd.randint(1, self.size) for j in range(2)]
33 diameter = rd.randint(1, 50)
34 self.canv.create_oval(x, y, x+diameter, y+diameter,
35 fill=self.rd_col())
308 36 Cours de Python / Université Paris Cité / UFR Sciences du Vivant
37 def dessine_lignes(self):
38 for i in range(20):
25.6. Le widget canvas Chapitre 25. Fenêtres graphiques et Tkinter
Lignes 4 à 6. Comme montré dans la rubrique Construire une application Tkinter avec une classe, notre classe
AppliCanevas hérite de la classe générale tk.Tk et la fenêtre Tk se retrouve dans la variable self.
Ligne 7. On crée un attribut de la classe self.size qui contiendra la taille (hauteur et largeur) du canvas. On
rappelle que cet attribut sera visible dans l’ensemble de la classe puisqu’il est « accroché » à celle-ci par le self.
Ligne 8. On lance la méthode .creer_widgets() (qui est elle aussi « accrochée » à la classe par le self).
Lignes 12 à 14. On crée un widget canvas en instanciant la classe tk.Canvas. On place ensuite le canvas dans la
fenêtre avec la méthode .pack() en lui précisant où le placer avec la variable Tkinter tk.LEFT.
Lignes 15 à 24. On crée des widgets boutons et on les place dans la fenêtre. À noter que chacun de ces widgets appelle
une méthode différente, dont deux que nous avons créées dans la classe (.dessine_cercle() et .dessine_lignes()).
Ligne 26 à 28. Cette méthode renvoie une couleur au hasard sous forme de chaîne de caractères.
Lignes 30 à 40. On définit deux méthodes qui vont dessiner des paquets de 20 cercles (cas spécial d’une ellipse) ou 20
lignes aléatoires. Lors de la création de ces cercles et lignes, on ne les récupère pas dans une variable car on ne souhaite
ni les réutiliser ni changer leurs propriétés par la suite. Vous pourrez noter ici l’avantage de programmer avec une classe,
le canvas est directement accessible dans n’importe quelle méthode de la classe avec self.canv (pas besoin de le passer
en argument ou de créer une variable globale).
Dans ce dernier exemple, nous allons illustrer la puissance du widget canvas en vous montrant que l’on peut animer
les objets se trouvant à l’intérieur. Nous allons également découvrir une technique intéressante, à savoir, comment «
intercepter » des clics de souris générés ou des touches pressées par l’utilisateur. L’application consiste en une « baballe
» qui se déplace dans la fenêtre et dont on contrôle les propriétés à la souris (cf. figure 25.6). Vous pouvez télécharger
le script ici 11 .
11. https://python.sdv.u-paris.fr/data-files/tk_baballe.py
Lignes 19 à 23. Les coordonnées de la baballe, ses pas de déplacement, et sa taille sont créés en tant qu’attributs de
notre classe. Ainsi ils seront visibles partout dans la classe.
Lignes 25 à 31. Le canvas est ensuite créé et placé dans la fenêtre, puis on définit notre fameuse baballe. À noter,
les coordonnées self.x et self.y de la baballe représentent en fait son côté « nord-ouest » (en haut à gauche, voir le
point (x0 , y0 ) dans la documentation officielle 12 ).
Lignes 33 à 35. Jusqu’à maintenant, nous avons utilisé des événements provenant de clics sur des boutons. Ici, on va
« intercepter » des événements générés par des clics de souris sur le canvas et les lier à une fonction / méthode (comme
nous l’avions fait pour les clics sur des boutons avec l’option command=...). La méthode pour faire cela est .bind(),
voilà pourquoi on parle de event binding en anglais. Cette méthode prend en argument le type d’événement à capturer
en tant que chaîne de caractères avec un format spécial : par exemple "<Button-1>" correspond à un clic gauche de la
souris (de même "<Button-2>" et "<Button-3>" correspondent aux clics central et droit respectivement). Le deuxième
argument de la méthode .bind() est une méthode / fonction callback à appeler lors de la survenue de l’événement
(comme pour les clics de bouton, vous vous souvenez ? On l’appelle donc sans parenthèses ni arguments). On notera
que tous ces événements sont liés à des clics sur le canvas, mais il est possible de capturer des événements de souris sur
d’autres types de widgets.
Ligne 36. De même, on peut « intercepter » un événement lié à l’appui sur une touche, ici la touche Esc.
Ligne 38. La méthode .move() est appelée, ainsi l’animation démarrera dès l’exécution du constructeur, donc peu
après l’instanciation de notre application (Ligne 86).
Lignes 40 à 58. On définit une méthode .move() qui va gérer le déplacement de la baballe avec des chocs élastiques
sur les parois (et faire en sorte qu’elle ne sorte pas du canvas).
Lignes 55 et 56. On utilise la méthode .coords() de la classe Canvas, qui « met à jour » les coordonnées de
n’importe quel objet dessiné dans le canvas (c’est-à-dire que cela déplace l’objet).
Ligne 58. Ici, on utilise une autre méthode spécifique des objets Tkinter. La méthode .after() rappelle une autre
méthode ou fonction (second argument) après un certain laps de temps (ici 50 ms, passé en premier argument). Ainsi la
méthode .move() se rappelle elle-même, un peu comme une fonction récursive. Toutefois, ce n’est pas une vraie fonction
récursive comme celle vue dans le chapitre 13 (exemple du calcul de factorielle), car Python ne conserve pas l’état de la
fonction lors de l’appel de .after(). C’est comme si on avait un return, tout l’espace mémoire alloué à la méthode
.move() est détruit lorsque Python rencontre la méthode .after(). On obtiendrait un résultat similaire avec la boucle
suivante :
1 import time
2
3 ...
4
5 while True:
6 move()
7 time.sleep(0.05) # attendre 50 ms
Le temps de 50 ms donne 20 images (ou clichés) par seconde. Si vous diminuez ce temps, vous aurez plus d’images
par secondes et donc un « film » plus fluide.
Ligne 60 à 66. On définit la méthode .boom() de notre classe qui on se souvient est appelée lors d’un événement clic
central sur le canvas. Vous noterez qu’outre le self, cette fonction prend un autre argument que nous avons nommé
ici mclick. Il s’agit d’un objet spécial géré par Tkinter qui va nous donner des informations sur l’événement généré par
l’utilisateur. Dans les lignes 62 et 63, cet objet mclick récupère les coordonnées où le clic a eu lieu grâce aux attributs
mclick.x et mclick.y. Ces coordonnées sont réaffectées à la baballe pour la faire repartir de l’endroit du clic. Nous
créons ensuite un petit texte dans le canevas et affectons des valeurs aléatoires aux variables de déplacement pour faire
repartir la baballe dans une direction aléatoire.
Lignes 68 à 78. On a ici deux méthodes .incr() et .decr() appelées lors d’un clic gauche ou droit. Deux choses
sont à noter : i) l’attribut self.size est modifié dans les deux fonctions, mais le changement de diamètre de la boule ne
sera effectif dans le canvas que lors de la prochaine exécution de l’instruction self.canv.coords() (dans la méthode
.move()) ; ii) de même que pour la méthode .boom(), ces deux méthodes prennent un argument après le self (lclick
ou rclick) récupérant ainsi des informations sur l’événement de l’utilisateur. Même si on ne s’en sert pas, cet argument
après le self est obligatoire car il est imposé par la méthode .bind().
Lignes 80 à 82. Cette méthode quitte l’application lorsque l’utilisateur fait un clic sur la touche Esc.
12. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/create_oval.html
Figure 25.6 – Exemple de canvas animé à deux instants de l’exécution (panneau de gauche : au moment où on effectue
un clic central ; panneau de droite : après avoir effectué plusieurs clics gauches).
Il existe de nombreux autres événements que l’on peut capturer et lier à des méthodes / fonctions callback. Vous
trouverez une liste complète ici 13 .
13. http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
14. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
page Universal widget methods 15 vous donnera une vue d’ensemble des différentes méthodes associées à chaque widget.
Il existe également une extension de Tkinter nommée ttk, réimplémentant la plupart des widgets de base de Tkinter
et qui en propose de nouveaux (Combobox, Notebook, Progressbar, Separator, Sizegrip et Treeview). Typiquement, si
vous utilisez ttk, nous vous conseillons d’utiliser les widgets ttk en priorité, et pour ceux qui n’existent pas dans ttk, ceux
de Tkinter (comme Canvas qui n’existe que dans Tkinter). Vous pouvez importer le sous-module ttk de cette manière :
import tkinter.ttk as ttk.
Vous pourrez alors utiliser les classes de widget de ttk (par exemple ttk.Button, etc.). Si vous souhaitez importer
ttk et Tkinter, il suffit d’utiliser ces deux lignes :
1 import tkinter as tk
2 import tkinter.ttk as ttk
Ainsi vous pourrez utiliser des widgets de Tkinter et de ttk en même temps.
Pour plus d’informations, vous pouvez consulter la documentation officielle de Python 16 , ainsi que la documentation
très complète du site du MNT 17 .
Conseil
Si vous êtes débutant, vous pouvez sauter cette rubrique.
Lorsque vous souhaitez mettre un jour un widget avec une certaine valeur (par exemple le texte d’un label), vous ne
pouvez pas utiliser une variable Python ordinaire, il faudra utiliser une variable Tkinter dite de contrôle. Par exemple,
si on souhaitait afficher les coordonnées de notre baballe (cf. rubrique précédente) dans un label, et que cet affichage
se mette à jour au fur et à mesure des mouvements de la baballe, il faudrait utiliser des variables de contrôle. On peut
créer de telles variables avec les classes tk.StringVar pour les chaînes de caractères, tk.DoubleVar pour les floats, et
tk.IntVar pour les entiers. Une fois créée, par exemple avec l’instruction var = tk.StringVar(), on peut modifier
la valeur d’une variable de contrôle avec la méthode var.set(nouvelle_valeur) : ceci mettra à jour tous les widgets
utilisant cette variable var. Il existe aussi la méthode var.get() qui récupère la valeur actuelle contenue dans var.
Enfin, il faudra lors de la création du label utiliser l’option textvariable= avec votre variable de contrôle (par exemple
tk.Label(..., textvariable=var, ...)) pour que cela soit fonctionnel.
À nouveau, vous trouverez une documentation précise sur le site du MNT 18 .
Dans les exemples montrés dans ce chapitre, nous avons systématiquement utiliser la méthode .pack() pour placer
les widgets. Cette méthode très simple et directe « empaquette » les widgets les uns contre les autres et redimensionne
la fenêtre automatiquement. Avec l’option side= et les variables tk.BOTTOM, tk.LEFT, tk.TOP et tk.RIGHT on place
facilement les widgets les uns par rapport aux autres. Toutefois, la méthode .pack() peut parfois présenter des limites,
il existe alors deux autres alternatives.
La méthode .grid() permet, grâce à l’utilisation d’une grille, un placement mieux contrôlé des différents widgets. La
méthode .place() place enfin les widgets en utilisant les coordonnées de la fenêtre principale. Nous ne développerons
pas plus ces méthodes, mais voici de la documentation supplémentaire en accès libre :
• .pack() 19 ;
15. https://infohost.nmt.edu/tcc/help/pubs/tkinter/web/universal.html
16. https://docs.python.org/3/library/tkinter.ttk.html
17. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/ttk.html
18. https://infohost.nmt.edu/tcc/help/pubs/tkinter/web/control-variables.html
19. http://effbot.org/tkinterbook/pack.htm
• .grid() 20 , 21 ;
• .place() 22 .
Lignes 17 à 21. Commentons d’abord le programme principal : ici on crée la fenêtre principale dans l’instance racine
puis on instancie notre classe en passant racine en argument.
Lignes 4 et 5. Ici réside la principale différence par rapport à ce que nous vous avons montré dans ce chapitre : en
ligne 4 on passe l’argument racine à notre constructeur, puis en ligne 5 on passe ce même argument racine lors de
l’appel du constructeur de la classe tk.Frame (ce qui était inutile lorsqu’on héritait de la classe Tk).
Ligne 6. L’argument racine passé à la méthode .__init__() est finalement une variable locale. Comme il s’agit
de l’instance de notre fenêtre principale à passer à tous nos widgets, il faut qu’elle soit visible dans toute la classe. La
variable self.racine est ainsi créée afin d’être réutilisée dans d’autres méthodes.
Vous pourrez vous posez la question : « Pourquoi en ligne 4 l’argument par mot-clé racine=None prend la valeur
None par défaut ? ». Et bien, c’est parce que notre classe Application peut s’appeler sans passer d’instance de fenêtre
Tk. Voici un exemple avec les lignes qui changent seulement (tout le reste est identique au code précédent) :
1 [...]
2 class Application(tk.Frame):
3 def __init__(self, racine=None):
4 tk.Frame.__init__(self)
5 self.racine = racine
6 [...]
7 [...]
8 if __name__ == "__main__":
9 app = Application()
10 app.mainloop()
Dans un tel cas, l’argument racine prend la valeur par défaut None lorsque la méthode .__init__() de notre
classe est exécutée. L’appel au constructeur de la classe Frame en ligne 4 instancie automatiquement une fenêtre Tk (car
cela est strictement obligatoire). Dans la suite du programme, cette instance de la fenêtre principale sera self.racine
et il n’y aura pas de changement par rapport à la version précédente. Cette méthode reste toutefois peu intuitive car
cette instance de la fenêtre principale self.racine vaut finalement None !
20. http://effbot.org/tkinterbook/grid.htm
21. https://infohost.nmt.edu/tcc/help/pubs/tkinter/web/grid.html
22. http://effbot.org/tkinterbook/place.htm
Hériter de la classe Frame ou de la classe Tk sont deux manières tout à fait valides pour créer des applications
Tkinter. Le choix de l’une ou de l’autre relève plus de préférences que l’on acquiert en pratiquant, voire de convictions
philosophiques sur la manière de programmer. Toutefois, nous pensons qu’hériter de la classe tk.Tk est une manière
plus générale et plus compacte : tout ce qui concerne le fenêtrage Tkinter se situera dans votre classe Application, et le
programme principal n’aura qu’à instancier l’application et à lancer le gestionnaire d’événements (les choses seront ainsi
mieux « partitionnées »). C’est donc la méthode que nous vous recommandons.
Si vous allez chercher de la documentation supplémentaire sur Tkinter, il se peut que vous tombiez sur ce style de
syntaxe lorsque vous créez votre classe contenant l’application graphique :
1 class MonApplication(tk.Tk):
2 def __init__(self, *args, **kwargs):
3 tk.Tk.__init__(self, *args, **kwargs)
4 # ici débute la construction de votre appli
5 [...]
6
7 # programme principal
8 if __name__ == "__main__":
9 [...]
10 app = MonApplication()
11 [...]
Les arguments *args et **kwargs récupérent facilement tous les arguments « positionnels » et « par mot-clé ». Pour
plus de détails sur comment *args et **kwargs fonctionnent, reportez-vous au chapitre 26 Remarques complémentaires
(en ligne).
Dans l’exemple ci-dessus, *args et **kwargs sont inutiles car lors de l’instanciation de notre application, on ne passe
aucun argument : app = MonApplication(). Toutefois, on pourrait être intéressé à récupérer des arguments passés
au constructeur, par exemple :
app = MonApplication(arg1, arg2, option1=val1, option2=val2)
Ainsi certains auteurs laissent toujours ces *args et **kwargs au cas où on en ait besoin dans le futur. Cela est
bien utile lorsqu’on distribue notre classe à la communauté et que l’on souhaite que les futurs utilisateurs puissent passer
des arguments Tkinter au constructeur de notre classe.
Toutefois, même si cela « ne coûte rien », nous vous recommandons de ne pas mettre ces *args et **kwargs si vous
n’en avez pas besoin, comme nous vous l’avons montré dans les exemples de ce chapitre. Rappelons nous de la PEP 20
(voir le chapitre 16 Bonnes Pratiques en programmation Python), les assertions « Simple is better than complex » ou «
Sparse is better than dense » nous suggèrent qu’il est inutile d’ajouter des choses dont on ne se sert pas.
Tkinter est parfois surprenant. Dans le code suivant, on pourrait penser que celui-ci n’est pas fonctionnel :
1 >>> import tkinter as tk
2 >>> bouton = tk.Button(text="Quitter")
3 >>> bouton.pack()
Pour autant, cela fonctionne et on voit un bouton apparaître ! En fait, Tkinter va automatiquement instancier la
fenêtre principale, si bien qu’il n’est pas obligatoire de passer cette instance en argument d’un widget. À ce moment, on
peut se demander où est passé cette instance. Heureusement, Tkinter garde toujours une filiation des widgets avec les
attributs .master et .children :
1 >>> racine = bouton.master
2 >>> racine
3 <tkinter.Tk object .>
4 >>> racine.children
5 {'!button': <tkinter.Button object .!button>}
6 >>> bouton["command"] = racine.destroy
Conseil
Même si cela est possible, nous vous conseillons de systématiquement préciser l’instance de la fenêtre principale lors
de la création de vos widgets.
Comme vu dans nos exemples ci-dessus, les fonctions callback ne prennent pas d’arguments ce qui peut se révéler
parfois limitant. Il existe toutefois une astuce qui utilise les fonctions lambda ; nous expliquons brièvement les fonctions
lambda dans le chapitre 26 Remarques complémentaires (en ligne). Toutefois, nous ne développons pas leur utilisation
avec Tkinter et les fonctions callback car cela dépasse le cadre de cet ouvrage. Pour de plus amples explications sur cette
question, vous pouvez consulter le site pythonprogramming 23 et le livre de Gérard Swinnen 24 .
Dans ce chapitre d’introduction, nous vous avons montré des GUI simples avec une seule page. Toutefois, si votre
projet se complexifie, il se peut que vous ayez besoin de créer plusieurs fenêtre différentes. Le livre de Gérard Swinnen 25
et le site pythonprogramming 26 sont des bonnes sources pour commencer et voir concrètement comment faire cela.
25.8 Exercices
Conseil
Pour ces exercices, créez des scripts puis exécutez-les dans un shell. Nous vous recommandons de concevoir une classe
pour chaque exercice.
25.8.2 Horloge
Sur la base de l’application précédente, faites une application qui affiche l’heure dans un label en se mettant à jour
sur l’heure de l’ordinateur une fois par seconde. Vous concevrez une méthode .mise_a_jour_heure() qui met à jour
l’heure dans le label et qui se rappelle elle-même toutes les secondes (n’oubliez pas la méthode .after(), cf. rubrique
Un canvas animé dans une classe ci-dessus). Pour cette mise à jour, vous pourrez utiliser la méthode .configure(), par
exemple : self.label.configure(text=heure) où heure est une chaîne de caractères représentant l’heure actuelle.
35. https://fr.wikipedia.org/wiki/Triangle_de_Sierpi%C5%84ski
36. https://fr.wikipedia.org/wiki/Jeu_du_chaos
Le rendu final attendu est montré dans la figure 25.8. On utilisera un canevas de 400x400 pixels. Il y a aura un
bouton « Quitter » et un bouton « Launch ! » qui calculera et affichera 10000 points supplémentaires dans le triangle de
Sierpinski.
Améliorer l’application précédente en proposant une liste de choix supplémentaire demandant à l’utilisateur de choisir
le nombre de sommets (de 3 à 10). Le programme calculera automatiquement la position des sommets. Pour prendre en
main le widget Listbox, voici un code minimal qui pourra vous aider. Celui-ci contient une Listbox et permet d’afficher
dans le terminal l’élément sélectionné. Nous vous conseillons de bien étudier le code ci-dessous et d’avoir résolu l’exercice
précédent avant de vous lancer !
1 import tkinter as tk
2
3 class MaListBox(tk.Tk):
4 def __init__(self):
5 # Instanciation fenêtre Tk.
6 tk.Tk.__init__(self)
7 self.listbox = tk.Listbox(self, height=10, width=4)
8 self.listbox.pack()
9 # Ajout des items à la listbox (entiers).
10 for i in range(1, 10+1):
11 # Utilisation de ma méthode .insert(index, element)
12 # Ajout de l'entier i (tk.END signifie en dernier).
13 self.listbox.insert(tk.END, i)
14 # Selection du premier élément de listbox.
15 self.listbox.select_set(0)
16 # Liaison d'une méthode quand clic sur listbox.
17 self.listbox.bind("<<ListboxSelect>>", self.clic_listbox)
18
19 def clic_listbox(self, event):
20 # Récupération du widget à partir de l'objet event.
21 widget = event.widget
22 # Récupération du choix sélectionné dans la listbox (tuple).
23 # Par exemple renvoie `(5,)` si on a cliqué sur `5`.
24 selection = widget.curselection()
25 # Récupération du nombre sélectionné (déjà un entier).
26 choix_select = widget.get(selection[0])
27 # Affichage.
28 print(f"Le choix sélectionné est {choix_select}, "
29 f"son type est {type(choix_select)}")
30
31
32
33 if __name__ == "__main__":
34 app = MaListBox()
35 app.title("MaListBox")
36 app.mainloop()
Remarques complémentaires
Dans ce chapitre, nous présentons un certain nombre de points en vrac qui ne rentraient pas forcément dans les
autres chapitres ou qui étaient trop avancés au moment où les chapitres étaient abordés. Outre quelques points mineurs,
nous abordons les grandes différences entre Python 2 et Python 3, les anciennes méthodes de formatage des chaînes
de caractères, les fonctions lambda, les itérateurs, la gestion des exceptions, les passage d’arguments avancés dans les
fonctions et les décorateurs. Certains de ces points sont réellement avancés et nécessiteront d’avoir assimilé d’autres
notions avant de les aborder.
Par contre, en Python 3, print() est une fonction. Ainsi, si vous n’utilisez pas de parenthèse, Python vous renverra
une erreur :
1 >>> print 12
2 File "<stdin>", line 1
3 print 12
4 ^
5 SyntaxError: Missing parentheses in call to 'print'
320
26.1. Différences Python 2 et Python 3 Chapitre 26. Remarques complémentaires
1 >>> 3 / 4
2 0.75
Faites très attention à cet aspect si vous programmez encore en Python 2, c’est une source d’erreur récurrente.
Comme on a vu au chapitre 5 Boucles et comparaisons, ces objets sont itérables produisant successivement les nombres
0, puis 1 puis 2 sur notre exemple :
1 >>> for i in range(3):
2 ... print(i)
3 ...
4 0
5 1
6 2
La création de liste avec range() était pratique, mais très peu efficace en mémoire lorsque l’argument donné à
range() était un grand nombre.
D’ailleurs la fonction xrange() est disponible en Python 2 pour faire la même chose que la fonction range() en
Python 3. Attention, ne vous mélangez pas les pinceaux !
1 >>> range(3)
2 [0, 1, 2]
3 >>> xrange(3)
4 xrange(3)
Remarque
Pour générer une liste d’entiers avec la fonction range() en Python 3, vous avez vu dans le chapitre 4 Listes qu’il
suffisait de l’associer avec la fonction list(). Par exemple :
1 >>> list(range(10))
2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Conseil
Pour une comparaison exhaustive entre xrange() en Python 2 et range() en Python 3, vous pouvez lire ce très
bon article 1 tiré du blog de Trey Hunner.
1 >>> # Python3.
2 >>> zip(range(4), range(10, 14))
3 <zip object at 0x7f11423ffd80>
Vous pouvez lire la rubrique Itérables, itérateurs, générateurs et module itertools un peu plus bas dans ce chapitre
pour en savoir plus sur les itérateurs.
Pour éviter ce genre de désagrément, ajoutez la ligne suivante en tout début de votre script :
1 # coding: utf-8
Si vous utilisez un shebang (voir rubrique précédente), il faudra mettre la ligne # coding: utf-8 sur la deuxième
ligne (la position est importante 2 ) de votre script :
1 #! /usr/bin/env python
2 # coding: utf-8
Remarque
L’encodage utf-8 peut aussi être déclaré de cette manière :
1 # -*- coding: utf-8 -*-
La deuxième manière avec la méthode .format() est encore utilisée et reste tout à fait valide. Elle est clairement
plus puissante et évite un certain nombre de désagréments par rapport à l’opérateur %. Vous la croiserez sans doute
de temps en temps dans des programmes et ouvrages plus ou moins récents. Heureusement elle a un fonctionnement
relativement proche des f-strings, donc vous ne serez pas totalement perdus !
Enfin, nous indiquons à la fin de cette rubrique nos conseils sur quelle méthode utiliser.
26.2.1 L’opérateur %
On a vu avec les entiers que l’opérateur % ou modulo renvoyait le reste d’une division entière. Cet opérateur existe
aussi pour les chaînes de caractères mais il met en place l’écriture formatée. En voici un exemple :
1 >>> x = 32
2 >>> nom = "John"
3 >>> print("%s a %d ans" % (nom, x))
4 John a 32 ans
5 >>> nb_G = 4500
6 >>> nb_C = 2575
7 >>> prop_GC = (nb_G + nb_C)/14800
8 >>> print("On a %d G et %d C -> prop GC = %.2f" % (nb_G, nb_C, prop_GC))
9 On a 4500 G et 2575 C -> prop GC = 0.48
La syntaxe est légèrement différente. Le symbole % est d’abord appelé dans la chaîne de caractères (dans l’exemple
ci-dessus %d, %d et %.2f) pour :
• Désigner l’endroit où sera placée la variable dans la chaîne de caractères.
• Préciser le type de variable à formater, d pour un entier (i fonctionne également) ou f pour un float.
• Éventuellement pour indiquer le format voulu. Ici .2 signifie une précision de deux décimales.
Le signe % est rappelé une seconde fois (% (nb_G, nb_C, prop_GC)) pour indiquer les variables à formater.
• Dans la chaîne de caractères, les accolades vides {} précisent l’endroit où le contenu de la variable doit être inséré.
• Juste après la chaîne de caractères, l’instruction .format(nom, x) fournit la liste des variables à insérer, d’abord
la variable nom puis la variable x.
• On peut éventuellement préciser le formatage en mettant un caractère deux-points : puis par exemple ici .2f qui
signifie deux chiffres après la virgule.
• La méthode .format() agit sur la chaîne de caractères à laquelle elle est attachée par le point.
Tout ce que nous avons vu avec les f-strings sur la manière de formater l’affichage d’une variable (après les :
au sein des accolades) est identique avec la méthode .format(). Par exemple {:.2f}, {:0>6d}, {:.6e}, etc.,
fonctionneront de la même manière. La différence notable est qu’on ne met pas directement le nom de la variable au
sein des accolades. Comme pour l’opérateur %, c’est l’emplacement dans les arguments passés à la méthode .format()
qui dicte quelle variable doit être remplacée. Par exemple, dans "{} {} {}".format(bidule, machin, truc), les
premières accolades remplaceront la variable bidule, les deuxièmes la variable machin, les troisièmes la variable truc.
Le formatage avec la méthode .format() se rapproche de la syntaxe des f-strings (accolades, deux-points), mais
présente l’inconvénient – comme avec l’opérateur % – de devoir mettre la liste des variables tout à la fin, alourdissant
ainsi la syntaxe. En effet, dans l’exemple avec la proportion de GC, la ligne équivalente avec une f-string apparait tout
de même plus simple à lire :
1 >>> print(f"On a {nb_G} G et {nb_C} C -> prop GC = {prop_GC:.2f}")
2 On a 4500 G et 2575 C -> prop GC = 0.48
Conseil
Pour conclure, ces deux anciennes façons de formater une chaîne de caractères avec l’opérateur % ou la méthode
.format() vous sont présentées à titre d’information. La première avec l’opérateur % est clairement déconseillée. La
deuxième avec la méthode .format() est encore tout à fait valable. Si vous débutez Python, nous vous conseillons
fortement d’apprendre et d’utiliser les f-strings. C’est ce que vous rencontrerez dans la suite de ce cours. Si vous connaissez
déjà Python et que vous utilisez la méthode .format(), nous vous conseillons de passer aux f-strings. Depuis que nous
les avons découvertes, aucun retour n’est envisageable pour nous tant elles sont puissantes et plus claires à utiliser !
Définition
Une fonction lambda est une fonction qui s’écrit sur une ligne. En Python, il s’agit du moyen d’implémenter une
fonction anonyme 6 (en anglais anonymous function), c’est-à-dire, une fonction qui est la plupart du temps non reliée
à un nom (d’où le terme anonyme). Une fonction lambda s’utilise en général à la volée. On parle aussi d’expressions
lambda utilisées pour fabriquer des fonctions lambda.
• Ligne 1. On a ici une expression lambda typique définissant une fonction lambda. La syntaxe est (dans l’ordre) :
le mot-clé (statement) lambda, zero ou un ou plusieurs argument(s), deux-points, une expression utilisant ou pas
les arguments.
• Ligne 2. Python confirme qu’il s’agit d’une fonction.
• Lignes 3 à 6. Pour utiliser la fonction lambda, pour l’instant, on la met entre parenthèses et on utilise un autre
jeu de parenthèses pour l’appeler et éventuellement passer des arguments.
Attention
Une fonction lambda ne s’écrit que sur une ligne. Si vous essayez de l’écrire sur plusieurs lignes, Python lèvera une
exception SyntaxError: invalid syntax.
Comme pour les fonctions classiques, le nombre d’arguments est variable et doit être cohérent avec l’appel :
4. https://realpython.com/python-string-formatting
5. https://realpython.com/python-f-strings/
6. https://en.m.wikipedia.org/wiki/Anonymous_function
Dans les deux cas l’appel est identique, mais la fonction lambda requière une syntaxe à une ligne lors de sa définition.
Même si on peut le faire, les dévelopeurs déconseillent toutefois d’assigner une fonction lambda à un nom dans
la PEP8 7 . Une des raisons est que si une erreur est générée, l’interpréteur ne renvoie pas le numéro de ligne dans la
Traceback :
1 >>> inverse = lambda: 1/0
2 >>> inverse()
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 File "<stdin>", line 1, in <lambda>
6 ZeroDivisionError: division by zero
Ligne 5. L’indication de la ligne pour l’erreur dans la fonction lambda (line 1) correspond à celle de l’appel et non
pas de la définition.
Alors qu’avec une fonction classique :
>>> def inverse():
... return 1/0
...
>>> inverse()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in inverse
ZeroDivisionError: division by zero
Ligne 5. Cette fois-ci, la Traceback indique bien la bonne ligne (line 2) dans la fonction.
7. https://peps.python.org/pep-0008/
Conseil
Pour cette raison, n’assignez pas une fonction lambda à un nom, mais utilisez la seulement à la volée (voir ci-dessous).
Une autre raison est que cela peut nuire à la lisibilité. Si une fonction lambda s’écrit en une ligne, c’est bien pour qu’on
puisse la lire quand elle est utilisée.
Lignes 2 et 4. La fonction lambda permet de lire clairement quelle opération on réalise plutôt que de se référer à
une fonction classique se trouvant à un autre endroit. Ainsi, cela améliore la lisibilité.
Cela vous rappelle peut-être ce qu’on a rencontré avec les objets NumPy et les opérations vectorielles :
1 >>> import numpy as np
2 >>> array1 = np.arange(10)
3 >>> array1
4 array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
5 >>> array1 * 2
6 array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
7 >>>
8 >>> liste1 = list(range(10))
9 >>> liste1
10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
11 >>> list(map(lambda x: x*2, liste1))
12 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Ligne 5. Nativement, l’opération array1 * 2 se fait vectoriellement (élément par élément) avec un array NumPy.
Ligne 11. La fonction map() applique l’opération * 2 de la lambda sur tous les éléments de la liste. Ainsi, on obtient
le même effet que sur l’array NumPy.
Bien que cela s’avère pratique, nous verrons dans la rubrique suivante sur les itérateurs qu’il existe une syntaxe plus
Pythonique avec les listes de compréhensions et les expressions génératrices.
La deuxième grande utilité des fonctions lambda concerne leur utilisation pour faire des tris puissants. Dans le chapitre
14 Conteneurs, nous avions vu les tris de dictionnaires par valeurs :
1 >>> dico = {"a": 15, "b": 5, "c":20}
2 >>> sorted(dico, key=dico.get)
3 ['b', 'a', 'c']
8. https://fr.wikipedia.org/wiki/Programmation_fonctionnelle
Ligne 2. On passe à l’argument par mot-clé key la callback dico.get (cette méthode renvoie initialement les valeurs
d’un dictionnaire). Cela permet finalement de trier par ce que renvoie cette méthode, à savoir les valeurs.
Cet argument par mot-clé peut prendre d’autres callback, par exemple len. Dans l’exemple suivant, on prend 10 mots
au hasard dans le dictionnaire et on les trie par leur longueur :
1 >>> mots = ['étudier', 'pie-grièche', 'figurerait', 'retraitait', 'allégerais',
2 'distribuent', 'affilierait', 'ramassa', 'galettes', 'connu']
3 >>> sorted(mots, key=len)
4 ['connu', 'étudier', 'ramassa', 'galettes', 'figurerait', 'retraitait',
5 'allégerais', 'pie-grièche', 'distribuent', 'affilierait']
Bien sûr, on peut utiliser aussi une fonction lambda. Celle-ci va nous permettre de passer une fonction de tri à la
volée au moment de l’appel de la fonction sorted(). Par exemple, si on reprend le même exemple que le dictionnaire
mais sous forme d’une liste de tuples :
1 liste1 = [('a', 15), ('b', 5), ('c', 20)]
Comment trier en fonction du deuxième élément de chaque tuple ? Réponse, avec une fonction lambda bien sûr !
Regardez :
1 >>> sorted(liste1, key=lambda x: x[1])
2 [('b', 5), ('a', 15), ('c', 20)]
Autre exemple, on souhaite trier une liste d’entiers aléatoires non pas par leur valeur, mais par le résultat de la fonction
x**2 :
1 >>> liste1 = [-5, 2, 5, 8, 6, 3, -9, 4, -10, 2]
2 >>> sorted(liste1, key=lambda x: x**2)
3 [2, 2, 3, 4, -5, 5, 6, 8, -9, -10]
Pour comprendre comment le tri est opéré en ligne 3, voici la liste initiale et une autre liste avec les carrés :
1 >>> liste1
2 [-5, 2, 5, 8, 6, 3, -9, 4, -10, 2]
3 >>> [x**2 for x in liste1]
4 [25, 4, 25, 64, 36, 9, 81, 16, 100, 4]
Le tri de liste1 ci-dessus est bien effectué en fonction des valeurs montrées en ligne 4.
L’agument par mot-clé key existe dans d’autres fonctions ou méthodes. Bien sûr il existe dans la méthode .sort()
qui trie les listes sur place. Mais aussi, dans les fonctions natives min() et max(). Enfin, on le croise dans la fonction
groupby() du module itertools (voir rubrique suivante). Dans tous ces cas, on peut utiliser une fonction lambda pour
l’argument key.
Par exemple, dans le code suivant :
1 >>> liste = ['baccalauréat', 'abaissera', 'barricadé', 'zouave', 'tabac',
2 'typographie', 'dactylographes', 'éclipse']
3 >>> min(liste)
4 'abaissera'
5 >>> max(liste)
6 'éclipse'
7 >>> min(liste, key=lambda x: x.count("a"))
8 'éclipse'
9 >>> max(liste, key=lambda x: x.count("a"))
10 'baccalauréat'
Vous l’aurez sans doute compris, avec notre fonction lambda, nous avons trié en fonction du nombre de lettres a dans
chaque mot !
26.3.4 Conclusion
Nous avons vu que les fonctions lambda permettaient des définitions de fonction rapidement sur une ligne. Il faut
absolument éviter de les assigner à un nom. Elles ont toute leur utilité lorsqu’on les utilise avec map() pour appliquer
une opération à tous les éléments d’un conteneur, ou pour des tris puissants avec sorted().
Conseil
Pour aller plus loin, vous pouvez consulter ces quelques articles : Dataquest 9 , Trey Hunner 10 , RealPython 11 et Dan
Bader 12 .
Définition
Un itérateur est un objet Python qui permet d’itérer sur une suite de valeurs avec la fonction next() jusqu’à temps
qu’elles soient épuisées. Si on itère sur une partie des valeurs seulement, l’itérateur garde en mémoire là où il s’est arrêté.
Si on le resollicite avec un next() il repartira de l’élément suivant. Une règle est toutefois importante : les valeurs ne
peuvent être parcourues qu’une seule fois.
On peut générer un itérateur avec la fonction iter() à partir de n’importe quel conteneur :
1 >>> animaux = ["chien", "chat", "souris"]
2 >>> iterateur = iter(animaux)
3 >>> iterateur
4 <list_iterator object at 0x7f917e907a30>
Une fois l’itérateur généré, on peut accéder à l’élément suivant avec la fonction next() :
1 >>> next(iterateur)
2 'chien'
3 >>> next(iterateur)
4 'chat'
5 >>> next(iterateur)
6 'souris'
7 >>> next(iterateur)
8 Traceback (most recent call last):
9 File "<stdin>", line 1, in <module>
10 StopIteration
Quand il n’y a plus de valeurs sur lesquelles itérer, la fonction next() lève une exception StopIteration. En général,
on n’utilisera pas les itérateurs de cette manière, mais plutôt avec une boucle for ce qui évitera cette levée d’exception :
9. https://www.dataquest.io/blog/tutorial-lambda-functions-in-python/
10. https://www.pythonmorsels.com/lambda-expressions/
11. https://realpython.com/python-lambda/
12. https://dbader.org/blog/python-lambda-functions
On peut transformer un objet de type itérateur en un objet de type séquentiel, par exemple en tuple :
1 >>> iterateur = iter(animaux)
2 >>> tuple(iterateur)
3 ('chien', 'chat', 'souris')
Le point important est qu’une fois toutes les valeurs parcourues, l’itérateur est épuisé et ne renvoie plus rien :
1 >>> iterateur = iter(animaux)
2 >>> tuple(iterateur)
3 ('chien', 'chat', 'souris')
4 >>> tuple(iterateur)
5 ()
Ainsi, on ne pourra parcourir l’ensemble des valeurs d’un itérateur qu’une fois.
À ce stade, on pourrait se dire que la construction d’un itérateur à partir d’une liste ci-dessus est inutile puisqu’on
peut itérer directement sur la liste avec une boucle for. Toutefois, lorsqu’on réalise une telle boucle, il y a un itérateur
qui est généré implicitement même si on ne s’en rend pas compte. Pour prouver cela, essayons la fonction next() avec
une liste :
1 >>> next(animaux)
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 TypeError: 'list' object is not an iterator
Ceci n’est pas possible car une liste n’est pas un itérateur. Alors pourquoi peut-on itérer dessus avec une boucle for ?
Et bien, c’est parce que l’objet de type liste possède une méthode dunder spéciale nommée .__iter__(). Celle-ci génère
un itérateur à partir d’elle-même permettant d’itérer sur ses éléments. L’objet itérateur ainsi généré possèdera une autre
méthode dunder spéciale .__next__() permettant de passer à l’élément suivant lorsqu’on itère dessus.
Remarque
Pour rappel, les méthodes dunder des classes ont été définies dans la rubrique 24.2.2 Méthodes magiques ou dunder
methods du chapitre 24 Avoir plus la classe avec les objets.
Lorsque vous construirez votre propre objet itérable, il faudra écrire une classe contenant ces deux méthodes dunder
et l’objet sera de facto un itérateur et itérable. Pour vous donnez une première idée, voici une classe minimale créant un
objet itérateur sur les lettres de l’alphabet :
1 class Alphabet:
2 def __init__(self):
3 self.current = 97 # ASCII code for a.
4
5 def __iter__(self):
6 return self
7
8 def __next__(self):
9 if self.current > 122: # ASCII code for z.
10 raise StopIteration
11 letter = chr(self.current)
12 self.current += 1
13 return letter
• Ligne 3. Dans le constructeur, on crée un attribut d’instance self.current qui gardera l’état de l’itérateur. On
l’initialise à 97 correspondant au code ASCII de la lettre a.
• Lignes 5 et 6. La méthode dunder .__iter__() est très simple à écrire. Elle renvoie self correspondant à
l’itérateur lui-même. Si cette méthode n’est pas présente, l’objet n’est pas itérable.
• Lignes 8 à 13. La méthode dunder .__next__() s’occupe de passer à l’élément suivant et de garder une mémoire
de là où l’itérateur est arrivé. Cela se passe en quatre étapes : i) levée d’une exception StopIteration si on est
arrivé au bout, ii) détermination de la lettre actuelle, iii) incrémenter le self.current de 1 pour l’itération suivante
et iv) retourner la lettre actuelle.
Si on sauve cette classe dans un fichier iterator.py, voici comment on pourrait l’utiliser :
À nouveau, une fois l’itérateur épuisé, il ne renvoie plus rien. Bien sûr, cela représente un exemple très simple et la
plupart du temps on créera ses propres classes itérateurs en implémentant de nombreuses fonctionnalités et méthodes sup-
plémentaires. Pour créer un itérateur basique comme celui-ci sur l’alphabet, il est plus commode d’utiliser les générateurs
(voir rubrique Générateurs ci-dessous).
Dans les chapitres précédents, nous avons déjà croisé des itérateurs sans le savoir, car nous ne vous l’avons pas
toujours précisé explicitement ! Dans le chapitre 5 Boucles avec la fonction enumerate(), dans le chapitre 11 Plus sur
les chaînes de caractères avec la fonction map() et dans le chapitre 12 Plus sur les listes avec la fonction zip(). Ces
trois fonctions renvoient des itérateurs qui sont épuisés une fois utilisés :
13. https://dbader.org/blog/python-iterators
14. https://treyhunner.com/2018/06/how-to-make-an-iterator-in-python/
15. https://realpython.com/python-iterators-iterables/
16. https://treyhunner.com/2018/02/python-range-is-not-an-iterator/
Lorsque ces fonctions avaient été évoquées, nous n’avions pas vu ce problème d’épuisement car elles étaient utilisées
directement dans une boucle. Par exemple :
1 >>> for i, j in zip(range(5), range(5, 10)):
2 ... print(i, j)
3 ...
4 0 5
5 1 6
6 2 7
7 3 8
8 4 9
Ainsi, l’itérateur était généré à chaque fois qu’on lançait la boucle et n’était utilisé qu’une seule fois.
Une derniere fonction renvoyant un itérateur qui existe nativement dans les fonctions builtins de Python est reversed
(). Celle-ci prend en argument un objet de type séquence (liste, tuple, chaîne de caractère ou range) et renvoie un itérateur
parcourant la séquence en sens inverse :
1 >>> reversed(range(5))
2 <range_iterator object at 0x7f8b34227780>
3 >>> rev_iterateur = reversed(range(5))
4 >>> for i in rev_iterateur:
5 ... print(i)
6 ...
7 4
8 3
9 2
10 1
11 0
12 >>> list(rev_iterateur)
13 []
Pour finir, examinons les propriétés des itérateurs que nous avions vues pour les conteneurs. Un objet itérateur est
bien sûr iterable et ordonné, par contre il n’est pas indexable. Il ne supporte pas la fonction len(), supporte l’opérateur
in et il est hachable.
1 >>> animaux = ["chien", "chat", "souris"]
2 >>> iterateur = iter(animaux)
3 >>> len(iterateur)
4 Traceback (most recent call last):
5 File "<stdin>", line 1, in <module>
6 TypeError: object of type 'list_iterator' has no len()
7 >>> iterateur[1]
8 Traceback (most recent call last):
9 File "<stdin>", line 1, in <module>
10 TypeError: 'list_iterator' object is not subscriptable
11 >>> "chien" in iterateur
12 True
13 >>> hash(iterateur)
14 8741535406492
Attention
L’utilisation de l’opérateur in pour un test d’appartenance sur un itérateur épuise ce dernier (au même titre que
l’utilisation de l’itérateur dans une boucle où avec la fonction list()) :
1 >>> line = "9 11 25 92 49 98 62 72 63 74"
2 >>> obj_map = map(int, line.split())
3 >>> 9 in obj_map
4 True
5 >>> 9 in obj_map
6 False
Ligne 3, on fait un premier test qui parcourt l’itérateur et renvoie True. Même si la valeur 9 était présente initialement,
le deuxième test, ligne 5, renvoie False car l’itérateur est épuisé.
La fonction product() prend (au moins) deux conteneurs en argument et génère toutes les combinaisons possibles
d’association :
1 >>> import itertools
2 >>> predateurs = ["lion", "requin", "tigre"]
3 >>> proies = ["souris", "oiseau", "gazelle"]
4 >>> for pred, proie in itertools.product(predateurs, proies):
5 ... print(pred, proie)
6 ...
7 lion souris
8 lion oiseau
9 lion gazelle
10 requin souris
11 requin oiseau
12 requin gazelle
13 tigre souris
14 tigre oiseau
15 tigre gazelle
On a ici toutes les combinaisons possibles entre les trois objets ma_liste passés en argument.
Avec deux conteneurs en argument, cette fonction product() revient à faire une double boucle sur les deux conte-
neurs. Elle est donc particulièrement adaptée pour parcourir toutes les éléments d’un tableau. Par exemple, la commande
suivante parcourera toutes les cases d’un échiquier :
17. https://docs.python.org/fr/3.12/library/itertools.html
18. https://docs.python.org/fr/3.12/library/itertools.html#itertools.product
19. https://en.wikipedia.org/wiki/Outer_product
20. https://docs.python.org/fr/3.12/library/itertools.html#itertools.groupby
Mais attention, la fonction product() est un itérateur. Donc quand elle est épuisée, on ne peut plus l’utiliser :
1 >>> list(parcours_echiquier)
2 []
Une utilisation particulièrement utile de product() en bioinformatique peut être de générer toutes les séquences
d’ADN possibles (mots) de deux lettres :
1 >>> bases = "atgc"
2 >>> list(itertools.product(bases, bases))
3 [('a', 'a'), ('a', 't'), ('a', 'g'), ('a', 'c'), ('t', 'a'), ('t', 't'), ('t', 'g'),
4 ('t', 'c'), ('g', 'a'), ('g', 't'), ('g', 'g'), ('g', 'c'), ('c', 'a'), ('c', 't'),
5 ('c', 'g'), ('c', 'c')]
De même, itertools.product(bases, bases, bases) itérera sur tous les mots de trois lettres possibles. Ou en-
core, si on définit une chaîne de caractères contenant les vingt acides aminés comme suit aas = "acdefghiklmnpqrstvwy
", itertools.product(aas, aas) produira tous les dipeptides possibles.
La fonction groupby() permet de faire des regroupements puissants. Pour vous montrer son fonctionnement, nous
allons prendre un exemple. Nous partons d’une liste de mots que nous triions par longueur avec l’argument key auquel
on passe la callback len (voir chapitre 12 Plus sur les listes) :
1 >>> mots = ["bar", "babar", "bam", "ba", "bababar", "barre", "bla", "barbare"]
2 >>> mots.sort(key=len)
3 >>> mots
4 ['ba', 'bar', 'bam', 'bla', 'babar', 'barre', 'bababar', 'barbare']
• Lignes 1 et 3. Il est important de passer à l’argument key la même fonction callback que lors du tri initial.
• Lignes 4 à 7. En transformant cet itérateur en liste, on voit qu’il génère une liste de tuples. Le premier élément de
chaque tuple est un entier correspondant à une longueur de mot, le second élément est un itérateur. Que contient
ce dernier ?
1 >>> for longueur, iterateur in itertools.groupby(mots, key=len):
2 ... print(longueur, list(iterateur))
3 ...
4 2 ['ba']
5 3 ['bar', 'bam', 'bla']
6 5 ['babar', 'barre']
7 7 ['bababar', 'barbare']
Lignes 4 à 7. La conversion de cet itérateur en liste montre qu’il contient tous les mots de même longueur.
Comme vu dans une rubrique précédente, on peut passer une fonction lambda à l’argument key :
1 >>> mots.sort(key=lambda chaine: chaine.count("a"))
2 >>> mots
3 ['ba', 'bar', 'bam', 'bla', 'barre', 'babar', 'barbare', 'bababar']
4 >>> itertools.groupby(mots, key=lambda chaine: chaine.count("a"))
5 <itertools.groupby object at 0x7f467a6d0ca0>
6 >>> list(itertools.groupby(mots, key=lambda chaine: chaine.count("a")))
7 [(1, <itertools._grouper object at 0x7f467a58c490>),
8 (2, <itertools._grouper object at 0x7f467a58c100>),
9 (3, <itertools._grouper object at 0x7f467a58c040>)]
10 >>> for nb_a, iterateur in itertools.groupby(mots, key=lambda chaine: chaine.count("a")):
11 ... print(nb_a, list(iterateur))
12 ...
13 1 ['ba', 'bar', 'bam', 'bla', 'barre']
14 2 ['babar', 'barbare']
15 3 ['bababar']
Conseil
Avant de faire un regroupement avecgroupby(), pensez à trier la liste initiale avec .sort() ou sorted() en utilisant
la même fonction (ou fonction lambda) passée à l’argument key.
Remarque
Il existe aussi une méthode .groupby() qui procède à des regroupements sur les dataframes pandas. Son mode de
fonctionnement est assez différent par rapport à la fonction groupby() du module itertools. Vous pouvez consulter le
chapitre 22 Modules pandas pour en savoir un peu plus.
26.4.4 Générateurs
Définition
Un générateur est un type d’itérateur particulier. On peut créer un générateur très facilement avec le mot-clé yield
ou avec les expression génératrices (generator expressions en anglais) qui ont une syntaxe similaire à celle des listes de
compréhension.
La création d’un générateur avec le mot-clé yield consiste à créer une fonction utilisant ce mot-clé. À partir de ce
moment là, la fonction renvoie un générateur. Avant de voir un exemple, imaginons une fonction qui crée et renvoie une
liste :
1 >>> def cree_alphabet():
2 ... alphabet = []
3 ... for i in range(97, 123):
4 ... alphabet.append(chr(i))
5 ... return alphabet
6 ...
7 >>> alphabet = cree_alphabet()
8 >>> alphabet
9 ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
10 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
Pour créer un générateur équivalent, il suffira de remplacer le .append() par un yield et d’enlever le return :
Comme pour tous les itérateurs, une fois tous les éléments parcourus le générateur est épuisé. Notez que le yield
n’est pas une fonction mais un mot-clé, on n’utilise donc pas de parenthèses. Ce mot-clé yield n’a de sens que dans
une fonction et ne s’utilise que pour créer des générateurs.
La technique avec une expression génératrice ressemble à la syntaxe des listes de compréhension (voir la rubrique
Listes de compréhension du chapitre 12 Plus sur les listes), mais on l’entoure de parenthèses à la place des crochets :
1 >>> [n**2 for n in range(10)] # Liste de compréhension.
2 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
3 >>> (n**2 for n in range(10)) # Expression génératrice.
4 <generator object <genexpr> at 0x7f917feb39f0>
5 >>> gen = (n**2 for n in range(10))
6 >>> for n in gen:
7 ... print(n)
8 ...
9 0
10 [...]
11 81
Conseil
Comme vous le voyez, créer un générateur est extrêmement aisé avec le mot-clé yield ou les expressions génératrices
par rapport à l’écriture d’une classe itérateur (voir ci-dessus). Ainsi nous vous conseillons d’utiliser plutôt les générateurs
lorsque vous souhaitez créer des itérateurs simples.
21. https://www.datacamp.com/tutorial/python-iterators-generators-tutorial
On peut lancer timeit directement à la ligne de commande Unix avec l’option -m suivie de l’instruction Python à
exécuter entre guillemets. Python va effectuer plusieurs fois l’instruction (ici 50 fois) et donnera une approximation au
plus juste du temps d’exécution de celle-ci. Le nombre d’exécutions de l’instruction dépendra du temps pris par celle-ci
et sera entièrement déterminé par Python.
En revenant à notre problématique, voici les résultats de notre somme de 1 à 100000 (testé sur un ordinateur portable
relativement récent avec la version Python 3.12 ) :
$ python -m timeit "sum(n**2 for n in range(100000))"
50 loops, best of 5: 3.76 msec per loop
$ python -m timeit "somme=0" "for n in range(100000): somme += n**2"
50 loops, best of 5: 3.59 msec per loop
$ python -m timeit "sum([n**2 for n in range(100000)])"
50 loops, best of 5: 4.89 msec per loop
• Ligne 1. On utilise un générateur et la fonction sum() pour calculer cette somme. Notez que lorsqu’un générateur
est utilisé dans une fonction, les parenthèses ne sont pas obligatoires. Cela simplifie la syntaxe par rapport à
sum((n**2 for n in range(nb))).
• Ligne 3. On utilise une boucle Python classique pour calculer cette somme. Notez que pour pouvoir utiliser timeit
sur une ligne, on est obligé de passer deux arguments entre guillemets (initialisation de la variable somme et boucle).
• Ligne 5. On utilise une liste de compréhension pour calculer cette somme.
La méthode avec les générateurs est à peu près équivalente à l’utilisation d’une boucle classique où on accumule la
somme, preuve que les deux méthodes sont bien optimisées. De manière spectaculaire, la liste de compréhension est bien
plus lente (presque 1 ms de plus). Ceci vient du fait qu’il faut créer la liste de tous les éléments en mémoire, ce qui est
contre-productif. Le générateur ou la boucle classique se contentent d’itérer et sont bien plus économes.
Dernier point, un test réalisé avec la version Python 3.13 sortie en octobre 2024 conduit aux mêmes observations.
Attention de ne pas transformer cet itérateur en liste ou tuple sous peine de saturer la mémoire de l’ordinateur et de
le faire planter !
Dans le même module les fonctions cycle() 26 et repeat() 27 sont également des itérateurs infinis.
De manière générale, l’utilisation d’itérateurs peut améliorer la lisibilité de vos programmes. Cet article 28 fait remarquer
que le simple fait de créer un itérateur et de le nommer donne un sens à ce qu’il contient. En reprenant notre exemple
sur la somme des carrés :
1 tous_les_carres = (n**2 for n in range(nb))
2 somme = sum(tous_les_carres)
On voit que ce que représente l’objet tous_les_carres n’existe tout simplement pas avec la boucle for ! Par
ailleurs, outre l’avantage de rapidité, l’utilisation de la fonction sum() rend la lecture très claire.
Dernier point, les itérateurs et notamment les générateurs, donnent un moyen de faire de la programmation fonction-
nelle 29 en Python. Sans rentrer dans les considérations théoriques, nous avons déjà vu l’idée générale lorsque nous avons
abordé le method chaining sur les chaînes de caractères ou sur les dataframes pandas. Initialement, la programmation
fonctionnelle en Python utilisait la fonction map() (ainsi que les fonctions filter() et reduce() non abordées ici).
Mais depuis l’arrivée des générateurs, on préfère ces derniers qui sont considérés plus Pythoniques. Regardons un exemple
où nous transformons une chaîne de caractères en entiers puis nous calculons la somme. D’abord avec un générateur :
1 >>> ligne = "9 11 25 92 49 98 62 72 63 74"
2 >>> sum(int(nb) for nb in ligne.split())
3 555
4 >>>
26. https://docs.python.org/3/library/itertools.html#itertools.cycle
27. https://docs.python.org/3/library/itertools.html#itertools.repeat
28. https://treyhunner.com/2019/06/loop-better-a-deeper-look-at-iteration-in-python/#How_iterators_
can_improve_your_code
29. https://fr.wikipedia.org/wiki/Programmation_fonctionnelle
30. https://dbader.org/blog/python-iterator-chains
Une chose à noter dans cet exemple est que lorsqu’on crée un générateur à partir d’un autre générateur, le générateur
initial n’est pas déclenché. Par exemple, en Ligne 4 pour inverses le générateur nombres n’est pas encore déclenché,
ou en Ligne 5 pour cos_inverses le générateur inverses n’est pas déclenché non plus. Tous les générateurs seront
déclenchés en chaine lorsqu’on exécutera la Ligne 6.
Conseil
En écrivant un générateur par ligne, le code est bien lisible. Evitez une syntaxe en une ligne qui s’avérera illisible :
(math.cos(nb) for nb in (nb**-1 for nb in (int(nb) for nb in ligne.split())))
La fonction input() demande à l’utilisateur de saisir une chaîne de caractères. Cette chaîne de caractères est ensuite
transformée en nombre entier avec la fonction int().
Si l’utilisateur ne rentre pas un nombre, voici ce qui se passe :
1 >>> nb = int(input("Entrez un nombre entier : "))
2 Entrez un nombre entier : ATCG
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 ValueError: invalid literal for int() with base 10: 'ATCG'
L’erreur provient de la fonction int() qui n’a pas pu convertir la chaîne de caractères "ATCG" en nombre entier, ce
qui est parfaitement normal. En termes plus techniques, on dira que « Python a levé une exception de type ValueError
». Eh oui il y a de nombreux types d’exceptions différents (voir plus bas) ! Le nom de l’exception apparaît toujours comme
le premier mot de la dernière ligne du message d’erreur. Si nous lancions ces lignes de code sous forme de script (du style
python script.py), cet exemple conduirait à l’arrêt de l’exécution du programme.
Le jeu d’instructions try / except permet de tester l’exécution d’une commande et d’intervenir en cas de levée
d’exception.
31. https://fr.wikipedia.org/wiki/Syst%C3%A8me_de_gestion_d%27exceptions
32. https://en.wikipedia.org/wiki/Exception_handling
1 >>> try:
2 ... nb = int(input("Entrez un nombre entier : "))
3 ... except:
4 ... print("Vous n'avez pas entré un nombre entier !")
5 ...
6 Entrez un nombre entier : ATCG
7 Vous n'avez pas entré un nombre entier !
Dans cet exemple, l’exception levée par la fonction int() (qui ne peut pas convertir "ATCG" en nombre entier) est
interceptée et déclenche l’affichage du message d’avertissement.
On peut ainsi redemander sans cesse un nombre entier à l’utilisateur, jusqu’à ce que celui-ci en rentre bien un.
1 >>> while True:
2 ... try:
3 ... nb = int(input("Entrez un nombre entier : "))
4 ... print("Le nombre est", nb)
5 ... break
6 ... except:
7 ... print("Vous n'avez pas entré un nombre entier !")
8 ... print("Essayez encore")
9 ...
10 Entrez un nombre entier : ATCG
11 Vous n'avez pas entré un nombre entier !
12 Essayez encore
13 Entrez un nombre entier : toto
14 Vous n'avez pas entré un nombre entier !
15 Essayez encore
16 Entrez un nombre entier : 3.2
17 Vous n'avez pas entré un nombre entier !
18 Essayez encore
19 Entrez un nombre entier : 55
20 Le nombre est 55
Notez que dans cet exemple, l’instruction while True est une boucle infinie car la condition True est toujours
vérifiée. L’arrêt de cette boucle est alors forcé par la commande break lorsque l’utilisateur a effectivement entré un
nombre entier.
La gestion des exceptions est très utile dès lors que des données extérieures entrent dans un programme Python, que
ce soit directement par l’utilisateur (avec la fonction input()) ou par des fichiers. Cela est fondamental si vous distribuez
votre code à la communauté : si les utilisateurs ne connaissent pas Python, un message comme Vous n'avez pas entré
un nombre entier ! reste plus clair que ValueError: invalid literal for int() with base 10: 'ATCG'.
Vous pouvez par exemple vérifier qu’un fichier a bien été ouvert.
1 >>> nom = "toto.pdb"
2 >>> try:
3 ... with open(nom, "r") as fichier:
4 ... for ligne in fichier:
5 ... print(ligne)
6 ... except:
7 ... print("Impossible d'ouvrir le fichier", nom)
Si une erreur est déclenchée, c’est sans doute que le fichier n’existe pas à l’emplacement indiqué sur le disque ou que
vous n’avez pas les droits pour le lire.
Il est également possible de spécifier le type d’erreur à gérer. Le premier exemple que nous avons étudié peut s’écrire :
1 >>> try:
2 ... nb = int(input("Entrez un nombre entier : "))
3 ... except ValueError:
4 ... print("Vous n'avez pas entré un nombre entier !")
5 ...
6 Entrez un nombre entier : ATCG
7 Vous n'avez pas entré un nombre entier !
Ici, on intercepte une exception de type ValueError, ce qui correspond bien à un problème de conversion avec
int().
Attention, si vous précisez le type d’exception comme ValueError, le except ValueError n’empêchera pas la
levée d’une autre exception.
1 >>> try:
2 ... nb = int(variable)
3 ... except ValueError:
4 ... print("Vous n'avez pas entré un nombre entier !")
5 ...
6 Traceback (most recent call last):
7 File "<stdin>", line 2, in <module>
8 NameError: name 'variable' is not defined. Did you mean: 'callable'?
Ici l’exception levée est de type NameError, car variable n’existe pas. Alors que si vous mettez except tout court,
cela intercepte n’importe quelle exception.
1 >>> try:
2 ... nb = int(variable)
3 ... except:
4 ... print("Vous n'avez pas entré un nombre entier !")
5 ...
6 Vous n'avez pas entré un nombre entier !
7 >>>
Vous voyez qu’ici cela pose un nouveau problème : le message d’erreur ne correspond pas à l’exception levée !
Conseil
• Nous vous conseillons vivement de toujours préciser le type d’exception dans vos except. Cela évite d’intercepter
une exception que vous n’aviez pas prévue. Il est possible d’intercepter plusieurs types d’exceptions en passant un
tuple à except, par exemple : except (Exception1, Exception2).
• Par ailleurs, ne mettez pas trop de lignes dans le bloc du try. Dans un tel cas, il peut être très pénible de trouver la
ligne qui a conduit à l’exécution du except. Pire encore, il se peut que des lignes que vous aviez prévues ne soient
pas exécutées ! Donc gardez des choses simples dans un premier temps, comme par exemple tester les conversions
de type ou vérifier qu’un fichier existe bien et que vous pouvez l’ouvrir.
Il existe de nombreux types d’exception comme RuntimeError, TypeError, NameError, IOError, etc. Vous pouvez
aller voir la liste complète 33 sur le site de Python. Nous avions déjà croisé des noms d’exception au chapitre 23 (Avoir
la classe avec les objets) en regardant ce que contient le module builtins.
Leur présence dans le module builtins signifie qu’elles font partie du langage lui même, au même titre que les
fonctions de base comme range(), list(), etc.
Avez-vous aussi remarqué que leur nom commence toujours par une majuscule et qu’il peut en contenir plusieurs à
la façon CamelCase ? Si vous avez bien lu le chapitre 16 Bonnes pratiques en programmation Python, avez-vous deviné
pourquoi ? Et bien, c’est parce que les exceptions sont des classes. C’est très intéressant car il est ainsi possible
d’utiliser l’héritage pour créer ses propres exceptions à partir d’exceptions pré-existantes. Nous ne développerons pas cet
aspect, mais en guise d’illustration, regardez ce que renvoit un help() de l’exception OverflowError.
33. https://docs.python.org/fr/3.12/library/exceptions.html#exceptions.TypeError
1 >>> help(OverflowError)
2 [...]
3 class OverflowError(ArithmeticError)
4 | Result too large to be represented.
5 |
6 | Method resolution order:
7 | OverflowError
8 | ArithmeticError
9 | Exception
10 | BaseException
11 | object
La ligne 2 lève une exception ValueError lorsque la variable valeur est négative. L’instruction raise est bien
pratique lorsque vous souhaitez stopper l’exécution d’un programme si une variable ne se trouve pas dans le bon intervalle
ou ne contient pas la bonne valeur. Vous avez sans doute compris maintenant pourquoi on parlait de « levée » d’exception…
Enfin, on peut aussi être très précis dans le message d’erreur. Observez la fonction download_page() qui, avec le
module urllib, télécharge un fichier sur internet.
1 import urllib.request
2
3 def download_page(address):
4 error = ""
5 page = ""
6 try:
7 data = urllib.request.urlopen(address)
8 page = data.read()
9 except IOError as e:
10 if hasattr(e, 'reason'):
11 error = "Cannot reach web server: " + str(e.reason)
12 if hasattr(e, 'code'):
13 error = f"Server failed {e.code:d}"
14 return page, error
15
16 data, error = download_page("https://files.rcsb.org/download/1BTA.pdb")
17
18 if error:
19 print(f"Erreur rencontrée : {error}")
20 else:
21 with open("proteine.pdb", "w") as prot:
22 prot.write(data.decode("utf-8"))
23 print("Protéine enregistrée")
La variable e est une instance de l’exception IOError. Certains de ses attributs sont testés avec la fonction hasattr()
pour ainsi affiner le message renvoyé (ici contenu dans la variable error).
Si tout se passe bien, la page est téléchargée est stockée dans la variable data, puis enregistrée sur le disque dur.
il va alors contenir :
1 #!/usr/bin/env python
2
3 print("Hello World !")
Remarque
La ligne #! /usr/bin/env python n’est pas considérée comme un commentaire par Python, ni comme une ins-
truction Python d’ailleurs . Cette ligne a une signification particulière pour le système d’exploitation Unix.
Pour exécuter le script, il suffit alors de taper son nom précédé des deux caractères ./ (afin de préciser au shell où se
trouve le script) :
$ ./test.py
Hello World !
Définition
Le shebang 34 correspond aux caractères #! qui se trouvent au début de la première ligne du script test.
Le shebang est suivi du chemin complet du programme qui interprète le script ou du programme qui sait où se
trouve l’interpréteur Python. Dans l’exemple précédent, c’est le programme /usr/bin/env qui indique où se trouve
l’interpréteur Python.
L’utilisation de la syntaxe *args permet d’empaqueter tous les arguments positionnels dans un tuple unique args
récupéré au sein de la fonction. L’avantage est que nous pouvons passer autant d’arguments positionnels que l’on veut.
Toutefois, on s’aperçoit en ligne 10 que cette syntaxe ne fonctionne pas avec les arguments par mot-clé.
Il existe un équivalent avec les arguments par mot-clé :
34. http://fr.wikipedia.org/wiki/Shebang
La syntaxe **kwargs permet d’empaqueter l’ensemble des arguments par mot-clé, quel que soit leur nombre, dans
un dictionnaire unique kwargs récupéré dans la fonction. Les clés et valeurs de celui-ci sont les noms des arguments et les
valeurs passées à la fonction. Toutefois, on s’aperçoit en ligne 9 que cette syntaxe ne fonctionne pas avec les arguments
positionnels.
Si on attend un mélange d’arguments positionnels et par mot-clé, on peut utiliser *args et **kwargs en même
temps :
1 >>> def fct(*args, **kwargs):
2 ... print(args)
3 ... print(kwargs)
4 ...
5 >>> fct()
6 ()
7 {}
8 >>> fct(1, 2)
9 (1, 2)
10 {}
11 >>> fct(z=1, y=2)
12 ()
13 {'y': 2, 'z': 1}
14 >>> fct(1, 2, 3, z=1, y=2)
15 (1, 2, 3)
16 {'y': 2, 'z': 1}
Conseil
Les noms *args et **kwargs sont des conventions en Python, ils rappellent les mots arguments et keyword argu-
ments. Bien qu’on puisse mettre ce que l’on veut, nous vous conseillons de respecter ces conventions pour faciliter la
lecture de votre code par d’autres personnes.
L’utilisation de la syntaxe *args et **kwargs est très classique dans le module Fenêtres graphiques et Tkinter
présenté dans le chapitre 25 (en ligne).
Il est possible d’utiliser ce mécanisme d’empaquetage / désempaquetage (packing / unpacking) dans l’autre sens :
Avec la syntaxe *t on désempaquette le tuple à la volée lors de l’appel à la fonction. Cela est aussi possible avec un
dictionnaire :
1 >>> def fct(x, y, z):
2 ... print(x, y, z)
3 ...
4 >>> dico = {'x': -1, 'y': -2, 'z': -3}
5 >>> fct(**dico)
6 -1 -2 -3
Au final, on récupère des tuples au lieu des listes initiales. Mais à ce stade, vous devriez être capable de les retransformer
en liste ;-).
26.8 Décorateurs
Dans le chapitre 24, nous avons rencontré la notion de décorateur pour déclarer des objets de type property. Cela
permettait de rendre des méthodes accessibles comme des attributs (décorateur @property), et plus généralement de
Définition
Un décorateur est une fonction qui modifie le comportement d’une autre fonction.
Ceci étant dit, comme cela fonctionne-t-il ? Commençons par une fonction simple qui affiche de la nourriture :
1 def imprime_victuaille():
2 print("tomate / mozza")
On souhaite améliorer cette fonction et transformer cette victuaille en sandwich, en affichant une tranche de pain avant
et après. La stratégie va être de créer une fonction spéciale, qu’on appelle décorateur, modifiant imprime_victuaille
().
1 def transforme_en_sandwich(fonction_a_decorer):
2 def emballage():
3 print("Pain")
4 fonction_a_decorer()
5 print("Pain")
6 return emballage
La fonction transforme_en_sandwich() est notre décorateur, elle prend en argument la fonction que l’on sou-
haite décorer sous forme de callback (donc sans les parenthèses). On voit qu’à l’intérieur, on définit une sous-fonction
emballage() qui va littéralement « emballer » (wrap) notre fonction à décorer, c’est-à-dire, effectuer une action avant
et après l’appel de la fonction à décorer. Enfin, le décorateur renvoie cette sous-fonction emballage sous forme de
callback. Pour que le décorateur soit actif, il faudra « transformer » la fonction à décorer avec notre fonction décoratrice :
1 imprime_victuaille = transforme_en_sandwich(imprime_victuaille)
Au final l’idée est d’appeler la fonction décoratrice plutôt que la fonction imprime_victuaille() elle-même.
Regardons ce que donne l’exécution de la fonction avant et après décoration :
Fonction non décorée:
tomate/ mozza
Fonction décorée:
Pain
tomate/ mozza
Pain
Le premier appel en ligne 13 exécute la fonction simple, alors que le second en ligne 17 exécute la fonction décorée.
Cette construction peut sembler ardue et difficile à comprendre. Heureusement, Python a une notation en « sucre
syntaxique » (syntactic sugar) qui en facilite la lecture. Celle-ci utilise le symbole @ :
1 def transforme_en_sandwich(fonction_a_decorer):
2 def emballage():
3 print("Pain")
4 fonction_a_decorer()
5 print("Pain")
6 return emballage
7
8 @transforme_en_sandwich
9 def imprime_victuaille():
10 print("tomate / mozza")
11
12 if __name__ == "__main__":
13 imprime_victuaille()
La ligne 8 transforme irrémédiablement la fonction imprime_victuaille() en fonction décorée. Cela parait déjà
un peu plus lisible. L’exécution donnera bien sûr :
Pain
tomate / mozza
Pain
Au final, la notation :
1 @decorator
2 def fct():
3 [...]
est équivalente à :
1 fct = decorator(fct)
Cela fonctionne avec n’importe quelle fonction prenant en argument une autre fonction.
Conseil
Nous vous conseillons bien sûr d’utiliser systématiquement la notation @decorator qui est plus lisible et intuitive.
Si tout cela vous semble ardu (on vous comprend…), vous devez vous dire « pourquoi utiliser une construction aussi
complexe ? ». Et bien, c’est tout simplement parce qu’un décorateur est ré-utilisable dans n’importe quelle fonction. Si
on reprend la même fonction décoratrice que ci-dessus :
1 @transforme_en_sandwich
2 def imprime_victuaille1():
3 print("tomate / mozza")
4
5 @transforme_en_sandwich
6 def imprime_victuaille2():
7 print("jambon / fromage")
8
9 if __name__ == "__main__":
10 imprime_victuaille1()
11 print()
12 imprime_victuaille2()
On a donc un décorateur permettant de transformer en sandwich n’importe quelle fonction imprimant une victuaille !
Ceci renverra :
Pain
tomate / mozza
Pain
Pain
jambon / fromage
Pain
Un exemple plus concret de décorateur pourrait être la mesure du temps d’exécution d’une fonction :
import time
def mesure_temps(fonction_a_decorer):
def emballage():
temps1 = time.time()
fonction_a_decorer()
temps2 = time.time()
print(f"Le temps d'éxécution de {fonction_a_decorer.__name__} est "
f"{temps2 - temps1} s")
return emballage
En ligne 8, l’attribut .__name__ renvoie le nom de la fonction sous forme de chaîne de caractères. Dans cet exemple,
le décorateur @mesure_temps mis devant n’importe quelle fonction affichera systématiquement le temps d’exécution de
celle-ci.
Pour finir, si on revient sur le décorateur @property vu dans le chapitre 24 Avoir plus la classe avec les objets, nous
avions vu également qu’il existait une fonction property(). Donc pour les décorateurs pré-existants que nous avons
abordés dans le chapitre 24, il existe des fonctions équivalentes. Comme dans notre exemple, la notation @decorateur
va finalement appeler la fonction décoratrice. Donc derrière une notation @quelquechose, il existe toujours une fonction
quelquechose() remplissant ce rôle de décorateur.
35. https://realpython.com/primer-on-python-decorators/
Lignes 7. On calcule directement les valeurs en ordonnées avec la fonction cosinus du module NumPy. On constate
ici que NumPy redéfinit certaines fonctions ou constantes mathématiques de base, comme pi, cos() ou abs() (valeur
absolue, ou module d’un nombre complexe). Ces fonctions sont directement utilisables avec un objet array.
Ligne 9. On calcule la transformée de Fourier avec la fonction fft() qui renvoie un vecteur (objet array à une
dimension) de nombres complexes. Eh oui, le module NumPy gère aussi les nombres complexes !
Ligne 10. On extrait le module du résultat précédent avec la fonction abs().
Ligne 11. La variable x_ABSTFL représente l’abscisse du spectre (en radian−1 ).
Ligne 12. La variable ABSTF contient le spectre lui même. L’analyse de ce dernier nous donne un pic à 0,15 radian−1 ,
ce qui correspond bien à 2π (c’est plutôt bon signe de retrouver ce résultat).
Quittez Python. L’historique de toutes vos commandes est dans votre répertoire personnel, dans le fichier .history.
Relancez l’interpréteur Python.
1 >>> import readline
2 >>> readline.read_history_file()
Vous pouvez accéder aux commandes de la session précédente avec la flèche du haut de votre clavier. D’abord les
commandes readline.read_history_file() et import readline de la session actuelle, puis print(a), a = a +
11, a = 22…
Mini-projets
Dans ce chapitre, nous vous proposons quelques scénarios pour développer vos compétences en Python et mettre en
œuvre les concepts que vous avez rencontrés dans les chapitres précédents.
Conseil
Des explications sur le format FASTA et des exemples de code sont fournis dans l’annexe A Quelques formats de
données en biologie.
27.1.2 Genbank2fasta
Ce projet consiste à écrire un convertisseur de fichier, du format GenBank au format FASTA.
Pour cela, nous allons utiliser le fichier GenBank du chromosome I de la levure de boulanger Saccharomyces cerevisiae.
Vous pouvez télécharger ce fichier :
• soit via le lien sur le site du cours NC_001133.gbk 4 ;
1. https://python.sdv.u-paris.fr/data-files/english-common-words.txt
2. https://python.sdv.u-paris.fr/data-files/human-proteome.fasta
3. https://www.uniprot.org/help/human_proteome
4. https://python.sdv.u-paris.fr/data-files/NC_001133.gbk
349
Chapitre 27. Mini-projets 27.1. Description des projets
• soit directement sur la page de Saccharomyces cerevisiae S288c chromosome I, complete sequence 5 sur le site du
NCBI, puis en cliquant sur Send to, puis Complete Record, puis Choose Destination : File, puis Format : GenBank
(full) et enfin sur le bouton Create File.
Vous trouverez des explications sur les formats FASTA et GenBank ainsi que des exemples de code dans l’annexe A
Quelques formats de données en biologie.
Vous pouvez réaliser ce projet sans ou avec des expressions régulières (abordées dans le chapitre 17).
d2θ g
aθ (t) = 2
(t) = − ∗ sin(θ (t))
dt l
où θ représente l’angle entre la verticale et la tige du pendule, aθ l’accélération angulaire, g la gravité, et l la longueur
de la tige (note : pour la dérivation d’une telle équation vous pouvez consulter la page wikipedia 7 ou l’accompagnement
pas à pas, cf. la rubrique suivante).
Pour trouver la valeur de θ en fonction du temps, on pourra utiliser la méthode semi-implicite d’Euler 8 de résolution
d’équation différentielle. La formule ci-dessus donne l’accélération angulaire au temps t : aθ (t) = − gl × sin(θ (t)). À
partir de celle-ci, la méthode propose le calcul de la vitesse angulaire au pas suivant : vθ (t + δ t) = vθ (t) + aθ (t) × δ t
(où δ t représente le pas de temps entre deux étapes successives de la simulation). Enfin, cette vitesse vθ (t + δ t) donne
l’angle θ au pas suivant : θ (t + δ t) = θ (t) + vθ (t + δ t) × δ t. On prendra un pas de temps δ t = 0.05 s, une accélération
gravitationnelle g = 9.8 m.s−2 et une longueur de tige de l = 1 m.
Pour la visualisation, vous pourrez utiliser le widget canvas du module Tkinter (voir le chapitre 25 Fenêtres graphiques
et Tkinter (en ligne), rubrique Un canvas animé dans une classe). On cherche à obtenir un résultat comme montré dans
la figure 27.1.
5. https://www.ncbi.nlm.nih.gov/nuccore/NC_001133
6. https://fr.wikipedia.org/wiki/Pendule_simple
7. https://en.wikipedia.org/wiki/Pendulum_(mathematics)#math_Eq._1
8. https://en.wikipedia.org/wiki/Euler_method
Nous vous conseillons de procéder d’abord à la mise en place du simulateur physique (c’est-à-dire obtenir θ en fonction
du temps ou du pas de simulation). Faites par exemple un premier script Python qui produit un fichier à deux colonnes
(temps et valeur de θ ). Une fois que cela fonctionne bien, il vous faudra construire l’interface Tkinter et l’animer. Vous
pouvez ajouter un bouton pour démarrer / stopper le pendule et une règle pour modifier sa position initiale.
N’oubliez pas, il faudra mettre dans votre programme final une fonction qui convertit l’angle θ en coordonnées
cartésiennes x et y dans le plan du canvas. Faites également attention au système de coordonnées du canvas où les
ordonnées sont inversées par rapport à un repère mathématique. Pour ces deux aspects, reportez-vous à l’exercice
Polygone de Sierpinski du chapitre 25 Fenêtres graphiques et Tkinter (en ligne).
Toujours dans le script words_in_proteome.py, écrivez la fonction read_sequences() qui va lire le protéome
dans le fichier dont le nom est fourni en second argument du script. Cette fonction va renvoyer un dictionnaire dont les
clefs sont les identifiants des protéines (par exemple, O95139, O75438, Q8N4C6) et dont les valeurs associées sont les
séquences.
Dans le programme principal, affichez le nombre de séquences lues. À des fins de test, affichez également la séquence
associée à la protéine O95139.
9. https://python.sdv.u-paris.fr/data-files/english-common-words.txt
10. https://python.sdv.u-paris.fr/data-files/human-proteome.fasta
11. https://www.uniprot.org/help/human_proteome
Écrivez maintenant la fonction search_words_in_proteome() qui prend en argument la liste de mots et le dic-
tionnaire contenant les séquences des protéines et qui va compter le nombre de séquences dans lesquelles un mot est
présent. Cette fonction renverra un dictionnaire dont les clefs sont les mots et les valeurs le nombre de séquences qui
contiennent ces mots. La fonction affichera également le message suivant pour les mots trouvés dans le protéome :
ACCESS found in 1 sequences
ACID found in 38 sequences
ACT found in 805 sequences
[...]
Pour terminer, écrivez maintenant la fonction find_most_frequent_word() qui prend en argument le dictionnaire
renvoyé par la précédente fonction search_words_in_proteome() et qui affiche le mot trouvé dans le plus de protéines,
ainsi que le nombre de séquences dans lesquelles il a été trouvé, sous la forme :
=> xxx found in yyy sequences
Jusqu’à présent, nous avions déterminé, pour chaque mot, le nombre de séquences dans lesquelles il apparaissait.
Nous pourrions aller plus loin et calculer aussi le nombre de fois que chaque mot apparaît dans les séquences.
Pour cela modifier la fonction search_words_in_proteome() de façon à compter le nombre d’occurrences d’un
mot dans les séquences. La méthode .count() vous sera utile.
Déterminez alors quel mot est le plus fréquent dans le protéome humain.
Créez un script genbank2fasta.py et créez la fonction lit_fichier() qui prend en argument le nom du fichier et
qui renvoie le contenu du fichier sous forme d’une liste de lignes, chaque ligne étant elle-même une chaîne de caractères.
Testez cette fonction avec le fichier GenBank NC_001133.gbk et affichez le nombre de lignes lues.
Dans le même script, ajoutez la fonction extrait_organisme() qui prend en argument le contenu du fichier
précédemment obtenu avec la fonction lit_fichier() (sous la forme d’une liste de lignes) et qui renvoie le nom de
l’organisme. Pour récupérer la bonne ligne vous pourrez tester si les premiers caractères de la ligne contiennent le mot-clé
ORGANISM.
Testez cette fonction avec le fichier GenBank NC_001133.gbk et affichez le nom de l’organisme.
Dans le fichier GenBank, les gènes sens sont notés de cette manière :
gene 58..272
ou
gene <2480..>2707
ou
gene complement(<13363..>13743)
Les valeurs numériques séparées par .. indiquent la position du gène dans le génome (numéro de la première base,
numéro de la dernière base).
Remarque
Le symbole < indique un gène partiel sur l’extrémité 5’, c’est-à-dire que le codon START correspondant est incomplet.
Respectivement, le symbole > désigne un gène partiel sur l’extrémité 3’, c’est-à-dire que le codon STOP correspondant
est incomplet. Pour plus de détails, consultez la documentation du NCBI sur les délimitations des gènes 12 . Nous vous
proposons ici d’ignorer ces symboles > et <.
Repérez ces différents gènes dans le fichier NC_001133.gbk. Pour récupérer ces lignes de gènes il faut tester si la
ligne commence par
gene
(c’est-à-dire 5 espaces, suivi du mot gene, suivi de 12 espaces). Pour savoir s’il s’agit d’un gène sur le brin direct ou
complémentaire, il faut tester la présence du mot complement dans la ligne lue.
Ensuite si vous souhaitez récupérer la position de début et de fin de gène, nous vous conseillons d’utiliser la fonction
replace() et de ne garder que les chiffres et les . Par exemple
gene <2480..>2707
sera transformé en
2480..2707
Enfin, avec la méthode .split() vous pourrez facilement récupérer les deux entiers de début et de fin de gène.
Dans le même script genbank2fasta.py, ajoutez la fonction recherche_genes() qui prend en argument le contenu
du fichier (sous la forme d’une liste de lignes) et qui renvoie la liste des gènes.
Chaque gène sera lui-même une liste contenant le numéro de la première base, le numéro de la dernière base et une
chaîne de caractère "sens" pour un gène sens et "antisens" pour un gène antisens.
Testez cette fonction avec le fichier GenBank NC_001133.gbk et affichez le nombre de gènes trouvés, ainsi que le
nombre de gènes sens et antisens.
La taille du génome est indiqué sur la première ligne d’un fichier GenBank. Trouvez la taille du génome stocké dans
le fichier NC_001133.gbk.
Dans un fichier GenBank, la séquence du génome se trouve entre les lignes
ORIGIN
12. https://www.ncbi.nlm.nih.gov/Sitemap/samplerecord.html#BaseSpanB
et
//
Au début ce drapeau aura la valeur False. Ensuite, quand il se mettra à True, on pourra lire les lignes contenant la
séquence, puis quand il se remettra à False on arrêtera.
Une fois la séquence récupérée, il suffira d’éliminer les chiffres, retours chariots et autres espaces (Conseil : calculer
la longueur de la séquence et comparer la à celle indiquée dans le fichier gbk).
Toujours dans le même script genbank2fasta.py, ajoutez la fonction extrait_sequence() qui prend en argument
le contenu du fichier (sous la forme de liste de lignes) et qui renvoie la séquence nucléique du génome (dans une chaîne
de caractères). La séquence ne devra pas contenir d’espaces, ni de chiffres ni de retours chariots.
Testez cette fonction avec le fichier GenBank NC_001133.gbk et affichez le nombre de bases de la séquence extraite.
Vérifiez que vous n’avez pas fait d’erreur en comparant la taille de la séquence extraite avec celle que vous avez trouvée
dans le fichier GenBank.
Le numéro du gène sera un numéro consécutif depuis le premier gène jusqu’au dernier. Il n’y aura pas de différence
de numérotation entre les gènes sens et les gènes antisens.
Testez cette fonction avec le fichier GenBank NC_001133.gbk.
ou
gene <2480..>2707
ou
gene complement(<13363..>13743)
Les valeurs numériques séparées par .. indiquent la position du gène dans le génome (numéro de la première base,
numéro de la dernière base).
Remarque
Le symbole < indique un gène partiel sur l’extrémité 5’, c’est-à-dire que le codon START correspondant est incomplet.
Respectivement, le symbole > désigne un gène partiel sur l’extrémité 3’, c’est-à-dire que le codon STOP correspondant
est incomplet. Pour plus de détails, consultez la documentation du NCBI sur les délimitations des gènes 13 .
Repérez ces différents gènes dans le fichier NC_001133.gbk. Construisez deux expressions régulières pour extraire du
fichier GenBank les gènes sens et les gènes antisens.
Modifiez ces expressions régulières pour que les numéros de la première et de la dernière base puissent être facilement
extraits.
Dans le même script genbank2fasta.py, ajoutez la fonction recherche_genes() qui prend en argument le contenu
du fichier (sous la forme d’une liste de lignes) et qui renvoie la liste des gènes.
Chaque gène sera lui-même une liste contenant le numéro de la première base, le numéro de la dernière base et une
chaîne de caractère "sens" pour un gène sens et "antisens" pour un gène antisens.
Testez cette fonction avec le fichier GenBank NC_001133.gbk et affichez le nombre de gènes trouvés, ainsi que le
nombre de gènes sens et antisens.
et
//
• Prendre la séquence complémentaire. C’est-à-dire à remplacer la base a par la base t, t par a, c par g et g par c.
• Prendre l’inverse. C’est-à-dire à que la première base de la séquence complémentaire devient la dernière base et
réciproquement, la dernière base devient la première.
Pour vous faciliter le travail, ne travaillez que sur des séquences en minuscule.
Testez cette fonction avec les séquences atcg, AATTCCGG et gattaca.
Toujours dans le même script, ajoutez la fonction ecrit_fasta() qui prend en argument un nom de fichier (sous
forme de chaîne de caractères), un commentaire (sous forme de chaîne de caractères) et une séquence (sous forme de
chaîne de caractères) et qui écrit un fichier FASTA. La séquence sera à écrire sur des lignes ne dépassant pas 80 caractères.
Pour rappel, un fichier FASTA suit le format suivant :
>commentaire
sequence sur une ligne de 80 caractères maxi
suite de la séquence .......................
suite de la séquence .......................
...
Toujours dans le même script, ajoutez la fonction extrait_genes() qui prend en argument la liste des gènes, la
séquence nucléotidique complète (sous forme d’une chaîne de caractères) et le nom de l’organisme (sous forme d’une
chaîne de caractères) et qui pour chaque gène :
• extrait la séquence du gène dans la séquence complète ;
• prend la séquence complémentaire inverse (avec la fonction construit_comp_inverse() si le gène est antisens ;
• enregistre le gène dans un fichier au format FASTA (avec la fonction ecrit_fasta()) ;
• affiche à l’écran le numéro du gène et le nom du fichier fasta créé.
La première ligne des fichiers FASTA sera de la forme :
>nom-organisme|numéro-du-gène|début|fin|sens ou antisens
Le numéro du gène sera un numéro consécutif depuis le premier gène jusqu’au dernier. Il n’y aura pas de différence
de numérotation entre les gènes sens et les gènes antisens.
Testez cette fonction avec le fichier GenBank NC_001133.gbk.
Pour terminer, modifiez le script genbank2fasta.py de façon à ce que le fichier GenBank à analyser (dans cet
exemple NC_001133.gbk), soit entré comme argument du script.
Vous afficherez un message d’erreur si :
• le script genbank2fasta.py est utilisé sans argument,
• le fichier fourni en argument n’existe pas.
Pour vous aider, n’hésitez pas à jeter un œil aux descriptions des modules sys et pathlib dans le chapitre 9 sur les
modules.
Testez votre script ainsi finalisé.
F = ma
Cette loi est exprimée ici dans le système de coordonnées cartésiennes (le plan à 2 dimensions). La force F et
l’accélération a sont des vecteurs dont les composantes sont respectivement (Fx , Fy ) et (ax , ay ). La force F correspond
à la somme vectorielle de T et P. La tige du pendule étant rigide, le mouvement de la boule est restreint sur le cercle
de rayon égal à la longueur L de la tige (dessiné en pointillé). Ainsi, seule la composante tangentielle de l’accélération a
sera prise en compte dans ce mouvement. Comment la calculer ? La force de tension T étant orthogonale au mouvement
du pendule, celle-ci n’aura pas d’effet. De même, la composante orthogonale mgcosθ due au poids P n’aura pas d’effet
non plus. Au final, on ne prendra en compte que la composante tangentielle due au poids, c’est-à-dire mgsinθ (cf. figure
27.3). Au final, on peut écrire l’expression suivante en raisonnant sur les valeurs scalaires :
F = ma = −mgsinθ
14. https://fr.wikipedia.org/wiki/Pendule_simple
15. https://en.wikipedia.org/wiki/Pendulum_(mathematics)
16. https://fr.wikipedia.org/wiki/Lois_du_mouvement_de_Newton
Le signe − dans cette formule est très important. Il indique que l’accélération s’oppose systématiquement à θ . Si le
pendule se balance vers la droite et que θ devient plus positif, l’accélération tendra toujours à faire revenir la boule dans
l’autre sens vers sa position d’équilibre à θ = 0. On peut faire un raisonnement équivalent lorsque le pendule se balance
vers la gauche et que θ devient plus négatif.
Si on exprime l’accélération en fonction de θ , on trouve ce résultat qui peut sembler peu intuitif au premier abord :
a = −gsinθ
s = θL
Pour bien comprendre cette formule, souvenez-vous de la formule bien connue du cercle l = 2π r (où l est la circon-
férence, et r le rayon) ! Elle relie la valeur de θ à la distance de l’arc entre la position actuelle de la boule et l’origine (à
θ = 0). On peut donc exprimer la vitesse du pendule en dérivant s par rapport au temps t :
ds dθ
v= =L
dt dt
On peut aussi exprimer l’accélération a en dérivant l’arc s deux fois par rapport à t :
d2s d2θ
a= 2
=L 2
dt dt
A nouveau, cette dernière formule exprime l’accélération de la boule lorsque le mouvement de celle-ci est restreint sur
le cercle pointillé. Si la tige n’était pas rigide, l’expression serait différente.
Si on remplace a dans la formule ci-dessus, on trouve :
d2θ
L = −gsinθ
dt 2
Soit en remaniant, on trouve l’équation différentielle en θ décrivant le mouvement du pendule :
d2θ g
+ sinθ = 0
dt 2 L
Dans la section suivante, nous allons voir comment résoudre numériquement cette équation différentielle.
Il existe de nombreuses méthodes numériques de résolution d’équations différentielles 17 . L’objet ici n’est pas de
faire un rappel sur toutes ces méthodes ni de les comparer, mais juste d’expliquer une de ces méthodes fonctionnant
efficacement pour simuler notre pendule.
Nous allons utiliser la méthode semi-implicite d’Euler 18 . Celle-ci est relativement intuitive à comprendre.
17. https://en.wikipedia.org/wiki/Numerical_methods_for_ordinary_differential_equations
18. https://en.wikipedia.org/wiki/Semi-implicit_Euler_method
Commençons d’abord par calculer l’accélération angulaire aθ au temps t en utilisant l’équation différentielle précé-
demment établie :
d2θ g
aθ (t) = (t) = − sinθ (t)
dt 2 L
L’astuce sera de calculer ensuite la vitesse angulaire au pas suivant t + δ t grâce à la relation :
dθ
vθ (t + δ t) = (t + δ t) ≈ vθ (t) + aθ (t) × δ t
dt
Cette équation est ni plus ni moins qu’un remaniement de la définition de l’accélération, à savoir, la variation de
vitesse par rapport à un temps. Cette vitesse vθ (t + δ t) permettra au final de calculer θ au temps t + δ t (c’est-à-dire ce
que l’on cherche !) :
θ (t + δ t) ≈ θ (t) + vθ (t + δ t) × δ t
Dans une réalisation algorithmique, il suffira d’initialiser les variables de notre système puis de faire une boucle sur
un nombre de pas de simulation. A chaque pas, on calculera aθ (t), puis vθ (t + δ t) et enfin θ (t + δ t) à l’aide des formules
ci-dessus.
L’initialisation des variables pourra ressembler à cela :
L <- 1 # longueur tige en m
g <- 9.8 # accélération gravitationnelle en m/s^2
t <- 0 # temps initial en s
dt <- 0.05 # pas de temps en s
# conditions initiales
theta <- pi / 4 # angle initial en rad
dtheta <- 0 # vitesse angulaire initiale en rad/s
L’initialisation des valeurs de theta et dtheta est très importante, car elle détermine le comportement du pendule.
Nous avons choisi ici d’avoir une vitesse angulaire nulle et un angle de départ du pendule θ = π /4 rad = 45 deg. Le pas
dt est également très important, c’est lui qui déterminera l’erreur faite sur l’intégration de l’équation différentielle. Plus
ce pas est petit, plus on est précis, mais plus le calcul sera long. Ici, on choisit un pas dt de 0.05 s qui constitue un bon
compromis.
À ce stade, vous avez tous les éléments pour tester votre pendule. Essayez de réaliser un petit programme python
pendule_basic.py qui utilise les conditions initiales ci-dessus et simule le mouvement du pendule. À la fin de cette
rubrique, nous proposons une solution en langage algorithmique. Essayez dans un premier temps de le faire vous-même.
À chaque pas, le programme écrira le temps t et l’angle θ dans un fichier pendule_basic.dat. Dans les équations,
θ doit être exprimé en radian, mais nous vous conseillons de convertir cet angle en degré dans le fichier (plus facile à
comprendre pour un humain !). Une fois ce fichier généré, vous pourrez observer le graphe correspondant avec matplotlib
en utilisant le code suivant :
1 import matplotlib.pyplot as plt
2 import numpy as np
3
4 # La fonction np.genfromtxt() renvoie un array à 2 dim.
5 array_data = np.genfromtxt("pendule_basic.dat")
6 # col 0: t, col 1: theta
7 t = array_data[:,0]
8 theta = array_data[:,1]
9
10 # Figure.
11 fig, ax = plt.subplots(figsize=(8, 8))
12 mini = min(theta) * 1.2
13 maxi = max(theta) * 1.2
14 ax.set_xlim(0, max(t))
15 ax.set_ylim(mini, maxi)
16 ax.set_xlabel("t (s)")
17 ax.set_ylabel("theta (deg)")
18 ax.plot(t, theta)
19 fig.savefig("pendule_basic.png")
Si vous observez une sinusoïde, bravo, vous venez de réaliser votre première simulation de pendule ! Vous avez
maintenant le « squelette » de votre « moteur » de simulation. N’hésitez pas à vous amuser avec d’autres conditions
initiales. Ensuite vous pourrez passer à la rubrique suivante.
Si vous avez bloqué dans l’écriture de la boucle, voici à quoi elle pourrait ressembler en langage algorithmique :
tant qu'on n'arrête pas le pendule:
# acc angulaire au tps t (en rad/s^2)
d2theta <- -(g/L) * sin(theta)
# v angulaire mise à jour de t -> t + dt
dtheta <- dtheta + d2theta * dt
# theta mis à jour de t -> t + dt
theta <- theta + dtheta * dt
# t mis à jour
t <- t + dt
# mettre à jour l'affichage
afficher_position_pendule(t, theta)
Nous allons maintenant construire l’application tkinter en vous guidant pas à pas. Il est bien sûr conseillé de relire le
chapitre 25 sur Fenêtres graphiques et Tkinter (en ligne) avant de vous lancer dans cette partie.
Comme expliqué largement dans les chapitres 23 Avoir la classe avec les objets et 24 Avoir plus la classe avec les
objets (en ligne), nous allons construire l’application avec une classe. Le programme principal sera donc très allégé et se
contentera d’instancier l’application, puis de lancer le gestionnaire d’événements :
1 if __name__ == "__main__":
2 """Programme principal (instancie la classe principale, donne un
3 titre et lance le gestionnaire d'événements)
4 """
5 app_pendule = AppliPendule()
6 app_pendule.title("Pendule")
7 app_pendule.mainloop()
Ensuite, nous commençons par écrire le constructeur de la classe. Dans ce constructeur, nous aurons une section
initialisant toutes les variables utilisées pour simuler le pendule (voir rubrique précédente), puis, une autre partie générant
les widgets et tous les éléments graphiques. Nous vous conseillons vivement de bien les séparer, et surtout de mettre
des commentaires pour pouvoir s’y retrouver. Voici un « squelette » pour vous aider :
1 class AppliPendule(tk.Tk):
2 def __init__(self):
3 # Instanciation de la classe Tk.
4 tk.Tk.__init__(self)
5 # Ici vous pouvez définir toutes les variables
6 # concernant la physique du pendule.
7 self.theta = np.pi / 4 # valeur intiale theta
8 self.dtheta = 0 # vitesse angulaire initiale
9 [...]
10 self.g = 9.8 # cst gravitationnelle en m/s^2
11 [...]
12 # Oci vous pouvez construire l'application graphique.
13 self.canv = tk.Canvas(self, bg='gray', height=400, width=400)
14 # Création d'un boutton demarrer, arreter, quitter.
15 # Pensez à placer les widgets avec .pack()
16 [...]
Conseil
Pour éviter un message d’erreur si toutes les méthodes n’existe pas encore, vous pouvez indiquer command=self.quit
pour chaque bouton (vous le changerez après).
Le pivot et la boule pourront être créés avec la méthode .create_oval(), la tige le sera avec la méthode .
create_line(). Pensez à créer des variables pour la tige et la boule lors de l’instanciation car celles-ci bougeront par
la suite.
Comment placer ces éléments dans le canvas ? Vous avez remarqué que lors de la création de ce dernier, nous avons
fixé une dimension de 400 × 400 pixels. Le pivot se trouve au centre, c’est-à-dire au point (200, 200). Pour la tige et
la boule, il sera nécessaire de connaître la position de la boule dans le repère du canvas. Or, pour l’instant, nous
définissons la position de la boule avec l’angle θ . Il va donc nous falloir convertir θ en coordonnées cartésiennes (x, y)
dans le repère mathématique défini dans la figure 27.3, puis dans le repère du canvas (xc , yc ) (cf. rubrique suivante).
Conversion de θ en coordonnées (x, y) Cette étape est relativement simple si on considère le pivot comme le centre du
repère. Avec les fonctions trigonométriques sin() et cos(), vous pourrez calculer la position de la boule (voir l’exercice
sur la spirale dans le chapitre 7 Fichiers). Faites attention toutefois aux deux aspects suivants :
• la trajectoire de la boule suit les coordonnées d’un cercle de rayon L (si on choisit L = 1 m, ce sera plus simple) ;
• nous sommes décalés par rapport au cercle trigonométrique classique ; si on considère L = 1 m :
— quand θ = 0, on a le point (0, −1) (pendule en bas) ;
— quand θ = +π /2 = 90 deg, on a (1, 0) (pendule à droite) ;
— quand θ = −π /2 = −90 deg, on a (−1, 0) (pendule à gauche) ;
— quand θ = ±π = ±180 deg, on a (0, 1) (pendule en haut).
La figure 27.3 montre graphiquement les valeurs de θ .
Si vous n’avez pas trouvé, voici la solution :
1 self.x = np.sin(self.theta) * self.L
2 self.y = -np.cos(self.theta) * self.L
Conversion des coordonnées (x, y) en (xc , yc ) Il nous faut maintenant convertir les coordonnées naturelles mathéma-
tiques du pendule (x, y) en coordonnées dans le canvas (xc , yc ). Plusieurs choses sont importantes pour cela :
• le centre du repère mathématique (0, 0) a la coordonnée (200, 200) dans le canvas ;
• il faut choisir un facteur de conversion : par exemple, si on choisit L = 1 m, on peut proposer le facteur 1 m →
100 pixels ;
• l’axe des ordonnées dans le canvas est inversé par rapport au repère mathématique.
Conseil
Dans votre classe, cela peut être une bonne idée d’écrire une méthode qui réalise cette conversion. Celle-ci pourrait
s’appeler par exemple map_realcoor2canvas().
Il reste maintenant à gérer les boutons permettant de démarrer / stopper le pendule. Pour cela il faudra créer trois
méthodes dans notre classe :
• La méthode .start() : met en mouvement le pendule ; si le pendule n’a jamais été en mouvement, il part de son
point de départ ; si le pendule avait déjà été en mouvement, celui-ci repart d’où on l’avait arrêté (avec la vitesse
qu’il avait à ce moment-là).
• La méthode .stop() : arrête le mouvement du pendule.
• La méthode .move() : gère le mouvement du pendule (génère les coordonnées du pendule au pas suivant).
Le bouton « Démarrer » appellera la méthode .start(), le bouton « Arrêter » appellera la méthode .stop() et
le bouton « Quitter » quittera l’application. Pour lier une action au clic d’un bouton, on se souvient qu’il faut donner à
l’argument par mot-clé command une callback (c’est-à-dire le nom d’une fonction ou méthode sans les parenthèses) :
• btn1 = tk.Button(self, text="Quitter", command=self.quit)
• btn2 = tk.Button(self, text="Demarrer", command=self.start)
• btn3 = tk.Button(self, text="Arrêter", command=self.stop)
Ici, self.start() et self.stop() sont des méthodes que l’on doit créer, self.quit() pré-existe lorsque la fenêtre
tkinter est créée.
Nous vous proposons ici une stratégie inspirée du livre de Gérard Swinnen 19 . Créons d’abord un attribut d’instance
self.is_moving dans le constructeur. Celui-ci va nous servir de « drapeau » pour définir le mouvement du pendule.
Il contiendra un entier positif ou nul. Lorsque ce drapeau sera égal à 0, le pendule sera immobile. Lorsqu’il sera > 0, le
pendule sera en mouvement. Ainsi :
• la méthode .start() ajoutera 1 à self.is_moving. Si self.is_moving est égal à 1 alors la méthode self.
move() sera appelée ;
• la méthode .stop() mettra la valeur de self.is_moving à 0.
Puisque .start() ajoute 1 à self.is_moving, le premier clic sur le bouton « Démarrer » appellera la méthode
.move() car self.is_moving vaudra 1. Si l’utilisateur appuie une deuxième fois sur le bouton « Démarrer », self
.is_moving vaudra 2, mais n’appellera pas .move() une deuxième fois ; cela sera vrai pour tout clic ultérieur de
l’utilisateur sur ce bouton. Cette astuce évite des appels concurrents de la méthode .move().
Il nous reste maintenant à générer la méthode .move() qui meut le pendule. Pour cela vous pouvez vous inspirer de
la rubrique Un canvas animé dans une classe du chapitre 25 Fenêtres graphiques et Tkinter (en ligne).
Cette méthode va réaliser un pas de simulation de t à t + δ t. Il faudra ainsi réaliser dans l’ordre :
19. https://inforef.be/swi/python.htm
• Calculer la nouvelle valeur de θ (self.theta) au pas t + δ t comme nous l’avons fait précédemment avec la
méthode semi-implicite d’Euler.
• Convertir la nouvelle valeur de θ (self.theta) en coordonnées cartésiennes dans le repère du pendule (self.x
et self.y).
• Convertir ces coordonnées cartésiennes dans le repère du Canvas (self.x_c et self.y_c).
• Mettre à jour le dessin de la baballe et de la tige avec la méthode self.canv.coords().
• Incrémenter le pas de temps.
• Si le drapeau self.is_moving est supérieur à 0, la méthode self.move() est rappelée après 20 millisecondes
(Conseil : la méthode .after() est votre amie).
Remarque
• Prenez le temps de chercher par vous-même avant de télécharger les scripts de correction.
• Nous proposons une correction. D’autres solutions sont possibles.
20. https://python.sdv.u-paris.fr/data-files/words_in_proteome.py
21. https://python.sdv.u-paris.fr/data-files/genbank2fasta_sans_regex.py
22. https://python.sdv.u-paris.fr/data-files/genbank2fasta_avec_regex.py
23. https://python.sdv.u-paris.fr/data-files/tk_pendule_simple.py
24. https://python.sdv.u-paris.fr/data-files/tk_pendule.py
A.1 FASTA
Le format FASTA est utilisé pour stocker une ou plusieurs séquences, d’ADN, d’ARN ou de protéines. Ces séquences
sont classiquement représentées sous la forme :
>en-tête
séquence avec un nombre maximum de caractères par ligne
séquence avec un nombre maximum de caractères par ligne
séquence avec un nombre maximum de caractères par ligne
séquence avec un nombre maximum de caractères par ligne
séquence avec un nombre max
La première ligne débute par le caractère > et contient une description de la séquence. On appelle souvent cette ligne
« ligne de description » ou « ligne de commentaire ».
Les lignes suivantes contiennent la séquence à proprement dite, mais avec un nombre maximum fixe de caractères
par ligne. Ce nombre maximum est généralement fixé à 60, 70 ou 80 caractères. Une séquence de plusieurs centaines de
bases ou de résidus est donc répartie sur plusieurs lignes.
Un fichier est dit multifasta lorsqu’il contient plusieurs séquences au format FASTA, les unes à la suite des autres.
Les fichiers contenant une ou plusieurs séquences au format FASTA portent la plupart du temps l’extension .fasta
mais on trouve également .seq, .fas, .fna ou .faa.
A.1.1 Exemples
La séquence protéique au format FASTA de l’insuline humaine 1 , extraite de la base de données UniProt, est :
>sp|P01308|INS_HUMAN Insulin OS=Homo sapiens OX=9606 GN=INS PE=1 SV=1
MALWMRLLPLLALLALWGPDPAAAFVNQHLCGSHLVEALYLVCGERGFFYTPKTRREAED
LQVGQVELGGGPGAGSLQPLALEGSLQKRGIVEQCCTSICSLYQLENYCN
La première ligne contient la description de la séquence (Insulina), le type de base de données (ici, sp qui signifie Swiss-
Prot), son identifiant (P01308) et son nom (INS_HUMAN) dans cette base de données, ainsi que d’autres informations
(OS=Homo sapiens OX=9606 GN=INS PE=1 SV=1B).
Les lignes suivantes contiennent la séquence sur des lignes ne dépassant pas, ici, 60 caractères. La séquence de
l’insuline humaine est composée de 110 acides aminés, soit une ligne de 60 caractères et une seconde de 50 caractères.
1. https://www.uniprot.org/uniprot/P01308
366
A.1. FASTA Annexe A. Quelques formats de données en biologie
Définition
UniProt 2 est une base de données de séquences de protéines. Ces séquences proviennent elles-mêmes de deux autres
bases de données : Swiss-Prot (où les séquences sont annotées manuellement) et TrEMBL (où les séquences sont annotées
automatiquement).
Voici maintenant la séquence nucléique (ARN), au format FASTA, de l’insuline humaine 3 , extraite de la base de
données GenBank 4 :
>BT006808.1 Homo sapiens insulin mRNA, complete cds
ATGGCCCTGTGGATGCGCCTCCTGCCCCTGCTGGCGCTGCTGGCCCTCTGGGGACCTGACCCAGCCGCAG
CCTTTGTGAACCAACACCTGTGCGGCTCACACCTGGTGGAAGCTCTCTACCTAGTGTGCGGGGAACGAGG
CTTCTTCTACACACCCAAGACCCGCCGGGAGGCAGAGGACCTGCAGGTGGGGCAGGTGGAGCTGGGCGGG
GGCCCTGGTGCAGGCAGCCTGCAGCCCTTGGCCCTGGAGGGGTCCCTGCAGAAGCGTGGCATTGTGGAAC
AATGCTGTACCAGCATCTGCTCCCTCTACCAGCTGGAGAACTACTGCAACTAG
On retrouve sur la première ligne la description de la séquence (Homo sapiens insulin mRNA), ainsi que son identifiant
(BT006808.1) dans la base de données GenBank.
Les lignes suivantes contiennent les 333 bases de la séquence, réparties sur cinq lignes de 70 caractères maximum. Il
est curieux de trouver la base T (thymine) dans une séquence d’ARN, qui ne devrait contenir normalement que les bases
A, U, G et C. Ici, la représentation d’une séquence d’ARN avec les bases de l’ADN est une convention.
Pour terminer, voici trois séquences protéiques, au format FASTA, qui correspondent à l’insuline humaine (Homo
sapiens), féline (Felis catus) et bovine (Bos taurus) :
>sp|P01308|INS_HUMAN Insulin OS=Homo sapiens OX=9606 GN=INS PE=1 SV=1
MALWMRLLPLLALLALWGPDPAAAFVNQHLCGSHLVEALYLVCGERGFFYTPKTRREAED
LQVGQVELGGGPGAGSLQPLALEGSLQKRGIVEQCCTSICSLYQLENYCN
>sp|P06306|INS_FELCA Insulin OS=Felis catus OX=9685 GN=INS PE=1 SV=2
MAPWTRLLPLLALLSLWIPAPTRAFVNQHLCGSHLVEALYLVCGERGFFYTPKARREAED
LQGKDAELGEAPGAGGLQPSALEAPLQKRGIVEQCCASVCSLYQLEHYCN
>sp|P01317|INS_BOVIN Insulin OS=Bos taurus OX=9913 GN=INS PE=1 SV=2
MALWTRLRPLLALLALWPPPPARAFVNQHLCGSHLVEALYLVCGERGFFYTPKARREVEG
PQVGALELAGGPGAGGLEGPPQKRGIVEQCCASVCSLYQLENYCN
Ces séquences proviennent de la base de données UniProt 5 . Chaque séquence est délimitée par la ligne d’en-tête qui
débute par >.
Pour chaque séquence lue dans le fichier FASTA, on affiche son identifiant et son nom, puis les 30 premiers résidus
de sa séquence :
2. https://www.uniprot.org/
3. https://www.ncbi.nlm.nih.gov/nuccore/BT006808.1?report=fasta
4. https://www.ncbi.nlm.nih.gov/nuccore/AY899304.1?report=genbank
5. https://www.uniprot.org/
sp|P06306|INS_FELCA
MAPWTRLLPLLALLSLWIPAPTRAFVNQHL
sp|P01317|INS_BOVIN
MALWTRLRPLLALLALWPPPPARAFVNQHL
sp|P01308|INS_HUMAN
MALWMRLLPLLALLALWGPDPAAAFVNQHL
Notez que les protéines sont stockées dans un dictionnaire (prot_dict) où les clefs sont les identifiants et les valeurs
les séquences.
On peut faire la même chose avec le module Biopython :
1 from Bio import SeqIO
2 with open("insulin.fasta", "r") as fasta_file:
3 for record in SeqIO.parse(fasta_file, "fasta"):
4 print(record.id)
5 print(str(record.seq)[:30])
Cela produit le même résultat. L’utilisation de Biopython rend le code plus compacte car on utilise ici la fonction
SeqIO.parse() qui s’occupe de lire le fichier FASTA.
Remarque
L’attribut .id renvoie l’identifiant d’une séquence, c’est-à-dire la première partie de l’entête, sans le caractère >. Pour
obtenir l’entête complet (toujours sans le caractère >), il faut utiliser l’attribut .description.
A.2 GenBank
GenBank est une banque de séquences nucléiques. Le format de fichier associé contient l’information nécessaire pour
décrire un gène ou une portion d’un génome. Les fichiers GenBank portent le plus souvent l’extension .gbk.
Le format GenBank est décrit de manière très complète sur le site du NCBI 6 . En voici néanmoins les principaux
éléments, avec l’exemple du gène qui code pour la trypsine 7 chez l’Homme.
A.2.1 L’en-tête
• Ligne 1 (LOCUS) : le nom du locus (HUMTRPSGNA), la taille du gène (800 paires de bases), le type de molécule
(ARN messager).
• Ligne 3 (ACCESSION) : l’identifiant de la séquence (M22612).
• Ligne 4 (VERSION) : la version de la séquence (M22612.1). Le nombre qui est séparé de l’identifiant de la séquence
par un point est incrémenté pour chaque nouvelle version de la fiche GenBank. Ici, .1 indique que nous en sommes
à la première version.
• Ligne 6 (SOURCE) : la provenance de la séquence (souvent l’organisme d’origine).
• Ligne 7 (ORGANISME) : le nom scientifique de l’organisme, suivi de sa taxonomie (lignes 8 à 10).
6. https://www.ncbi.nlm.nih.gov/Sitemap/samplerecord.html
7. https://www.ncbi.nlm.nih.gov/nuccore/M22612.1
[...]
FEATURES Location/Qualifiers
source 1..800
/organism="Homo sapiens"
/mol_type="mRNA"
/db_xref="taxon:9606"
/map="7q32-qter"
/tissue_type="pancreas"
gene 1..800
/gene="TRY1"
CDS 7..750
/gene="TRY1"
/codon_start=1
/product="trypsinogen"
/protein_id="AAA61231.1"
/db_xref="GDB:G00-119-620"
/translation="MNPLLILTFVAAALAAPFDDDDKIVGGYNCEENSVPYQVSLNSG
YHFCGGSLINEQWVVSAGHCYKSRIQVRLGEHNIEVLEGNEQFINAAKIIRHPQYDRK
TLNNDIMLIKLSSRAVINARVSTISLPTAPPATGTKCLISGWGNTASSGADYPDELQC
LDAPVLSQAKCEASYPGKITSNMFCVGFLEGGKDSCQGDSGGPVVCNGQLQGVVSWGD
GCAQKNKPGVYTKVYNYVKWIKNTIAANS"
sig_peptide 7..51
/gene="TRY1"
/note="G00-119-620"
[...]
• Ligne 9 (gene 1..800) : la délimitation du gène. Ici, de la base 1 à la base 800. Par ailleurs, la notation <x..y
indique que la séquence est partielle sur l’extrémité 5’. Réciproquement, x..y> indique que la séquence est partielle
sur l’extrémité 3’. Enfin, pour les séquences d’ADN, la notation complement(x..y) indique que le gène se trouve
de la base x à la base y, mais sur le brin complémentaire.
• Ligne 10 (/gene="TRY1") : le nom du gène.
• Ligne 11 (CDS 7..750) : la délimitation de la séquence codante.
• Ligne 14 (/product="trypsinogen") : le nom de la protéine produite.
• Lignes 17 à 20 (/translation="MNPLLIL...) : la séquence protéique issue de la traduction de la séquence
codante.
• Ligne 22 (sig_peptide 7..51) : la délimitation du peptide signal.
A.2.3 La séquence
[...]
ORIGIN
1 accaccatga atccactcct gatccttacc tttgtggcag ctgctcttgc tgcccccttt
61 gatgatgatg acaagatcgt tgggggctac aactgtgagg agaattctgt cccctaccag
121 gtgtccctga attctggcta ccacttctgt ggtggctccc tcatcaacga acagtgggtg
181 gtatcagcag gccactgcta caagtcccgc atccaggtga gactgggaga gcacaacatc
241 gaagtcctgg aggggaatga gcagttcatc aatgcagcca agatcatccg ccacccccaa
301 tacgacagga agactctgaa caatgacatc atgttaatca agctctcctc acgtgcagta
361 atcaacgccc gcgtgtccac catctctctg cccaccgccc ctccagccac tggcacgaag
421 tgcctcatct ctggctgggg caacactgcg agctctggcg ccgactaccc agacgagctg
481 cagtgcctgg atgctcctgt gctgagccag gctaagtgtg aagcctccta ccctggaaag
541 attaccagca acatgttctg tgtgggcttc cttgagggag gcaaggattc atgtcagggt
601 gattctggtg gccctgtggt ctgcaatgga cagctccaag gagttgtctc ctggggtgat
661 ggctgtgccc agaagaacaa gcctggagtc tacaccaagg tctacaacta cgtgaaatgg
721 attaagaaca ccatagctgc caatagctaa agcccccagt atctcttcag tctctatacc
781 aataaagtga ccctgttctc
//
La séquence est contenue entre les balises ORIGIN (ligne 2) et // (ligne 17).
Chaque ligne est composée d’une série d’espaces, puis du numéro du premier nucléotide de la ligne, puis d’au plus
6 blocs de 10 nucléotides. Chaque bloc est précédé d’un espace. Par exemple, ligne 10, le premier nucléotide de la ligne
(t) est le numéro 421 dans la séquence.
À partir de l’exemple précédent, voici comment lire un fichier GenBank avec Python et le module Biopython :
Pour la séquence lue dans le fichier GenBank, on affiche son identifiant, sa description et les 60 premiers résidus :
M22612.1
Human pancreatic trypsin 1 (TRY1) mRNA, complete cds.
ACCACCATGAATCCACTCCTGATCCTTACCTTTGTGGCAGCTGCTCTTGCTGCCCCCTTT
Il est également possible de lire un fichier GenBank sans le module Biopython. Une activité dédiée est proposée dans
le chapitre 27 Mini-projets (en ligne).
A.3 PDB
La Protein Data Bank 8 (PDB) est une banque de données qui contient les structures de biomacromolécules (protéines,
ADN, ARN, virus…). Historiquement, le format de fichier qui y est associé est le PDB, dont une documentation détaillée
est disponible sur le site éponyme 9 . Les extensions de fichier pour ce format de données sont .ent et surtout .pdb.
Un fichier PDB est constitué de deux parties principales : l’en-tête et les coordonnées.
• L’en-tête est lisible et utilisable par un être humain (comme par une machine).
• À l’inverse, les coordonnées sont surtout utilisables par un programme pour calculer certaines propriétés de la struc-
ture ou simplement la représenter sur l’écran d’un ordinateur. Bien sûr, un utilisateur expérimenté peut parfaitement
jeter un œil à cette seconde partie.
A.3.1 En-tête
Pour la trypsine bovine, l’en-tête compte 510 lignes. En voici quelques unes :
8. https://www.rcsb.org/
9. http://www.wwpdb.org/documentation/file-format-content/format33/v3.3.html
10. https://www.rcsb.org/structure/2PTN
A.3.2 Coordonnées
Avec la même protéine, la partie coordonnées représente plus de 1 700 lignes. En voici quelques unes correspondantes
au résidu leucine 99 :
11. https://www.uniprot.org/uniprot/P00760
[...]
ATOM 601 N LEU A 99 10.007 19.687 17.536 1.00 12.25 N
ATOM 602 CA LEU A 99 9.599 18.429 18.188 1.00 12.25 C
ATOM 603 C LEU A 99 10.565 17.281 17.914 1.00 12.25 C
ATOM 604 O LEU A 99 10.256 16.101 18.215 1.00 12.25 O
ATOM 605 CB LEU A 99 8.149 18.040 17.853 1.00 12.25 C
ATOM 606 CG LEU A 99 7.125 19.029 18.438 1.00 18.18 C
ATOM 607 CD1 LEU A 99 5.695 18.554 18.168 1.00 18.18 C
ATOM 608 CD2 LEU A 99 7.323 19.236 19.952 1.00 18.18 C
[...]
Chaque ligne correspond à un atome et débute par ATOM ou HETATM. ATOM désigne un atome de la structure de la
biomolécule. HETATM est utilisé pour les atomes qui ne sont pas une biomolécule, comme les ions ou les molécules d’eau.
Toutes les lignes de coordonnées ont sensiblement le même format. Par exemple, pour la première ligne :
• ATOM (ou HETATM).
• 601 : le numéro de l’atome.
• N : le nom de l’atome. Ici, un atome d’azote du squelette peptidique. La structure complète du résidu leucine est
représentée figure A.1.
• LEU : le résidu dont fait partie l’atome. Ici, une leucine.
• A : le nom de la chaîne peptidique.
• 99 : le numéro du résidu dans la protéine.
• 10.007 : la coordonnée x de l’atome.
• 19.687 : la coordonnée y de l’atome.
• 17.536 : la coordonnée z de l’atome.
• 1.00 : le facteur d’occupation, c’est-à-dire la probabilité de trouver l’atome à cette position dans l’espace en
moyenne. Cette probabilité est inférieure à 1 lorsque, expérimentalement, on n’a pas pu déterminer avec une totale
certitude la position de l’atome. Par exemple, dans le cas d’un atome très mobile dans une structure, qui est
déterminé comme étant à deux positions possibles, chaque position aura alors la probabilité 0.50.
• 12.25 : le facteur de température, qui est proportionnel à la mobilité de l’atome dans l’espace. Les atomes situés
en périphérie d’une structure sont souvent plus mobiles que ceux situés au coeur de la structure.
• N : l’élément chimique de l’atome. Ici, l’azote.
Une documentation plus complète des différents champs qui constituent une ligne de coordonnées atomiques se trouve
sur le site de la PDB 12 .
Les résidus sont ensuite décrits les uns après les autres, atome par atome. Voici par exemple les premiers résidus de
la trypsine bovine :
[...]
ATOM 1 N ILE A 16 -8.155 9.648 20.365 1.00 10.68 N
ATOM 2 CA ILE A 16 -8.150 8.766 19.179 1.00 10.68 C
ATOM 3 C ILE A 16 -9.405 9.018 18.348 1.00 10.68 C
ATOM 4 O ILE A 16 -10.533 8.888 18.870 1.00 10.68 O
ATOM 5 CB ILE A 16 -8.091 7.261 19.602 1.00 10.68 C
ATOM 6 CG1 ILE A 16 -6.898 6.882 20.508 1.00 7.42 C
ATOM 7 CG2 ILE A 16 -8.178 6.281 18.408 1.00 7.42 C
ATOM 8 CD1 ILE A 16 -5.555 6.893 19.773 1.00 7.42 C
ATOM 9 N VAL A 17 -9.224 9.305 17.090 1.00 9.63 N
ATOM 10 CA VAL A 17 -10.351 9.448 16.157 1.00 9.63 C
ATOM 11 C VAL A 17 -10.500 8.184 15.315 1.00 9.63 C
ATOM 12 O VAL A 17 -9.496 7.688 14.748 1.00 9.63 O
ATOM 13 CB VAL A 17 -10.123 10.665 15.222 1.00 9.63 C
ATOM 14 CG1 VAL A 17 -11.319 10.915 14.278 1.00 11.95 C
ATOM 15 CG2 VAL A 17 -9.737 11.970 15.970 1.00 11.95 C
[...]
Vous remarquerez que le numéro du premier résidu est 16 et non pas 1. Cela s’explique par la technique expérimentale
utilisée qui n’a pas permis de déterminer la structure des 15 premiers résidus.
La structure de la trypsine bovine n’est constituée que d’une seule chaîne peptidique (notée A). Lorsqu’une structure
est composée de plusieurs chaînes, comme dans le cas de la structure du récepteur GABAB 1 et 2 chez la drosophile
12. http://www.wwpdb.org/documentation/file-format-content/format33/sect9.html
Figure A.1 – Structure tridimensionnelle d’un résidu leucine. Les noms des atomes sont indiqués en noir.
[...]
ATOM 762 HB1 ALA A 44 37.162 -2.955 2.220 1.00 0.00 H
ATOM 763 HB2 ALA A 44 38.306 -2.353 3.417 1.00 0.00 H
ATOM 764 HB3 ALA A 44 38.243 -1.621 1.814 1.00 0.00 H
TER 765 ALA A 44
ATOM 766 N GLY B 95 -18.564 3.009 13.772 1.00 0.00 N
ATOM 767 CA GLY B 95 -19.166 3.646 12.621 1.00 0.00 C
ATOM 768 C GLY B 95 -20.207 2.755 11.976 1.00 0.00 C
[...]
La première chaîne est notée A et la seconde B. La séparation entre les deux chaînes est marquée par la ligne :
Dans un fichier PDB, chaque structure porte un nom de chaîne différent (par exemple : A,B,C‘, etc.).
Enfin, lorsque la structure est déterminée par RMN, il est possible que plusieurs structures soient présentes dans le
même fichier PDB. Toutes ces structures, ou « modèles », sont des solutions possibles du jeu de contraintes mesurées
expérimentalement en RMN. Voici un exemple, toujours pour la structure du récepteur GABAB 1 et 2 chez la drosophile :
13. http://www.rcsb.org/structure/5X9X
[...]
MODEL 1
ATOM 1 N MET A 1 -27.283 -9.772 5.388 1.00 0.00 N
ATOM 2 CA MET A 1 -28.233 -8.680 5.682 1.00 0.00 C
[...]
ATOM 1499 HG2 GLU B 139 36.113 -5.242 2.536 1.00 0.00 H
ATOM 1500 HG3 GLU B 139 37.475 -4.132 2.428 1.00 0.00 H
TER 1501 GLU B 139
ENDMDL
MODEL 2
ATOM 1 N MET A 1 -29.736 -10.759 4.394 1.00 0.00 N
ATOM 2 CA MET A 1 -28.372 -10.225 4.603 1.00 0.00 C
[...]
ATOM 1499 HG2 GLU B 139 36.113 -5.242 2.536 1.00 0.00 H
ATOM 1500 HG3 GLU B 139 37.475 -4.132 2.428 1.00 0.00 H
TER 1501 GLU B 139
ENDMDL
MODEL 2
ATOM 1 N MET A 1 -29.736 -10.759 4.394 1.00 0.00 N
ATOM 2 CA MET A 1 -28.372 -10.225 4.603 1.00 0.00 C
[...]
et :
ENDMDL
où n est le numéro du modèle. Pour la structure du récepteur GABAB 1 et 2, il y a 20 modèles de décrits dans le
fichier PDB.
Remarque
Les fichiers PDB sont parfois (très) mal formatés. Si Biopython ne parvient pas à lire un tel fichier, remplacez alors
la ligne 2 par parser = PDBParser(PERMISSIVE=1). Soyez néanmoins très prudent quant aux résultats obtenus.
ce qui produit :
1 hydrolase (serine proteinase)
2 x-ray diffraction
1 model = structure[0]
2 chain = model["A"]
3 res1 = chain[16]
4 res2 = chain[17]
5 print(res1.resname, res1["N"].coord)
6 print(res2.resname, res2["CA"].coord)
ce qui produit :
1 ILE [ -8.15499973 9.64799976 20.36499977]
2 VAL [-10.35099983 9.44799995 16.15699959]
L’objet res1["N"].coord est un array de NumPy (voir le chapitre 20 Module NumPy). On peut alors obtenir
simplement les coordonnées x, y et z d’un atome :
1 print(res1["N"].coord[0], res1["N"].coord[1], res1["N"].coord[2])
ce qui produit :
1 -8.155 9.648 20.365
Remarque
Biopython utilise la hiérarchie suivante :
structure > model > chain > residue > atom
même lorsque la structure ne contient qu’un seul modèle. C’est d’ailleurs le cas ici, puisque la structure a été obtenue
par cristallographie aux rayons X.
Enfin, pour afficher les coordonnées des carbones α (notés CA) des 10 premiers résidus (à partir du résidu 16, car
c’est le premier résidu dont on connaît la structure) :
1 res_start = 16
2 model = structure[0]
3 chain = model["A"]
4 for i in range(10):
5 idx = res_start + i
6 print(chain[idx].resname, idx, chain[idx]["CA"].coord)
Il est aussi très intéressant (et formateur) d’écrire son propre parser de fichier PDB, c’est-à-dire un programme qui
lit un fichier PDB (sans le module Biopython). Dans ce cas, la figure A.2 vous aidera à déterminer comment extraire les
différentes informations d’une ligne de coordonnées ATOM ou HETATM.
Exemple : pour extraire le nom du résidu, il faut isoler le contenu des colonnes 18 à 20 du fichier PDB, ce qui
correspond aux index de 17 à 19 pour une chaîne de caractères en Python (soit la tranche de chaîne de caractères
[17:20], car la première borne est incluse et la seconde exclue).
Pour lire le fichier PDB de la trypsine bovine (2PTN.pdb) et extraire (encore) les coordonnées des carbones α des 10
premiers résidus, nous pouvons utiliser le code suivant :
ce qui donne :
ILE 16 -8.15 8.766 19.179
VAL 17 -10.351 9.448 16.157
GLY 18 -12.021 6.63 14.259
GLY 19 -10.902 3.899 16.684
TYR 20 -12.651 1.442 19.016
THR 21 -13.018 0.938 22.76
CYS 22 -10.02 -1.163 23.76
GLY 23 -11.683 -2.865 26.714
ALA 24 -10.648 -2.627 30.361
ASN 25 -6.97 -3.437 31.02
Remarque
Pour extraire des valeurs numériques, comme des numéros de résidus ou des coordonnées atomiques, il ne faudra pas
oublier de les convertir en entiers ou en floats.
A.4.1 XML
Le format XML est un format de fichier à balises qui permet de stocker quasiment n’importe quel type d’information
de façon structurée et hiérarchisée. L’acronyme XML signifie Extensible Markup Language qui pourrait se traduire en
français par « Langage de balisage extensible 14 ». Les balises dont il est question servent à délimiter du contenu :
<balise>contenu</balise>
La balise <balise> est une balise ouvrante. La balise </balise> est une balise fermante. Notez le caractère / qui
marque la différence entre une balise ouvrante et une balise fermante.
Il existe également des balises vides, qui sont à la fois ouvrantes et fermantes :
<balise />
Une balise peut avoir certaines propriétés, appelées attributs, qui sont définies, dans la balise ouvrante. Par exemple :
<balise propriété1=valeur1 propriété2=valeur2>contenu</balise>
Un attribut est un couple nom et valeur (par exemple propriété1 est un nom et valeur1 est la valeur associée).
Enfin, les balises peuvent être imbriquées les unes dans les autres :
14. https://fr.wikipedia.org/wiki/Extensible_Markup_Language
<protein>
<element>élément 1</element>
<element>élément 2</element>
<element>élément 3</element>
</protein>
Dans cet exemple, nous avons trois balises element qui sont contenues dans une balise protein.
Voici un autre exemple avec l’enzyme trypsine 15 humaine (code P07477 16 ), telle qu’on peut la trouver décrite dans
la base de données UniProt :
<?xml version='1.0' encoding='UTF-8'?>
<uniprot xmlns="http://uniprot.org/uniprot" xmlns:xsi=[...]>
<entry dataset="Swiss-Prot" created="1988-04-01" modified="2018-09-12" [...]>
<accession>P07477</accession>
<accession>A1A509</accession>
[...]
<gene>
<name type="primary">PRSS1</name>
<name type="synonym">TRP1</name>
<name type="synonym">TRY1</name>
</gene>
[...]
<sequence length="247" mass="26558" checksum="DD49A487B8062813" [...]>
MNPLLILTFVAAALAAPFDDDDKIVGGYNCEENSVPYQVSLNSGYHFCGGSLINEQWVVS
AGHCYKSRIQVRLGEHNIEVLEGNEQFINAAKIIRHPQYDRKTLNNDIMLIKLSSRAVIN
ARVSTISLPTAPPATGTKCLISGWGNTASSGADYPDELQCLDAPVLSQAKCEASYPGKIT
SNMFCVGFLEGGKDSCQGDSGGPVVCNGQLQGVVSWGDGCAQKNKPGVYTKVYNYVKWIK
NTIAANS
</sequence>
</entry>
[...]
</uniprot>
• Ligne 1. On utilise le sous-module etree du module lxml pour lire le fichier XML.
15. https://www.uniprot.org/uniprot/P07477
16. https://www.uniprot.org/uniprot/P07477.xml
• Ligne 2. On utilise le module d’expressions régulières re pour supprimer tous les attributs de la balise uniprot
(ligne 7). Nous ne rentrerons pas dans les détails, mais ces attributs rendent plus complexe la lecture du fichier
XML.
• Ligne 9. La variable root contient le fichier XML prêt à être manipulé.
• Ligne 11. On recherche les noms des gènes (balises <name></name>) associés à la trypsine. Pour cela, on utilise
la méthode .xpath(), avec comme argument l’enchaînement des différentes balises qui conduisent aux noms des
gènes.
• Ligne 12. Pour chaque nom de gène, on va afficher son contenu (gene.text) et la valeur associée à l’attribut
type avec la méthode .get("type").
• Ligne 14. On stocke dans la variable sequence la balise associée à la séquence de la protéine. Comme root.
xpath("/uniprot/entry/sequence") renvoie un itérateur et qu’il n’y a qu’une seule balise séquence, on prend
ici le seul et unique élément root.xpath("/uniprot/entry/sequence")[0].
• Ligne 15. On affiche le contenu de la séquence sequence.text, nettoyé d’éventuels retours chariots ou espaces
sequence.text.strip().
• Ligne 16. On affiche la taille de la séquence en récupérant la valeur de l’attribut length (toujours de la balise
<sequence></sequence>).
Le résultat obtenu est le suivant :
gene : PRSS1 (primary)
gene : TRP1 (synonym)
gene : TRY1 (synonym)
gene : TRYP1 (synonym)
sequence: MNPLLILTFVAAALAAPFDDDDKIVGGYNCEENSVPYQVSLNSGYHFCGGSLINEQWVVS
AGHCYKSRIQVRLGEHNIEVLEGNEQFINAAKIIRHPQYDRKTLNNDIMLIKLSSRAVIN
ARVSTISLPTAPPATGTKCLISGWGNTASSGADYPDELQCLDAPVLSQAKCEASYPGKIT
SNMFCVGFLEGGKDSCQGDSGGPVVCNGQLQGVVSWGDGCAQKNKPGVYTKVYNYVKWIK
NTIAANS
length: 247
L’acronyme CSV signifie « Comma-Separated values », qu’on peut traduire littéralement par « valeurs séparées par
des virgules ». De façon similaire, TSV signifie « Tabulation-Separated Values », soit des « valeurs séparées par des
tabulations ».
Ces deux formats sont utiles pour stocker des données structurées sous forme de tableau, comme vous pourriez l’avoir
dans un tableur.
À titre d’exemple, le tableau ci-dessous liste les structures associées à la transferrine, protéine présente dans le plasma
sanguin et impliquée dans la régulation du fer. Ces données proviennent de la Protein Data Bank (PDB). Pour chaque
protéine (PDB ID) est indiqué le nom de l’organisme associé (Source), la date à laquelle cette structure a été déposée
dans la PDB (Deposit Date), le nombre d’acides aminés de la protéine et sa masse moléculaire (MW ).
17. https://python.sdv.u-paris.fr/data-files/transferrin_report.csv
Sur chaque ligne, les différentes valeurs sont séparées par une virgule. La première ligne contient le nom des colonnes
et est appelée ligne d’en-tête.
L’équivalent en TSV 18 est :
PDB ID Source Deposit Date Length MW
1A8E Homo sapiens 1998-03-24 329 36408.40
1A8F Homo sapiens 1998-03-25 329 36408.40
1AIV Gallus gallus 1997-04-28 686 75929.00
1AOV Anas platyrhynchos 1996-12-11 686 75731.80
[...]
Sur chaque ligne, les différentes valeurs sont séparées par une tabulation.
Attention
Le caractère tabulation est un caractère invisible « élastique », c’est-à-dire qu’il a une largeur variable suivant l’éditeur
de texte utilisé. Par exemple, dans la ligne d’en-tête, l’espace entre PDB ID et Source apparaît comme différent de
l’espace entre Deposit Date et Length alors qu’il y a pourtant une seule tabulation à chaque fois.
A.4.2.2 Lecture
En Python, le module csv de la bibliothèque standard est très pratique pour lire et écrire des fichiers au format CSV
et TSV. Nous vous conseillons de lire la documentation très complète sur ce module 19 .
Voici un exemple :
1 import csv
2
3 with open("transferrin_report.csv") as f_in:
4 f_reader = csv.DictReader(f_in)
5 for row in f_reader:
6 print(row["PDB ID"], row["Deposit Date"], row["Length"])
18. https://python.sdv.u-paris.fr/data-files/transferrin_report.tsv
19. https://docs.python.org/fr/3.7/library/csv.html
1 import csv
2
3 with open("transferrin_PDB_report.tsv") as f_in:
4 f_reader = csv.DictReader(f_in, delimiter="\t")
5 for row in f_reader:
6 print(row["PDB ID"], row["Deposit Date"], row["Length"])
A.4.2.3 Écriture
De façon très similaire, l’écriture d’un fichier TSV est réalisée avec le code suivant :
1 import csv
2
3 with open("test.tsv", "w") as f_out:
4 fields = ["Name", "Quantity"]
5 f_writer = csv.DictWriter(f_out, fieldnames=fields, delimiter="\t")
6 f_writer.writeheader()
7 f_writer.writerow({"Name": "girafe", "Quantity":5})
8 f_writer.writerow({"Name": "tigre", "Quantity":3})
9 f_writer.writerow({"Name": "singe", "Quantity":8})
Vous êtes désormais capables de lire et écrire des fichiers aux formats CSV et TSV. Les codes que nous vous avons
proposés ne sont que des exemples. À vous de poursuivre l’exploration du module csv.
Remarque
Le module pandas décrit dans le chapitre 22 Module Pandas est tout à fait capable de lire et écrire des fichiers CSV
et TSV. Nous vous conseillons de l’utiliser si vous analysez des données avec ces types de fichiers.
Installation de Python
Attention
La procédure d’installation ci-dessous a été testée avec la version Miniconda Latest - Conda 24.5.0 Python
3.12.4 released Jun 26, 2024.
Python est déjà présent sous Linux ou Mac OS X et s’installe très facilement sous Windows. Toutefois, nous décri-
vons dans cet ouvrage l’utilisation de modules supplémentaires qui sont très utiles en bioinformatique (NumPy, scipy,
matplotlib, pandas, Biopython), mais également les notebooks Jupyter.
On va donc utiliser un gestionnaire de paquets qui va installer ces modules supplémentaires. On souhaite également que
ce gestionnaire de paquets soit disponible pour Windows, Mac OS X et Linux. Fin 2018, il y a deux grandes alternatives :
1. Anaconda et Miniconda : Anaconda 1 est une distribution complète de Python qui contient un gestionnaire de
paquets très puissant nommé conda. Anaconda installe de très nombreux paquets et outils mais nécessite un espace
disque de plusieurs gigaoctets. Miniconda 2 est une version allégée d’Anaconda, donc plus rapide à installer et
occupant peu d’espace sur le disque dur. Le gestionnaire de paquet conda est aussi présent dans Miniconda.
2. Pip : pip 3 est le gestionnaire de paquets de Python et qui est systématiquement présent depuis la version 3.4.
1. https://www.anaconda.com/
2. https://conda.io/miniconda.html
3. https://pip.pypa.io/en/stable/
383
Annexe B. Installation de Python B.2. Installation de Python avec Miniconda
Depuis quelques années, Windows 10 (et 11) propose le WSL 4 (Windows Subsystem for Linux). Le WSL permet de
lancer un terminal Linux au sein de Windows et propose (quasiment) toutes les fonctionnalités disponibles sous un vrai
système Linux. Nous ne détaillons par comment l’installer, mais vous pouvez vous référer à la page d’installation sur le site
de Microsoft 5 . Si vous avez installé WSL sur votre ordinateur, nous vous recommandons de suivre la procédure ci-dessous
comme si vous étiez sous Linux (rubrique Installation de Python avec Miniconda pour Linux), plutôt que d’installer la
version Windows.
signifie l’invite d’un shell quel qu’il soit (PowerShell sous Windows, bash sous Mac OS X et Linux).
Comme demandé, appuyez sur la touche Entrée. Faites ensuite défiler la licence d’utilisation avec la touche Espace.
Tapez yes puis appuyez sur la touche Entrée pour valider :
Do you accept the license terms? [yes|no]
[no] >>> yes
Le programme d’installation vous propose ensuite d’installer Miniconda dans le répertoire miniconda3 dans votre
répertoire personnel. Par exemple, dans le répertoire /home/pierre/miniconda3 si votre nom d’utilisateur est pierre.
Validez cette proposition en appuyant sur la touche Entrée :
Miniconda3 will now be installed into this location:
/home/pierre/miniconda3
[/home/pierre/miniconda3] >>>
L’installation de Miniconda est terminée. L’espace utilisé par Miniconda sur votre disque dur est d’environ 450 Mo.
Ouvrez un nouveau shell. Vous devriez voir dans votre invite la chaîne (base) indiquant que l’environnement conda
de base est activé. À partir de maintenant, lorsque vous taperez la commande python, c’est le Python 3 de Miniconda
qui sera lancé :
$ python
Python 3.12.4 | packaged by Anaconda, Inc. | (main, Jun 18 2024, 15:12:24) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Quittez Python en tapant la commande exit() puis appuyant sur la touche Entrée.
De retour dans le shell, testez si le gestionnaire de paquets conda est fonctionnel. Tapez la commande conda dans
le shell, vous devriez avoir la sortie suivante :
$ conda
usage: conda [-h] [-v] [--no-plugins] [-V] COMMAND ...
conda is a tool for managing and deploying applications, environments and packages.
options:
-h, --help Show this help message and exit.
[...]
Si c’est bien le cas, bravo, conda et bien installé et vous pouvez passez à la suite (rendez-vous à la rubrique Installation
des modules supplémentaires) !
Si vous souhaitez supprimer Miniconda, rien de plus simple, il suffit de suivre ces deux étapes :
Étape 1. Supprimer le répertoire de Miniconda. Par exemple pour l’utilisateur pierre :
$ rm -rf /home/pierre/miniconda3
Étape 2. Dans le fichier de configuration du shell Bash, supprimer les lignes comprises entre
# >>> conda initialize >>>
et
# <<< conda initialize <<<
puis suivez les mêmes instructions que dans la rubrique précédente (la seule petite subtilité est pour le chemin,
choisissez /User/votre_nom_utilisateur/miniconda3 sous Mac au lieu de /home/votre_nom_utilisateur/
miniconda3 sous Linux).
Attention
Nous partons du principe qu’aucune version d’Anaconda, Miniconda, ou encore de Python « classique » (obtenue sur
le site officiel de Python 7 ) n’est installée sur votre ordinateur. Si tel est le cas, nous vous recommandons vivement de la
désinstaller pour éviter des conflits de version.
7. https://www.python.org/downloads/
Il nous faut maintenant initialiser conda. Cette manipulation va permettre de le rendre visible dans n’importe quel
shell Powershell.
L’installateur a en principe ajouté des nouveaux raccourcis dans le Menu Démarrer contenant le mot Anaconda :
• Anaconda Powershell Prompt (Miniconda3) : pour lancer un shell Powershell (shell standard de Windows
équivalent du bash sous Linux) avec conda qui est activé correctement ;
• Anaconda Prompt (Miniconda3) : même chose mais avec le shell nommé cmd ; ce vieux shell est limité et nous
vous en déconseillons l’utilisation.
Nous allons maintenant initialiser conda « à la main ». Cliquez sur Anaconda Powershell Prompt (Miniconda3)
qui va lancer un Powershell avec conda activé, puis tapez la commande conda init :
Lorsque vous presserez la touche Entrée vous obtiendrez une sortie de ce style :
$ conda init
no change C:\Users\Pat\miniconda3\Scripts\conda.exe
no change C:\Users\Pat\miniconda3\Scripts\conda-env.exe
no change C:\Users\Pat\miniconda3\Scripts\conda-script.py
no change C:\Users\Pat\miniconda3\Scripts\conda-env-script.py
no change C:\Users\Pat\miniconda3\condabin\conda.bat
no change C:\Users\Pat\miniconda3\Library\bin\conda.bat
no change C:\Users\Pat\miniconda3\condabin\_conda_activate.bat
no change C:\Users\Pat\miniconda3\condabin\rename_tmp.bat
no change C:\Users\Pat\miniconda3\condabin\conda_auto_activate.bat
no change C:\Users\Pat\miniconda3\condabin\conda_hook.bat
no change C:\Users\Pat\miniconda3\Scripts\activate.bat
no change C:\Users\Pat\miniconda3\condabin\activate.bat
no change C:\Users\Pat\miniconda3\condabin\deactivate.bat
modified C:\Users\Pat\miniconda3\Scripts\activate
modified C:\Users\Pat\miniconda3\Scripts\deactivate
modified C:\Users\Pat\miniconda3\etc\profile.d\conda.sh
modified C:\Users\Pat\miniconda3\etc\fish\conf.d\conda.fish
no change C:\Users\Pat\miniconda3\shell\condabin\Conda.psm1
modified C:\Users\Pat\miniconda3\shell\condabin\conda-hook.ps1
no change C:\Users\Pat\miniconda3\Lib\site-packages\xontrib\conda.xsh
modified C:\Users\Pat\miniconda3\etc\profile.d\conda.csh
modified C:\Users\Pat\Documents\WindowsPowerShell\profile.ps1
modified HKEY_CURRENT_USER\Software\Microsoft\Command Processor\AutoRun
388 Cours
==> For changes to take effect, close and re-open decurrent
your Python /shell.
Université
<== Paris Cité / UFR Sciences du Vivant
$ conda init
no change C:\Users\Pat\Miniconda3\Scripts\conda.exe
B.2. Installation de Python avec Miniconda Annexe B. Installation de Python
Cela signifie que vous êtes bien dans l’interpréteur Python. À partir de là vous pouvez taper exit() puis appuyer sur
Une fois revenu dans le shell, tapez la commande conda, vous devriez obtenir :
usage: conda-script.py [-h] [-v] [--no-plugins] [-V] COMMAND ...
conda is a tool for managing and deploying applications, environments and packages.
options:
-h, --help Show this help message and exit.
-v, --verbose Can be used multiple times. Once for detailed output, twice for INFO logging, thrice
for DEBUG
logging, four times for TRACE logging.
--no-plugins Disable all plugins that are not built into conda.
-V, --version Show the conda version number and exit.
[...]
Si c’est le cas, bravo, conda est bien installé et vous pouvez passez à la suite (rendez-vous à la rubrique Installation
des modules supplémentaires) !
Si vous souhaitez désinstaller Miniconda, rien de plus simple. Dans le menu Windows, tapez Anaconda puis Désins-
taller. Cela vous emmènera dans le panneau de configuration. Faites alors un clic droit sur Miniconda3 py312..., puis
cliquez sur Désinstaller. Cela devrait ouvrir la fenêtre suivante :
Cliquez sur Next. Vous aurez alors l’écran suivant :
Cliquez sur Uninstall, puis à l’écran suivant confirmez que vous souhaitez désintaller Miniconda :
Le désinstallateur se lancera alors (cela peut prendre quelques minutes) :
Une fois la désinstallation terminée, cliquez sur Next :
Puis enfin sur Finish :
À ce point, Miniconda est bien désinstallé. Il reste toutefois une dernière manipulation que l’installateur n’a pas
effectué : il faut détruire à la main le fichier
C:\Users\nom_utilisateur\Documents\WindowsPowerShell\profile.ps1
(bien sûr, remplacez nom_utilisateur par votre propre nom d’utilisateur). Si vous ne le faites pas, cela affichera
un message d’erreur à chaque fois que vous lancerez un Powershell.
Cette commande va lancer l’installation des modules externes NumPy, pandas, matplotlib, scipy, Biopython et Jupyter
lab. Ces modules vont être téléchargés depuis internet par conda, il faut bien sûr que votre connexion internent soit
fonctionnelle. Au début, conda va déterminer les versions des paquets à télécharger en fonction de la version de Python
ainsi que d’autres paramètres (cela prend une à deux minutes). Cela devrait donner la sortie suivante :
Channels:
- defaults
Platform: linux-64
Collecting package metadata (repodata.json): done
Solving environment: done
## Package Plan ##
package | build
---------------------------|-----------------
anyio-4.2.0 | py312h06a4308_0 238 KB
argon2-cffi-21.3.0 | pyhd3eb1b0_0 15 KB
Cours de Python / Université Paris Cité
argon2-cffi-bindings-21.2.0| / UFR Sciences
py312h5eee18b_0 du Vivant33 KB 393
asttokens-2.0.5 | pyhd3eb1b0_0 20 KB
[...]
Annexe B. Installation de Python B.3. Utilisation de conda pour installer des modules complémentaires
Une fois que les versions des paquets ont été déterminées, conda vous demande confirmation avant de démarrer
le téléchargement. Tapez y puis appuyez sur la touche Entrée pour confirmer. S’en suit alors le téléchargement et
l’installation de tous les paquets (cela prendra quelques minutes) :
Une fois que tout cela est terminé, vous récupérez la main dans le shell :
[...]
Downloading and Extracting Packages:
mkl-2023.1.0 | 171.5 MB | #
###################################################################################################################
| 92%
Preparing transaction: done
Verifying transaction: done
Executing transaction: done
$
Si aucune erreur ne s’affiche et que vous récupérez la main dans l’interpréteur, bravo, ces modules sont bien installés.
Quittez l’interpréteur Python en tapant la commande exit() puis en appuyant sur la touche Entrée.
Vous êtes de nouveau dans le shell. Nous allons maintenant pouvoir tester Jupyter. Tapez dans le shell :
$ jupyter lab
Cette commande devrait ouvrir votre navigateur internet par défaut et lancer Jupyter :
Pour quitter Jupyter, allez dans le menu File puis sélectionnez Quit. Vous pourrez alors fermer l’onglet de Jupyter.
Pendant ces manipulations dans le navigateur, de nombreuses lignes ont été affichées dans l’interpréteur :
(base) PS C:\Users\Pat> jupyter lab
[I 18:26:05.544 LabApp] JupyterLab extension loaded from C:\Users\Pat\Miniconda3\lib\site-packages\
jupyterlab
[I 18:26:05.544 LabApp] JupyterLab application directory is C:\Users\Pat\Miniconda3\share\jupyter\lab
[...]
[I 18:27:20.645 LabApp] Interrupted...
[I 18:27:32.986 LabApp] Shutting down 0 kernels
(base) PS C:\Users\Pat>
Il s’agit d’un comportement normal. Quand Jupyter est actif, vous n’avez plus la main dans l’interpréteur et tous ces
messages s’affichent. Une fois que vous quittez Jupyter, vous devriez récupérer la main dans l’interpréteur. Si ce n’est
pas le cas, pressez deux fois la combinaison de touches Ctrl + C
Si tous ces tests ont bien fonctionné, bravo, vous avez installé correctement Python avec Miniconda ainsi que tous
les modules qui seront utilisés pour ce cours. Vous pouvez quitter le shell en tapant exit puis en appuyant sur la touche
Entrée et aller faire une pause !
Conseil
Si vous êtes débutant, vous pouvez sauter cette rubrique.
Comme indiqué au début de ce chapitre, pip 8 est un gestionnaire de paquets pour Python et permet d’installer des
modules externes. Pip est également présent dans Miniconda, donc utilisable et parfaitement fonctionnel. Vous pouvez
vous poser la question « Pourquoi utiliser le gestionnaire de paquets pip si le gestionnaire de paquets conda est déjà
présent ? ». La réponse est simple, certains modules ne sont présents que sur les dépôts pip. Si vous souhaitez les installer
il faudra impérativement utiliser pip. Inversement, certains modules ne sont présents que dans les dépôts de conda.
Toutefois, pour les modules classiques (comme NumPy, scipy, etc), tout est gérable avec conda.
Sauf cas exceptionnel, nous vous conseillons l’utilisation de conda pour gérer l’installation de modules
supplémentaires.
Si vous souhaitez installer un paquet qui n’est pas présent sur un dépôt conda avec pip, assurez vous d’abord que
votre environnement conda est bien activé (avec conda activate ou conda activate nom_environnement). La
syntaxe est ensuite très simple :
$ pip install nom_du_paquet
Si votre environnement conda était bien activé lors de l’appel de cette commande, celle-ci aura installé votre paquet
dans l’environnement conda. Tout est donc bien encapsulé dans l’environnement conda, et l’ajout de tous ces paquets
ne risque pas d’interférer avec le Python du système d’exploitation, rendant ainsi les choses bien « propres ».
Il faudra entrer votre mot de passe utilisateur puis valider en appuyant sur la touche Entrée.
Pour lancer cet éditeur, tapez la commande gedit dans un shell ou cherchez gedit dans le lanceur d’applications.
Vous devriez obtenir une fenêtre similaire à celle-ci :
On configure ensuite gedit pour que l’appui sur la touche Tab corresponde à une indentation de 4 espaces, comme
recommandée par la PEP 8 (chapitre 15 Bonnes pratiques en programmation Python). Pour cela, cliquez sur l’icône en
forme de 3 petites barres horizontales en haut à droite de la fenêtre de gedit, puis sélectionnez Préférences. Dans la
nouvelle fenêtre qui s’ouvre, sélectionnez l’onglet Éditeur puis fixez la largeur des tabulations à 4 et cochez la case Insérer
des espaces au lieu des tabulations :
Si vous le souhaitez, vous pouvez également cochez la case Activer l’indentation automatique qui indentera automati-
quement votre code quand vous êtes dans un bloc d’instructions. Fermez la fenêtre de paramètres une fois la configuration
terminée.
8. https://pip.pypa.io/en/stable/
9. https://notepad-plus-plus.org/download
sur Langage, puis à droite dans le carré Tabulations cochez la case Insérer des espaces en réglant sur 4 espaces
comme indiqué ci-dessous :
Ensuite, il est important de faire en sorte que Notepad++ affiche les numéros de ligne sur la gauche (très pratique
lorsque l’interpréteur nous indique qu’il y a une erreur, par exemple, à la ligne 47). Toujours dans la fenêtre Préférences,
dans la liste sur la gauche cliquez sur Zones d'édition, puis sur la droite cochez la case Afficher la numérotation
des lignes comme indiqué ici :
Sur les anciennes versions de Mac OS X (< 10.14), TextWrangler 10 était un éditeur de texte simple, intuitif et
efficace. Toutefois son développement a été arrêté car il fonctionnait en 32-bits. Il a été remplacé par BBedit 11 qui
possède de nombreuses fonctionnalités supplémentaires mais qui doit en principe être acheté. Toutefois, ce dernier est
utilisable gratuitement avec les mêmes fonctionnalités que TextWrangler, sans les nouvelles fonctionnalités étendues. Ne
possédant pas de Mac, nous nous contentons ici de vous donner quelques liens utiles :
• La page de téléchargement 12 ;
• La page vers de nombreuses ressources 13 utiles ;
• Le manuel d’utilisation 14 (avec toutes les instructions pour son installation au chapitre 2) ;
• Une page sur Stackoverflow 15 qui vous montre comment faire en sorte que l’appui sur la touche Tab affiche 4
espaces plutôt qu’une tabulation.
10. http://www.barebones.com/products/textwrangler/
11. https://www.barebones.com/products/bbedit/
12. http://www.barebones.com/products/bbedit/download.html
13. https://www.barebones.com/support/bbedit/
14. https://s3.amazonaws.com/BBSW-download/BBEdit_12.6.6_User_Manual.pdf
15. https://stackoverflow.com/questions/5750361/auto-convert-tab-to-4-spaces-in-textwrangler
Figure B.23 – Lancement d’un terminal depuis un répertoire donné avec Nautilus).
De façon similaire sous Windows, il existe deux astuces très pratiques. Lorsqu’on utilise l’explorateur Windows et que
l’on est dans un répertoire donné :
Figure B.24 – Lancement d’un powershell depuis un répertoire donné (étape 1).
16. https://fr.wikipedia.org/wiki/Environnement_de_d%C3%A9veloppement
17. https://code.visualstudio.com/
18. https://www.spyder-ide.org/
Figure B.25 – Lancement d’un powershell depuis un répertoire donné (étape 2).
puis on appuie sur entrée et le PowerShell se lance en étant directement dans le bon répertoire !
Deuxième astuce
En pressant la touche Shift et en faisant un clic droit dans un endroit de l’explorateur qui ne contient pas de fichier
(attention, ne pas faire de clic droit sur un fichier !). Vous verrez alors s’afficher le menu contextuel suivant :
Cliquez sur Ouvrir la fenêtre PowerShell ici, à nouveau votre Powershell sera directement dans le bon répertoire !
Vérification
La figure suivante montre le PowerShell, ouvert de la première ou la deuxième façon, dans lequel nous avons lancé
la commande ls qui affiche le nom du répertoire courant (celui dans lequel on se trouve, dans notre exemple D:\PAT\
Python) ainsi que les fichiers s’y trouvant (ici il n’y a qu’un fichier : test.py). Ensuite nous avons lancé l’exécution de
ce fichier test.py en tapant python test.py.
À votre tour !
Pour tester si vous avez bien compris, ouvrez votre éditeur favori, tapez les lignes suivantes puis enregistrez ce fichier
avec le nom test.py dans le répertoire de votre choix.
1 import tkinter as tk
2
3 racine = tk.Tk()
4 label = tk.Label(racine, text="J'adore Python !")
5 bouton = tk.Button(racine, text="Quitter", command=racine.quit)
6 bouton["fg"] = "red"
7 label.pack()
8 bouton.pack()
9 racine.mainloop()
10 print("C'est fini !")
Comme nous vous l’avons montré ci-dessus, ouvrez un shell et déplacez-vous dans le répertoire où se trouve test.py.
Lancez le script avec l’interpréteur Python :
$ python test.py
Si vous avez fait les choses correctement, cela devrait afficher une petite fenêtre avec un message « J’adore Python !
» et un bouton Quitter.
Figure B.26 – Lancement d’un powershell depuis un répertoire donné (étape 2bis).
21. http://pythontutor.com/visualize.html#mode=edit
22. https://play.google.com/store/apps/details?id=ru.iiec.pydroid3
Figure B.27 – Lancement d’un powershell depuis un répertoire donné (étape 3).
23. https://itunes.apple.com/us/app/pythonista-3/id1085978097