Tutoriel: Comment s’y prendre pour écrire des scripts en Python¶
Python est un langage de programmation avec lequel il est facile d’écrire des scripts. Ces scripts peuvent permettre d’automatiser des tâches répétées fréquemment à la main (envoyer des e-mails, analyser des fichiers, etc.) ou de développer des analyses complexes (remplacer une macro Excel).
Même si Python est un langage relativement simple à apprendre, il n’est pas aisé, en partant de zéro, de trouver la manière de s’y prendre pour l’intégrer dans son travail. L’objectif de ce tutoriel est donc principalement de proposer une méthode complète permettant l’écriture de scripts Python pouvant être exécutés facilement. Le tutoriel s’adresse à des débutants en programmation qui souhaitent intégrer Python dans leur méthode de travail.
Les informations fournies dans le tutoriel sont adaptées à la configuration suivante:
- Système d’exploitation (OS): Windows [7 ou 10]
- Gestionnaire de packages et d’environnement: Conda [≥ 4.7]
- Environnement de développement (IDE): Spyder [≥ 4]
- Langage: Python 3 [CPython ≥ 3.7]
- Packages: Numpy [≥ 1.16], pandas [≥ 0.25], Matplotlib [≥ 3.1]
A part Windows, tous les logiciels utilisés dans le tutoriel sont gratuits et open-source. Leurs auteurs et contributeurs sont remerciés pour l’ensemble de leur travail.
Introduction¶
Motivations¶
L’ordinateur est un outil qui nous facilite la vie. Le système d’opération (Windows dans notre cas) effectue une grosse partie du travail: il nous permet de contrôler la souris et le clavier, affiche des informations sur l’écran, nous donne la possibilité de créer des fichiers et de les lires, etc. Les logiciels qu’on installe en plus, comme la suite Microsoft Office nous facilitent encore plus la vie. On peut aisément formater un rapport, créer des tableaux, envoyer des emails, etc. Malgré tous ces outils, on se retrouve souvent à entreprendre des tâches répétitives. On va par exemple copier le contenu de dizaines de fichiers un à un dans un tableau Excel, télécharger quotidiennement sur un site internet les mêmes informations, etc.
Les outils comme Windows ou la suite Microsoft Office ne sont au final que des logiciels: ils se résument à (beaucoup) de lignes de code. La programmation permet de créer des outils, l’ordinateur n’est donc pas qu’un outil, c’est un outil avec lequel on peut créer d’autres outils.
La motivation est donc là, on va utiliser la programmation pour créer nos propres outils et venir compléter notre suite initiale d’outils. De cette manière, on limitera le temps passé à réaliser des tâches répétitives. On pourra aussi complémenter nos outils initiaux pour les améliorer.
Le langage qui va être utilisé dans ce tutoriel est Python, mais ce n’est pas le seul qu’on pourrait utiliser. On peut bien sûr mentionner VBA (dans Excel), ou encore le langage R qui est bien pratique lorsqu’il s’agit de faire des statistiques.
Python¶
Python
est un langage informatique qui a été conçu par
Guido van Rossum en 1990. C’est un langage très polyvalent.
Il est depuis des années très présent dans le Web
(Dropbox, Instagram, etc.) et
est récemment devenu le langage de la data science. Sa forte popularité
actuelle vient aussi de la facilité avec laquelle on peut l’aborder, sa
syntaxe est simple et épurée, ses outils intégrés sont nombreux,
en peu de temps on peut arriver à écrire du code qui va fonctionner et
être utile. Il attire beaucoup de débutants en programmation, mais
ne limite pas les développeurs expérimentés qui peuvent coder
des applications complexes.
La popularité du langage a des effets secondaires intéressants: la documentation (tutoriels, vidéos, etc.) disponible sur internet est incomparable, toutes nos questions vont trouver réponse.
Python
est un langage interprété. Cela signifie qu’il n’y
a pas besoin de le compiler (créer un .exe) pour exécuter du
code. On peut donc tester très rapidement un bout de code pour
voir s’il s’exécute rapidement. Ce cycle court favorise
l’apprentissage du code.
Python
est un langage de haut niveau et un langage dynamiquement
typé. Pour résumer, il travaille beaucoup pour nous et nous évite
d’écrire du code superflu, on peut aller droit à l’essentiel.
Python
ne désigne en fait qu’un langage, défini par un vocabulaire et
son lot de règles. Python
lui-même est écrit dans un autre
langage de programmation, le langage C. L’implémentation
de Python
en C
est nommée CPython. Il s’agit de
l’implémentation la plus utilisée
et c’est celle qui sera utilisée dans ce tutoriel.
Au cours du temps, les utilisateurs de Python
ont construit
beaucoup de packages codés dans ce langage. Le langage étant gratuit
et open-source, on retrouve beaucoup d’outils Python
qui sont
aussi gratuits et open-source. Le site PyPi
héberge des milliers et des milliers de ces outils, qui sont appelés
packages. Étant donné
qu’il est très facile de les installer, on a à notre disposition
une énorme boite à outils avec laquelle on peut construire tout
ce que l’on veut.
Scripts¶
Un script est un programme relativement simple contenu dans un fichier texte. On peut écrire un script qui va:
- analyser les fichiers d’un dossier et de ses sous-dossiers,
- zipper des fichiers,
- supprimer des fichiers,
- analyser le contenu d’un ou plusieurs fichier(s),
- modifier le contenu d’un fichier texte,
- convertir le format d’un fichier (image, vidéo, etc.),
- gérer l’exécution d’autres programmes,
- générer des fichiers PDF,
- récupérer des données sur internet,
- envoyer des emails,
- etc.
Comme on peut le voir, toutes ces tâches peuvent être réalisées
manuellement. Mais on peut aussi toutes les programmer en Python
!
Contenu du tutoriel¶
Environnement de travail¶
- Où enregistrer son code? Comment installer un package?
- Comment avoir plusieurs versions de
Python
?
Toutes ces questions peuvent se poser même lorsqu’on sait coder
en Python
. La configuration d’un environnement de
travail constitué de logiciels supports permet d’adresser
chacune de ces questions.
Installation¶
Une méthode est proposée pour installer les programmes
nécessaires à l’écriture de scripts en Python
.
Découverte de Python¶
Ici Python
est introduit rapidement. Ses concepts
les plus abordables sont présentés, spécialement ceux
qui peuvent être utiles pour écrire un simple script.
Les librairies Numpy
, pandas
et
Matplotlib
, incontournables dans l’écosystème
scientifique de Python, sont aussi rapidement introduites.
Écriture et exécution d’un script¶
Ici on présente comment s’y prendre pour écrire un script
en Python
et comment l’exécuter. Cette dernière
tâche n’est pas aussi facile qu’exécuter un fichier
.exe
sur Windows, il faut donc savoir se l’approprier.
Exercices¶
Des exercices d’application sont proposés afin d’utiliser les outils présentés dans le tutoriel ainsi que les concepts du langage et certaines de ses librairies:
- Récupération d’informations dans un fichier et analyse,
- Ajout de texte à un fichier PDF.
Environnement de travail¶
Mettre en place et savoir utiliser correctement son environnement de travail Python est essentiel. Celui-ci comprend:
- Internet: bien connaître les sites majeurs (StackOverflow, GitHub, etc.) permettant de récupérer des informations, du code, de résoudre les bugs rencontrés et trouver des ressources pour apprendre Python.
- Invite de commande (Command Prompt): même si l’usage de cet utilitaire Windows n’est ni intuitif ni esthétique, son maniement basique se révèle être bien utile pour le contrôle de Python et de ses utilitaires. (On pourra aussi utiliser PowerShell ou Terminal).
- Gestionnaire d’environnement (Environment Manager): conda permet de gérer les différentes versions de Python et des dépendances dont nous avons besoin pour un exécuter un script Python.
- Gestionnaire de package (Package Manager): conda permet aussi de gérer l’installation de packages; pip est un utilitaire effectuant la même chose et très présent dans le monde Python.
- Environnement de développement (IDE pour Integrated Development Environment): Spyder est un IDE plutôt taillé pour des applications scientifiques, certaines de ses fonctionnalités le rendent particulièrement appropriés pour l’apprentissage de Python et l’écriture rapide de scripts.
- Terminal Python: du code Python peut être exécuté directement dans un terminal en mode interactif, ligne par ligne, cela permet de tester rapidement des petits morceaux de code (copier/coller depuis un site internet par exemple) pour répondre aux questions qu’on peut se poser lorsqu’on apprend le langage ou lorsqu’on écrit un script.
Internet¶
Stack Overflow¶
Des recherches Google vont très souvent aboutir à des pages d’un site de questions/réponses appelés Stack Overflow (SO). Python étant un langage populaire, la plupart des questions que vous allez vous poser ont déjà été posées sur ce site (si ce n’est pas le cas, vous vous posez peut-être une mauvaise question).
La communauté SO est importante, ses membres incluent des programmeurs expérimentés qui passent du temps à rédiger des réponses claires et complètes. Surtout lorsque l’on débute (mais pas seulement), il est très fréquent d’avoir recours à ce site pour trouver rapidement une information précise. Il est même possible de copier directement le code que l’on voit sur le site dans un script ou interpréteur Python pour le tester. Ainsi, il est possible d’élaborer un script en faisant des copier/coller de codes postés sur SO. Cette pratique (« stackoverflow programming ») est très efficace mais doit être utilisée avec parcimonie, il est en effet important de comprendre un minimum le code que l’on utilise.
Voici un exemple de question/réponse trouvé sur SO après avoir cherché
dans Google python slice notation
. Le nombre 2886
à gauche est un
indicateur de la qualité de la question. En bas à droite, on peut voir que
la question a été posée en 2009 par l’OP (Original Poster) Simon
.

Les réponses affichées sous la question sont classées par défaut par nombre
de votes. Ainsi, la première réponse que l’on voit est celle que la communauté
a le plus apprécié. Ici, 3894
personnes de la communauté ont jugé la
réponse utile. Le symbole coché indique que la réponse a été acceptée par l’OP.

GitHub¶
Avant d’introduire GitHub, il est nécessaire de comprendre ce qu’est Git. Il s’agit d’un logiciel open-source qui permet de faire de la gestion de versions de projet informatique. La gestion de versions que vous pratiquez certainement consiste à changer le nom d’un fichier (document Word par exemple) de v1 à v2, v2 à v3, etc. Git fournit une méthode plus élaborée et pratique qui permet de suivre/archiver/contrôler précisément l’évolution d’un projet dont le nombre de contributeurs peut être très grand (des milliers de personnes). Une des particularités de Git est qu’il s’agit d’un système distribué: un contributeur peut travailler et enregistrer les changements qu’il a apportés au projet chez lui, puis les enregistrer au niveau distribué du système, comme un serveur internet.
Même s’il existe d’autre logiciel de gestion de versions (Subversion, Mercurial), Git est de loin le plus utilisé actuellement.
GitHub est un site internet qui utilise Git pour assurer la gestion de versions des projets informatiques qu’il héberge. En plus de cela, il propose des options et des aides au développement de ces projets. A l’instar de Git, GitHub est le site d’hébergement de projets informatique le plus utilisé (GitLab, BitBucket, SourceForge). Il permet à tout un chacun d’héberger gratuitement un projet informatique en ligne.
Énormément de projets open-source sont hébergés sur GitHub, dont Python lui-même, conda, pip, Spyder, pandas, QGIS, etc.
L’intérêt de ce site pour l’écriture d’un de nos scripts est double:
- Découvrir facilement de nombreux projets afin de voir si ce que l’on recherche à faire avec notre script n’a pas déjà été écrit et publié. Si oui, on pourrait réutiliser le code en question (après avoir vérifié la licence).
- Avoir accès à du code Python de bonne qualité afin d’apprendre et de s’en inspirer.
L’exemple ci-dessous montre le code inclus dans le module timeit.py
de
Python. On veut voir en haut à droit le nombre 26,162
de Stars que le
projet a. Ce nombre, élevé, correspond au nombre de personnes qui suivent/
sont intéressés par le projet. Il est un bon indicateur de sa qualité. A
l’inverse, un projet avec quelques Stars seulement pourrait contenir du code
de moins bonne qualité.

NB: Le module timeit.py
a eu 15 contributeurs différents depuis sa
création. Le premier contributeur, dont l’image est la première sur la
gauche, est Guido van Rossum, le créateur de Python.
Documentations officielles¶
Documentation de Python¶
La documentation officielle de Python est une ressource importante et relativement facile à aborder. Elle est disponible pour les différentes versions de Python et en plusieurs langues, incluant le Français. Il est toutefois recommandé de privilégier la version Anglaise, pour se familiariser avec les termes techniques et faciliter les recherches Google.

Packages¶
Les packages (librairies) du monde Python sont très souvent bien documentés. Ceci est particulièrement vrai pour les packages scientifiques. Il suffit alors de visiter leur site pour trouver des informations précises sur la manière de les installer/utiliser. On trouvera notamment des guides de démarrage rapide, des directives détaillées sur les fonctions que ces packages contiennent et des exemples d’applications.
Cheat Sheets¶
Les Cheat Sheets sont des fiches qui synthétisent les informations les plus importantes afin d’utiliser un logiciel, un package, etc. Voici une courte liste de Cheat Sheets:
- pandas
- conda
- matplotlib
- etc.
Tutoriels¶
Les tutoriels (tutorials) sont omniprésents sur internet. Lorsqu’on commence à utiliser un nouveau logiciel/package, il est recommandé de réaliser d’abord un tutoriel dédié. Cela permet de comprendre le fonctionnement général de l’outil et d’en maîtriser les bases. Même s’il est des fois difficile d’accorder du temps à cette étape, elle est bénéfique sur le long terme.
Une difficulté, toutefois, est de s’y retrouver dans la multitude des tutoriels à disposition. La popularité de Python est telle que le nombre de blogs/sites y consacrant des sujets est unique. Ceci est à la fois une force du langage et de son écosystème, mais peut être problématique lorsqu’on a besoin d’une réponse rapide.
Pour trouver un bon tutoriel, il est recommandé de chercher des tutoriels:
- en Anglais: même si un tutoriel en Français peut constituer une douce entrée en matière, il y a plus de chance de trouver des tutoriels en Anglais plus complets et plus à jour. Comme le langage Python est lui-même en Anglais, que le vocabulaire des packages est aussi en Anglais, l’apprentissage par des tutoriels en Anglais facilite leur utilisation sur le long terme.
- le plus récent possible: tout change très vite dans le monde informatique, une fonction d’un package peut être supprimée (deprecated) entre deux versions, rendant obsolète une partie de la documentation non-officielle disponible sur internet. Il faut faire particulièrement attention à ne pas suivre de tutoriel en Python 2.
- faciles à lire: souvent, les tutoriels esthétiques sont ceux dont le contenu est de qualité.
Voici deux sites particulièrement intéressants:
- Official Python Tutorial: le tutoriel de la documentation officielle de Python, il s’agit d’une source sûre.
- Real Python: des tutoriels agréables à lire, complets, pour tous niveaux.
Notebooks¶
Les notebooks dans l’écosystème Python sont le plus connus sous la forme des Jupyter Notebooks. Ce sont des fichiers qui contiennent à la fois du texte, qu’on peut mettre en forme facilement, et du code, qu’on peut exécuter facilement et dont le résultat (tableau, image, etc.) est affiché directement sous le code. Ces fichiers sont visualisables et éditables directement dans un navigateur (Chrome, Firefox, etc.). Ils constituent donc un très bon moyen pour:
- présenter une librairie sous la forme d’un tutoriel,
- analyser des données.

Les Jupyter Notebooks ont les caractéristiques suivantes:
- Ils ont l’extension .ipynb qui provient du nom IPython Notebook, le nom initial de ces notebooks,
- Pour les ouvrir, il faut utiliser l’application du même nom Jupyter Notebook, ou sa nouvelle version JupyterLab. L’ouverture basique d’un notebook à partir d’une de ces applications entraîne le démarrage d’un serveur local. Ce serveur déploie le notebook de telle manière qu’il est maintenant possible de le lire et l’éditer depuis un navigateur (Chrome, Firefox, etc.). En même temps, l’application démarre un kernel, qui est un programme permettant d’exécuter le code inclut dans le notebook. Le kernel par défaut est IPython, il permet d’exécuter du code Python.
- Ils sont divisés en cellules. Ces cellules peuvent contenir du texte écrit dans le langage de balise Markdown. Ce langage permet de mettre facilement en forme le document (titre, police en gras, etc.). Les cellules peuvent aussi contenir du code Python. Lorsque le code est exécuté, le résultat (output) est affiché juste en dessous de la cellule.
Les notebooks sont souvent partagés directement sur GitHub, plusieurs approches sont possibles afin de les utiliser:
- On peut directement télécharger les notebooks sur GitHub. Il vaut mieux télécharger le répertoire (repo pour repository) complet, si les notebooks s’appuient sur des données d’entrée enregistrées dans d’autres dossiers. Il sera peut-être nécessaire de créer un nouvel environnement conda et d’installer les packages nécessaires au fonctionnement des notebooks.
- Le logiciel git permet de télécharger facilement un répertoire
sur GitHub. Il faut d’abord l”installer.
La commande
git clone https://github.com/username/repo
va télécharger le contenu du répertoire dans le dossier où elle a été exécutée. A nouveau, il sera peut-être nécessaire d’installer un environnement conda spécifique. - binder est un site qui héberge des notebooks et permet de les exécuter directement sur internet, il n’est donc pas nécessaire d’installer quoi que ce soit. Si une petite image (badge) binder est visible sur un répertoire GitHub, cela veut dire que les notebooks qu’il contient sont hébergés sur binder.
Pour ouvrir un notebook, il faut exécuter la commande jupyter notebook
(ou jupyter lab
) dans le dossier où se trouve le ou les fichiers .ipynb.
Autres ressources¶
- Youtube: Il y a énormément de vidéos concernant Python sur Youtube. Certaines sont sous la forme de tutoriels, on voit ce que la personne code en direct pendant qu’on entend les explications. D’autres sont des enregistrements de conférence (PyCon, PyData, etc.).
- Certains sites (DataCamp) permettent d’exécuter du code directement sur internet, sans avoir besoin d’installer quoi que ce soit sur son PC.
- Livres: il y a beaucoup de livres sur Python et son écosystème. Un qui pourra nous intéresser est Automating the Boring Stuff with Python, qui est visible gratuitement.
- Practical Business Python: un blog avec des articles sur la manière d’intégrer Python dans les entreprises.
- Cours en français de l’Université Paris Diderot.
Command Prompt¶
La Command Prompt est un programme qui permet d’interagir en ligne
de commande avec Windows. Pour l’ouvrir, il faut appuyer sur la touche
Windows
, taper cmd
, et appuyer sur la touche Entrée
.

Le texte à gauche du symbole >
indique dans quelle dossier la Command
Prompt est active. Le programme dispose de commandes internes, il suffit
de taper help
pour en voir une liste. Pour accéder à l’aide d’une
commande quelconque, il faut rajouter l’option /?
après son nom (e.g.:
cd /?
).
Seules quelques commandes nous seront utiles. La première est cd
(pour
Change Directory), elle
permet de changer le répertoire actif. Voici comment l’utiliser:
cd C:\ProgramData
pour aller dans le répertoire ProgramData,cd ..
pour aller dans le répertoire Users,..
signifie le répertoire parent (« le répertoire du dessus »),.
signifie le répertoire en cours/actif,cd ..\..
pour aller à la racine du disque C:,cd "C:\Program Files"
pour aller dans le répertoire Program Files, les guillemets sont nécessaires pour les liens qui incluent des espaces,cd /D E:\Data
pour aller dans le dossier Data du disque E: (cd E:\Data
ne fonctionne pas, il faut rajouter /D).
Note
Après avoir tapé cd
ou cd Documents\
, appuyer sur
la touche Tab
plusieurs fois pour faire défiler les dossiers
disponibles (dans le deuxième cas, les dossiers disponibles dans
Documents sont affichés). L’utilisation de la touche Tab
(autocompletion) est possible après n’importe quelle commande, elle
permet aussi de faire défiler les fichiers inclus dans le dossier.
La deuxième est dir
(pour DIRectory), elle permet d’afficher la
liste des dossiers des fichiers et dossiers présents dans le dossier
en cours. La commande dir /b *.py > pythonfiles.txt
liste tous les
fichiers situés dans le dossier en cours et
dont l’extension est .py (*
est le symbole wildcard,
il remplace ici le nom du fichier), et les enregistre dans le
fichier texte pythonfiles.txt.
La troisième est cls
(pour CLear Screen), elle permet de réinitialiser
l’écran.
Lorsqu’un programme est en train d’être exécuté depuis la Command Prompt,
on peut l’arrêter en appuyant sur Ctrl + C
.
Astuce
Après avoir exécuté plusieurs commandes, on peut utiliser les flèches
du haut et du bas pour parcourir l’historique des commandes exécutées.
On peut aussi appuyer sur la touche F7
pour afficher une fenêtre
de l’historique et exécuter à nouveau une commande en la sélectionnant.
Les autres touches FX
permettent d’effectuer des actions similaires.
Voir ici
pour une liste des raccourcis disponibles.
La Command Prompt permet aussi d’exécuter des programmes présents dans
le PATH. Le PATH est une variable d’environnement qui liste un ensemble de dossiers.
Lorsque l’on exécute quelque chose qui n’est pas
une commande de la Command Prompt, le programme va chercher dans
les dossiers du PATH s’il existe un fichier exécutable (.exe, .bat
…) du même nom pour l’exécuter. S’il n’est pas trouvé, le message d’erreur suivant
s’affiche: 'blob' n'est pas reconnu en tant que commande interne ou externe, un programme exécutable ou un fichier de commandes.
Astuce
Pour afficher les dossiers dans le PATH, il suffit d’exécuter
echo %PATH%
.
Comme notepad
n’est pas une commande de la Command Prompt, le programme
cherche dans le PATH, trouve le fichier notepad.exe dans un
des dossiers d’installation de Windows, et l’exécute, ce qui a pour effet
d’ouvrir le bloc-notes.

Afin de savoir où se trouve l’exécutable notepad.exe, il suffit
d’exécuter where notepad
ce qui affiche le lien absolu vers le ou
les fichiers notepad.exe trouvés dans le PATH. where étant
lui-même un exécutable, where where
indique sa localisation.
Pour lancer la Command Prompt directement dans un dossier ouvert avec l’explorateur de fichiers, on peut:
- taper
cmd
dans la barre d’adresse et appuyer sur la toucheEntrée
. Cette méthode est intéressante car elle fonctionne avec tous les exécutables du PATH de Windows.calc
ouvrera la calculatrice.python
(si installé globalement) lancera l’interpréteur Python dans le dossier courant, - appuyer sur la touche
ctrl
, faire un clic droit dans la fenêtre de l’explorateur pour ouvrir un menu contextuel et cliquer sur Ouvrir avec l’invite de commande ici.
Astuce
Même si la Command Prompt est suffisante pour ce qu’on a à faire, on peut utiliser à la place le terminal PowerShell, qui est plus récent, plus complet et un peu plus coloré. Le logiciel Terminal disponible sous Windows 10 peut aussi être installé pour faciliter l’utilisation de terminaux (Command Prompt, PowerShell, etc.).
conda et pip¶
Leurs objectifs¶
Python est un langage qui est doté d’un riche écosystème de packages. Quand on code en Python, on est alors amené à réutiliser ces packages afin de ne pas réinventer la roue. Mais ce n’est pas si simple que cela pour les raisons suivantes:
- il faut pouvoir installer facilement ces packages,
- il existe plusieurs versions de Python, certains packages ne fonctionnent donc pas sur la ou les dernières versions disponibles,
- les packages ont eux aussi plusieurs versions et dépendent les uns des autres (Pandas, par exemple, dépend de Numpy et de Matplotlib), il est nécessaire de s’assurer qu’on installe la bonne combinaison de versions,
- certains packages, notamment les packages scientifiques, ne sont pas écrits qu’en Python (pure Python), mais aussi en d’autres langages de plus bas niveaux (C, Fortran, etc.) afin d’accélérer les calculs: cela complexifie la distribution et l’installation de ces packages-là.
conda est un logiciel qui permet d’adresser l’ensemble de ces difficultés:
- il permet d’installer des packages Python en les téléchargeant sur internet,
- il peut créer des environnements virtuels. On peut ainsi créer un environnement dans lequel Python 3.7 est installé, et un autre dans lequel c’est Python 3.6 qui est installé. Ces deux environnements sont totalement isolés l’un de l’autre. conda comprend par défaut un environnement virtuel appelé base,
- il vérifie que les versions des packages installés sont bien cohérentes les unes avec les autres. Comme on peut avoir un grand nombre de packages, cette vérification prend parfois du temps,
- les packages téléchargeables par conda sont en fait déjà pré-installés (build), leur installation est donc directe même si les packages sont complexes (comme Numpy).
D’autres outils permettent de gérer des environnements virtuels (e.g. virtualenv), l’avantage de conda est que plusieurs fonctionnalités sont encapsulées dans un seul et même programme, c’est pourquoi nous l’utiliserons principalement.
Note
conda ne permet pas seulement d’installer des packages Python. On peut installer le langage R et ses propres packages. On peut aussi installer d’autres logiciels, comme QGIS.
Note
Le téléchargement d’un package avec conda regarde par défaut dans la channel default si le package recherché se trouve dans un des repositories vers laquelle cette channel pointe. La channel conda-forge est une alternative intéressante, elle est maintenue par une communauté d’utilisateurs et est celle qui comprend le plus grand nombre de packages. On peut configurer conda pour qu’il télécharge les packages uniquement depuis la channel conda-forge.
pip est l’installeur officiel de packages de Python. Il prédate conda et permet de télécharger des packages sur le site PyPi. Il s’agit du site officiel de dépôt de package Python. Tous les packages présents sur ce site sont téléchargeables avec pip. Il y en a aujourd’hui (07/2020) plus de 245 000. Lorsqu’un nouveau package est installé avec pip, pip ne vérifie pas aussi précisément que conda que toutes les relations de dépendance entre l’ensemble des packages installés sont bien satisfaites.
Installation¶
conda est un logiciel gratuit et open-source distribué par la société Anaconda. Anaconda est un nom qui a plusieurs usages. Il désigne donc cette entreprise, qui s’appelait à l’origine Continuum Analytics. Elle a développé Anaconda et Miniconda, qui sont des distributions qui incluent plusieurs logiciels et librairies. Anaconda est la distribution complète, elle intègre directement conda et une interface graphique Anaconda Navigator. L’environnement base, qui inclut Python, comprend déjà de nombreux packages utiles pour la data science. Installer la distribution Anaconda permet donc d’obtenir rapidement un outil de travail permettant d’écrire des scripts en Python. Cette distribution est toutefois lourde. Miniconda est le pendant minimal d”Anaconda, cette distribution-là comprend conda, Python et c’est à peu près tout. On installe ensuite manuellement les packages que l’on souhaite utiliser, de préférence dans de nouveaux environnements. L’entreprise Anaconda se charge aussi d’héberger le site internet à partir duquel les packages sont téléchargés par conda.
- Installation de **Miniconda**
- Installation d”**Anaconda**
pip fait généralement partie de l’installation de Python, il n’y a donc aucune action particulière à effectuer pour l’installer.
Utilisation¶
Nous utiliserons conda de préférence lorsque le package à installer est disponible (sur la channel default ou conda-forge). Lorsque ce n’est pas le cas, nous utiliserons pip pour installer le package depuis PyPi (pip fonctionne assez bien directement dans conda).
Pour vérifier si un package est téléchargeable avec conda, il suffit de le chercher sur le site d”Anaconda. De la même manière, on peut voir si un package est disponible au téléchargement avec pip en le cherchant sur le site de PyPi. Si un package n’est pas disponible sur ces sites mais que le projet existe bien sur GitHub, on peut l’installer avec pip (voir la documentation).
Il est préférable d’utiliser conda depuis l”Anaconda Prompt qui est installée automatiquement avec Anaconda ou Miniconda. Cet utilitaire fonctionne comme la Command Prompt (c’est en fait la Command Prompt configurée pour conda), sauf qu’à son lancement l’environnement virtuel base est automatiquement activé. Cela rend tous les programmes installés dans cet environnement (Python, pip, etc.) disponibles.
Note
Suivant la configuration de l’installation d”Anaconda ou Miniconda,
il est possible que la commande conda
soit disponible directement depuis
la Command Prompt. Cela est le cas si conda est ajouté au PATH. La
commande conda init
permet de configurer la Command Prompt après installation
pour que l’environnement base de conda soit activé automatiquement au lancement
de la console.
conda est un programme qui s’utilise en ligne de commande. Le
principe est d’écrire conda
suivi par une commande et les arguments/
paramètres de cette commande. Pour obtenir l’aide de conda,
on peut exécuter conda -h
ou conda --help
. Pour obtenir l’aide
d’une commande particulière, on peut exécuter conda commande -h
ou
conda commande --help
(ou commande est le nom d’une commande conda,
comme list qui s’exécutera donc conda list
).
Note
-h
et --help
sont des flags, ils déclenchent un comportement
particulier de conda, celui d’afficher l’aide. Il est fréquent
de rencontrer des utilitaires en ligne de commande qui utilisent ce
même flag pour afficher l’aide.
Voici une liste de commandes conda utiles:
conda update conda
met à jour conda,conda info --envs
affiche la liste des environnements installés, l’astérisque signale l’environnement actuellement activé,conda create --name datascience python=3.7 pandas
crée un environnement nommé datascience dans lequel on souhaite installer la version 3.7 de Python ainsi que pandas, conda vérifie si cette combinaison est possible, et l’installera si oui, en sélectionnant les versions compatibles les plus à jour,conda activate datascience
active l’environnement datascience,conda install matplotlib
installer matplotlib dans l’environnement actif (datascience)conda list
affiche tous les packages installés dans l’environnement actif,conda env export > environment.yml
exporte la liste des packages installés et leur version dans le fichier environment.yml, il s’agit d’une manière simple d’enregistrer la configuration d’un environnement, pour pouvoir le partager à d’autres personnes par exemple,conda remove pandas
supprime pandas et les packages dont il dépend s’ils ne sont plus utiles à d’autres packages dans l’environnement,conda deactivate
désactive l’environnement actif (fonctionne aussi pour l’environnement base),conda remove --name datascience --all
supprime l’environnement datascience ainsi que les fichiers d’installation.
Astuce
Les fichiers environment.yml contiennent tous les détails
nécessaires à conda pour créer un environnement avec tous les
packages tels que spécifiés dans le fichier. Si l’on dispose
d’un tel fichier, il suffit d’exécuter
conda env create -f environment.yml
pour recréer l’environnement.
La liste des commandes pip qu’on utilisera est plus courte:
pip install pandas
installe pandas,pip uninstall pandas
désinstalle pandas.
L’éditeur Spyder¶
Astuce
Par défaut, Spyder est en français. On peut changer le paramétrage pour le configurer en anglais, cela facilite les recherches Google.
Présentation¶
Spyder peut être installé directement avec conda en exécutant conda install spyder
On privilégiera une installation de Spyder dans un environnement dédié, avec la commande
conda create -n spyder_env python=3.7 spyder pandas
.
Une fois installé, on le lance en cliquant sur le raccourci ajouté
dans la barre de lancement de Windows, ou en exécutant spyder
dans
l’environnement virtuel dans lequel il a été installé.

La fenêtre qui s’ouvre est composée de trois volets principaux.
L”éditeur (editor) se trouve à gauche. C’est dans ce panneau qu’on pourra écrire et enregistrer les scripts Python. On peut en fait y ouvrir n’importe quel type de fichier texte.
En bas à droite se trouve la console IPython. IPython est un terminal Python interactif, plus complet que le terminal Python de base. On peut donc directement exécuter du code dans cette console. Lorsqu’on exécute du code écrit dans l’éditeur, ce code-là est en fait directement exécuté par la console IPython ouverte.

Lorsqu’on écrit du code dans l’éditeur, des suggestions et des aides apparaissent automatiquement.


La panneau History (historique) se trouve dans le même volet. Il
contient l’historique des commandes qui ont été exécutées par la console.
(Ici les deux lignes dans l’éditeur ont été exécutées en les
sélectionnant et en appuyant ensuite sur la touche F9
).

En haut à droite se trouvent les panneaux Help (aide), Files (explorateur de fichiers), Plots (figures), Find (rechercher et remplacer) et Variable explorer (explorateur de variable).
Les panneaux Variable explorer et Plots sont particulièrement intéressants.
Le Variable explorer permet d’inspecter les objets (i.e. variables) qui
sont sont actifs dans la session actuelle. Un tableau affiche leur nom,
leur type, leur taille et leur valeur. On peut par exemple explorer les données
que contient un DataFrame
pandas. Dans l’exemple ci-dessous, on
peut voir que la string world est visible dans le panneau.

Le panneau Plots permet de voir l’historique des figures créées dans la session active. Dans l’exemple ci-dessous, deux figures sont générées avec Matplotlib, on peut les faire défiler dans le panneau et les enregistrer sur le disque.

La barre d’adresse en haut à droite permet de configurer le dossier en cours, qui est aussi appelé dossier de travail. Par exemple, cela est utile lorsqu’on souhaite ouvrir un fichier avec un lien relatif.

Astuce
Pour définir un dossier de travail en Python directement, on peut
écrire les deux instructions suivantes:
import os; os.chdir(r"path\to\workingdirectory")
Raccourcis¶
Voici une courte liste de raccourcis Spyder bien utiles:
Context | Name | Shortcut |
---|---|---|
editor | run selection | F9 |
run | F5 | |
editor | copy line | Ctrl + Alt + [Up/Down] |
editor | move line [up/down] | Alt + [Up/Down] |
editor | delete line | Ctrl + D |
editor | toogle comment | Ctrl + 1 |
editor | indent / unindent | Tab / Shift + Tab |
editor | code completion | Ctrl + Space |
switch to console | Ctrl + Shift + I | |
switch to editor | Ctrl + Shift + E | |
console | code completion | Tab |
console | clear console | Ctrl + L |
console | array builder | Ctrl (+ Alt) + M |
Astuce
Le raccourci pour commenter n’est pas très pratique, on
peut par exemple le changer pour Ctrl + :
.
IPython¶
Le terminal IPython est doté de fonctionnalités agrémentées par
rapport au terminal Python classique.
Parmi elles les commandes magiques (magic commands)
qui sont à exécuter dans la console précédées du symbole %
.
%who
et%whos
affiche la liste des objets présents dans le namespace (plus complet que l’explorateur de variable).%timeit sum(range(1_000_001))
calcule le temps moyen qu’il faut pour calculer une somme de zéro à un million.%reset
réinitialise le namespace (supprime la référence aux objets créés), cela a pour effet de « vider » l’explorateur de variable.%lsmagic
affiche la liste complète des commandes magiques.
On peut obtenir l’aide d’un objet en rajoutant ?
(ou ??
pour
plus d’informations) après son nom. Par exemple, print?
va afficher
l’aide de la fonction print
.
On peut utiliser les commandes de la Command Prompt en les précédant
d’un !
. Par exemple, !dir
est équivalent à dir
dans la
Command Prompt.
Astuce
%quickref
affiche toutes les commandes spéciales mentionnées
ci-dessus.
Workflow¶
Lorsqu’on exécute du code écrit dans l’éditeur (avec F5
pour
exécuter la totalité du script ou F9
pour n’exécuter qu’une ou
plusieurs lignes), le code est exécuté dans la console IPython.
Les objets créés (variables, fonctions, etc.) sont toujours vivants,
ils sont réutilisables depuis la console IPython. Ils le sont donc
aussi depuis l’éditeur, le code de celui-ci étant exécuté dans la
console. Cette mécanique permet d’élaborer un script de manière souple
et itérative. Voici un exemple de workflow avec l’éditeur et la console:
- On écrit le début du code dans l’éditeur et on l’enregistre.
- En parallèle, on peut s’aider de la console pour construire le code, en vérifiant comment s’exécute une fonction, le format d’une donnée, etc.
- Si on le souhaite, le code qui vient d’être exécuté dans la console peut être collé vers l’éditeur après l’avoir copié dans la console (on peut aussi le copier depuis l’historique).
- Lorsque le code dans l’éditeur correspond à un bloc logique, on peut
l’exécuter entièrement avec
F5
. On peut aussi l’exécuter ligne par ligne avecF9
. - Le résultat est alors accessible et peut être inspecté depuis la console (et l’explorateur de variable). On peut alors vérifier si le code a bien fonctionné. En l’exécutant ligne par ligne, on peut voir en direct dans l’explorateur de variable la création des objets et l’évolution de leur valeur.
Astuce
On peut séparer des blocs de code dans l’éditeur en écrivant
#%%
sur une ligne. Le code situé sous cette ligne et entre
la prochaine ligne #%%
est une cellule (cell). Le code
d’une cellule peut être exécuté en appuyant sur Ctrl + Enter
.
Shift + Enter
effectue la même opération puis déplace
le curseur à la cellule suivante. Ce fonctionnement se rapproche de celui
d’un notebook.
La mécanique éditeur/console et la possibilité d’exécuter seulement
une partie du code (F9
) permet aussi de debugger le code
manuellement et facilement.
Indication
Spyder intègre aussi un debugger interne. Il peut être utile
d’apprendre à se servir de cet outil car il évite d’avoir à modifier
le code pour le débugger (on rajoute souvent des print()
dans le
code lorsqu’on l’inspecte manuellement) et permet de débugger plus
facilement du code complexe.
Terminal interactif Python¶
L’interpréteur Python, qui est le programme capable d’exécuter
un script écrit en Python, a aussi un mode interactif. Pour lancer
l’interpréteur dans ce mode, il suffit d’ouvrir un terminal
Anaconda Command Prompt et taper python
.
Du code Python peut maintenant être entré après le symbole >>>
et exécuter en appuyant sur Enter
. En d’autres mots, on vient
d’allumer le moteur de Python, il attend maintenant nos instructions
pour pouvoir les exécuter.
Note
Le mode interactif de l’interpréteur Python est aussi appelé REPL, pour Read-Eval-Print Loop.

L’exemple ci-dessous
montre l’exécution de l’instruction print("hello world")
. Le
résultat de cette instruction est affiché directement en dessous. La
ligne du dessous affiche >>>
, indiquant qu’il est possible
de taper une nouvelle instruction.

Pour quitter l’interpréteur interactif, on peut taper quit()
ou
appuyer sur Ctrl + Z + Enter
.
L’interpréteur interactif Python a cependant quelques limites. Il n’affiche aucune couleur et n’a pas la fonctionnalité autocompletion dans Windows.
Un autre interpréteur interactif Python disponible est IPython.
Pour le lancer, il suffit de taper ipython
dans un terminal.
Au lieu du symbole `>>>`
, IPython affiche In [X]
pour
signaler la ligne où l’on peut écrire du code. On peut voir
que le code entré est colorisé, cela en facilite la lecture.

Lorsqu’on écrit du code dans ce terminal, l’appui sur la touche
Tab
montre les possibilités disponibles. On peut ensuite continuer
à appuyer sur Tab
(ou avec les flèches) pour naviguer dans les
propositions faites et appuyer sur Entrée
pour en sélectionner une.

Pour quitter l’interpréteur interactif IPython on tape quit()
.
L’interpréteur IPython peut aussi être lancé dans sa propre fenêtre.
Il suffit d’exécuter jupyter qtconsole
dans l”Anaconda Prompt.

Dans cette fenêtre, l’aide d’une fonction s’affiche lorsqu’on tape la parenthèse d’ouverture.

Il est possible d’afficher des figures directement dans la console (dans les deux premiers interpréteurs interactifs, les figures s’ouvrent dans une nouvelle fenêtre).

Installation¶
Dans un environnement scientifique, il est souvent recommandé d’installer Python et les packages que l’on souhaite utiliser grâce à conda. Il est possible d’installer deux versions différentes:
- Anaconda: Intègre conda avec un environnement (nommé base) contenant beaucoup de packages utiles pour la data science,
- Miniconda: Intègre seulement conda et python, il est donc beaucoup plus léger qu”Anaconda.
Il est recommandé dans ce tutoriel d’installer Miniconda et de l’utiliser de la manière suivante.
L’environnement base, qui est l’environnement créé par défaut dans lequel se trouve installé conda, doit
rester le plus simple possible. Il ne doit servir que pour conda lui-même, qu’on mettra à jour lorsque nécessaire
avec la commande conda update conda
à exécuter directement depuis cet environnement base. Si l’on souhaite installer d’autres packages (comme pandas), on
le fera dans un ou plusieurs environnement(s) dédié(s). On exécutera par exemple conda create -n data_analysis pandas
pour créer un environnement data_analysis incluant pandas, et conda create -n spyder spyder
pour
créer un environnement incluant Spyder. Cette approche permet de bien séparer les outils qu’on utilise,
de pouvoir les mettre à jour plus facilement, et évite donc bien des problèmes.
La procédure pour installer Miniconda, Spyder et créer des environnements conda est décrite ci-dessous.
Télécharger Miniconda, la version 64bits de Windows étant celle qu’il faudra vraisemblablement choisir. Lancer l’executable pour démarrer l’installation et garder les paramètres par défaut, sauf ceux ci-dessous. Même si avoir conda dans le
PATH
pourrait être pratique, désactiver cette option évite quelques problèmes potentiels.Chercher la console Anaconda Prompt dans le menu Démarrer et la lancer. On voit qu’elle s’ouvre dans l’environnement base qui est créé par défaut lors de l’installation. Dans cet environnement se trouve python, conda et quelques autres packages.
Dans la console Anaconda Prompt, exécuter la commande suivante pour installer Spyder dans un environnement dédié nommé spyder_env dans lequel se trouve la version 3.7 de Python. pandas est installé pour permettre au Variable Explorer de Spyder de visualiser des DataFrame. Plus d’information est disponible sur le Wiki de Spyder à ce sujet.
Dans la console Anaconda Prompt, exécuter la commande suivante pour installer un environnement avec les packages que l’on souhaite utiliser pour réaliser une tâche. L’exemple ci-dessous crée un environnement gis dans lequel vont être installés les packages geopandas et rasterio, plus leurs dépendances. Le package spyder-kernels permettra de connecter Spyder à cet environnement. Sans cela, il aurait été nécessaire de l’installer à nouveau.
Dans les options de Spyder, on paramètre l’interpréteur pour qu’il pointe vers la version de Python de l’environnement qu’on souhaite utiliser. Se référer à nouveau au Wiki de Spyder pour obtenir la manière de paramétrer Spyder pour qu’il fonctionne ainsi.
L’installation ci-dessus a les effets suivants:
- Des raccourcis pour la console Anaconda Prompt et l’IDE Spyder sont disponibles dans le menu Démarrer de Windows,
- Trois environnements (exécuter
conda info --envs
dans l”Anaconda Prompt pour lister les environnement installés) ont été créés: base, spyder_env et gis. - Spyder est paramétré de tel sorte qu’il est connecté à l’environnement gis et a donc accès aux packages qu’il contient tel que geopandas.
Attention
L’installation présentée ci-dessus est une option, il en existe d’autres. Il aurait par exemple été possible de:
- installer Anaconda au lieu de Miniconda. Anaconda contient un environnement base qui comprend une suite complète de packages dédiés à l’analyse de données. Cette suite est assez lourde, contient des packages que l’on n’utilisera pas forcément, et est difficile à mettre à jour,
- privilégier le channel conda-forge pour installer des packages depuis cette source-là qui en contient plus et qui sont parfois plus à jour,
- ajouter conda et python directement au
PATH
pour qu’ils soient disponibles directement depuis la Command Prompt classique de Windows sans passer par l”Anaconda Prompt. - exécuter la commande
conda init
pour modifier la Command Prompt de manière à ce qu’elle démarre toujours en activant l’environnement base.
Découverte de Python¶
Ce chapitre contient deux notebooks principaux:
- Introduction pas à pas de Python
- Numpy, pandas et Matplotlib en express
Le contenu de ces deux notebooks devrait être suffisant pour réaliser l’exercice.
Une version étendue du premier notebook est aussi disponible. Elle contient plus d’explications et aborde des concepts supplémentaires.
Les trois notebooks sont téléchargeables en scripts Python
(extension .py
) ici.
Les cellules initiales des notebooks sont reproduites
dans les fichiers .py
, leur début est marqué par
le symbole # %%
. Lorsqu’on ouvre ces fichiers dans Spyder, on
peut exécuter le code cellule par cellule comme dans un notebook.
Introduction pas à pas de Python¶
Ce notebook constitue une rapide introduction à Python. Le langage est découvert en exécutant un grand nombre de simples et courtes lignes de code. L’objectif est d’entrapercevoir la manière dont Python fonctionne afin d’éviter de tomber dans les pièges classiques des débutants.
Cette approche est inspirée des tutoriels Python by immersion et Python Epiphanies créés par Stuart Williams.
Indentation¶
Tout est dans le style
Python a été conçu de manière à être facile à lire, partant du principe qu’on passe plus de temps à lire du code qu’à en écrire. Cela se matérialise par l’importance de l”indentation (décalage d’une ligne de code vers la droite). L’indentation du code détermine en partie sa structure. Dans l’exemple ci-dessous, les deux instructions print
sont situées au même niveau d’indentation.
[682]:
print("Hey")
print("you")
Hey
you
Le code est exécuté normalement. La deuxième instruction du code suivant a été décalée de deux espaces, le code ne peut tout simplement plus être exécuté car cette structure n’a aucun sens pour Python.
[683]:
print("Hey")
print("you")
File "<ipython-input-683-a6cbeba02af8>", line 2
print("you")
^
IndentationError: unexpected indent
L’utilité de l’indentation est plus évidente dans le cas de blocs logiques, tel qu’un bloc for
. Le code ci-dessous s’exécute correctement.
[684]:
for i in [1, 2, 3]:
print(i) # L'indentation est de 4 espaces en général.
1
2
3
Alors que le code ci-dessous ne peut pas être exécuté, les instructions sous la ligne for
n’étant pas indentées. Comme on peut le voir, c’est le code du dessus qui est le plus facile à lire, l’indentation nous aide à comprendre la structure logique du code.
[685]:
for i in [1, 2, 3]:
print(i)
File "<ipython-input-685-3e96090b8527>", line 2
print(i)
^
IndentationError: expected an indented block
Afin de définir les boucles et autres structures de contrôle, d’autres langages utilisent des symbôles ({}
, ()
). En choisissant d’utiliser l’indentation au lieu de symboles, Python a faix le choix du code épuré.
Commentaires et pass
¶
Fainéant
Les commentaires sont des lignes qui débutent par un signe #
. Ces lignes-là sont ignorées par l’interpréteur.
[686]:
# Ceci est un commentaire.
[687]:
# On
# peut
# les
# enchaîner
[688]:
###### Ceci aussi est un commentaire.
On peut rajouter un commentaire à la suite d’un code valide (séparé de deux espaces par convention).
[689]:
3.14 # Ceci est aussi un commentaire.
[689]:
3.14
[1]:
Le *keyword* **pass** fait comme son nom l'indique, il passe son tour.
File "<ipython-input-1-4bc46cd563cf>", line 1
Le *keyword* **pass** fait comme son nom l'indique, il passe son tour.
^
SyntaxError: invalid syntax
pass
Objets¶
Tout est un objet!
Exécuter les instructions simples ci-dessous peut faire penser que Python ne fait rien de plus qu’une simple calculette.
[690]:
3.4
[690]:
3.4
[691]:
3.4 + 4
[691]:
7.4
Pourtant, beaucoup de choses se passent!
[692]:
dir(3.4)
[692]:
['__abs__',
'__add__',
'__bool__',
'__class__',
'__delattr__',
'__dir__',
'__divmod__',
'__doc__',
'__eq__',
'__float__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getformat__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__int__',
'__le__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__pos__',
'__pow__',
'__radd__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rmod__',
'__rmul__',
'__round__',
'__rpow__',
'__rsub__',
'__rtruediv__',
'__set_format__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'__trunc__',
'as_integer_ratio',
'conjugate',
'fromhex',
'hex',
'imag',
'is_integer',
'real']
La fonction dir
retourne une liste de tous les attributs d’un objet. La liste ci-dessus montre donc tous les attributs de l’objet 3.4
. Les derniers attributs affichés, dont is_integer
, nous indiquent que Python a compris que 3.4
est un nombre. On peut le vérifier en inspectant la valeur de l’attribut __class__
. Pour accéder à la valeur d’un attribut, on rajout un point ``.`` après l’objet suivi du nom de l’attribut.
[12]:
3.4.__class__
[12]:
float
float
est un type d’objet qui représente les nombres réels. Mais plutôt que de vérifier le type d’un objet en accédant à son attribut *__class__*, on peut utiliser la fonction type
. Cette fonction, comme dir
, fait partie des fonctions dites built-in car faisant parties intégrantes de Python (il n’y a pas besoin d’importer quoi que ce soit pour les appeler). On va voir plusieurs de ces fonctions dans la suite du notebook.
[13]:
type(3.4)
[13]:
float
On a donc vu que lorsqu’on exécute 3.4
, Python ne fait pas juste qu’afficher ce nombre mais l’interprète comme un objet de type float
doté de nombreux attributs. Que se passe-t-il alors lorsqu’on exécute 3.4 + 4
?
[693]:
3.4.__add__(4)
[693]:
7.4
L’addition que nous avons écrite est en fait exécutée par l’instruction ci-dessus. Python voit que nous avons écrit un +
après 3.4
, il cherche alors l’attribut dunder (racourci de double underscore donné aux attributs qui débutent et terminent par __
) __add__
dans la liste des attributs de 3.4
. L’attribut __add__
des floats
est en fait une méthode qui est capable de faire une action, dans ce cas-là, une addition. Lorsque Python trouve l’attribut
__add__
de 3.4
, il l’appelle avec l”argument 4
. La valeur retournée est le résultat de l’addition.
Python n’est donc évidemment pas qu’une simple calculatrice. Lorsqu’il évalue les expressions qu’on lui donne, il crée des objets dotés d’un type et d’attributs. Ces attributs sont soit équivalent à des propriétés, comme __class__
, soit des méthodes, comme __add__
. Cette mécanique est présente partout dans le langage, celui-ci travaille en effet beaucoup pour nous. Il faut aussi noter que Python s’occupe d’allouer et libérer la mémoire à notre place.
[694]:
type(3.4.__add__)
[694]:
method-wrapper
Chaque objet créé a sa propre identité définie comme son adresse physique dans la mémoire.
[695]:
id(3.4)
[695]:
1921982076992
[696]:
id(3.4)
[696]:
1921982076896
[18]:
id(4)
[18]:
140734432777120
dir
et id
sont des fonctions qu’on n’utilise presque jamais dans un script mais qui sont très utiles pour apprendre Python et inspecter des objets.
[2]:
type(dir)
[2]:
builtin_function_or_method
[698]:
type(id)
[698]:
builtin_function_or_method
Une fonction est aussi un objet (tout est un objet!) avec ses propres attributs, un type et une identité.
[699]:
dir(dir)
[699]:
['__call__',
'__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__name__',
'__ne__',
'__new__',
'__qualname__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__self__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__text_signature__']
[700]:
id(dir)
[700]:
1921920700776
[701]:
id(dir)
[701]:
1921920700776
Erreurs¶
Antifragilité
Lorsqu’on écrit du code en Python et qu’on l’exécute pour la première fois, il est habituel de voir un message d’erreur apparaître. Ces erreurs nous indiquent ce qui ne fonctionne pas dans le code, et nous oriente pour résoudre le bug qui a causé l’erreur. Python dispose d’un grand nombre de genres d’erreur, à force de les déclencher on s’habitue à les reconnaître et on comprend de plus en plus rapidement d’où vient le problème.
[702]:
type(1)
type(2) # L'espace déclenche l'erreur
File "<ipython-input-702-f456bc8300ff>", line 2
type(2) # L'espace déclenche l'erreur
^
IndentationError: unexpected indent
[703]:
1
3 +)°^ 3 # Cette suite de symboles n'a aucun sens
File "<ipython-input-703-57174e9a2a28>", line 2
3 +)°^ 3 # Cette suite de symboles n'a aucun sens
^
SyntaxError: invalid syntax
Les erreurs IntendationError
et SyntaxError
nous indique que le code n’a pas été écrit correctement. L’interpréteur analyse tout le code qu’on a écrit, s’il détecte ce genre d’erreur, il nous l’indique et n’exécute aucune ligne de code. Toutes les autres erreurs qu’on peut avoir surviennent pendant l’exécution du code (runtime).
print
et help
¶
Pour y voir plus clair
print
affiche les objets passés à la fonction. help
affiche l’aide d’un objet.
[704]:
print(3.4, 4, 5, 1, 0)
3.4 4 5 1 0
[705]:
help(print)
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
On va beaucoup utiliser la fonction print
. Le début de sa définition est print(value, ..., sep=' ')
:
value, ...
:print
accepte un nombre illimité d’arguments séparés par une virgule
[706]:
print(3.4, 4, 6, 103, 3030, 0, 0)
3.4 4 6 103 3030 0 0
sep=' '
: l’espacement par défaut entre deux valeurs affichées parprint
est un espace, on peut le changer en spécifiant le paramètresep
[708]:
print(3.4, 4, 6, 103, 3030, 0, 0, sep=" :) ")
3.4 :) 4 :) 6 :) 103 :) 3030 :) 0 :) 0
[709]:
# "\n" est interprété comme un retour à la ligne
print(3.4, 4, 6, 103, 3030, 0, 0, sep="\n")
3.4
4
6
103
3030
0
0
Nommer¶
Poser des post-its par-ci, par-là
Jusqu’à présent les objets qui ont été utilisés sont supprimés aussitôt générés par Python. On peut les garder à notre disposition en leur assignant un nom.
[710]:
height = 1.73
[711]:
height
[711]:
1.73
[712]:
type(height)
[712]:
float
[713]:
id(height)
[713]:
1921982077232
[714]:
id(height)
[714]:
1921982077232
[715]:
print(height, height, height)
1.73 1.73 1.73
Regardez bien les trois instructions ci-dessous, quelle est la valeur finale de ``x``?
[716]:
x = 2
y = x
y = 4
[717]:
print(x, y)
2 4
On aurait pu croire que x
valait 4
à la fin des trois instructions, mais non. Les noms en Python sont juste des références aux objets, on peut les comparer à des post-its.
[718]:
x = 2
# L'objet 2 est créé, un post-it avec inscrit *x* lui est aposé.
print("x =", x, " / id(x):", id(x))
y = x
# Un second post-it est aposé sur l'objet 2, il y est inscrit *y*.
print("x =", x, " / id(x):", id(x), " --- y =", y, " / id(y):", id(y))
# Aucun objet n'est créé lors de cette instruction.
y = 4
# L'objet 4 est créé, comme un nom ne peut référé qu'à un seul objet
# le post-it *y* qui était sur l'objet 2 est supprimé et un nouveau
# post-it *y* est aposé sur l'objet 4.
print("x =", x, " / id(x):", id(x), " --- y =", y, " / id(y):", id(y))
x = 2 / id(x): 140734432777056
x = 2 / id(x): 140734432777056 --- y = 2 / id(y): 140734432777056
x = 2 / id(x): 140734432777056 --- y = 4 / id(y): 140734432777120
Une fois que des références aux objets ont été créées, celles-ci sont rajoutées au namespace. On peut les références qu’on a nous-mêmes rajoutées au namespace avec la commande magique whos
.
[719]:
%whos
Variable Type Data/Info
-----------------------------
height float 1.73
i int 3
x int 2
y int 4
Ainsi, lorsqu’on l’on fait référence à un objet par un de ses noms, Python cherche dans le namespace la référence de cet objet et utilise la donnée qui lui est associée.
[720]:
height
[720]:
1.73
[721]:
poids
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-721-adb260374773> in <module>
----> 1 poids
NameError: name 'poids' is not defined
Comme poids
n’est pas une référence du namespace (on peut aussi dire que la variable poids
n’a pas encore été définie), l’interpréteur retourne une erreur.
On peut déréférencer un élément du namespace avec une instruction utilisant le keyword del
.
[722]:
# On enlève le post-it *height* de l'objet 1.73,
# celui-ci n'est plus accessible à la suite de ça.
del height
[723]:
%whos
Variable Type Data/Info
----------------------------
i int 3
x int 2
y int 4
[724]:
del height
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-724-f8c467be9fdb> in <module>
----> 1 del height
NameError: name 'height' is not defined
[725]:
height
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-725-5107fc8585e9> in <module>
----> 1 height
NameError: name 'height' is not defined
On peut déférencer tous les éléments du namespace avec la commande magique %reset -f
.
[727]:
%reset -f
[728]:
%whos
Interactive namespace is empty.
Keywords¶
Un peu de liant
Avec l’indentation, les keywords structurent le langage. On utilisera principalement les suivants:
- Le vide:
None
- Booléens:
True
,False
- Opérations booléennes:
and
,or
,not
- Appartenance:
in
,not in
- Identification:
is
,is not
- Boucles:
for
,while
break
,continue
,pass
, - Conditions:
if
,elif
,else
- Fonctions:
def
,return
- Import:
import
,from
,as
- Déférencement:
del
- Pas d’action:
pass
[729]:
help("keywords")
Here is a list of the Python keywords. Enter any keyword to get more help.
False class from or
None continue global pass
True def if raise
and del import return
as elif in try
assert else is while
async except lambda with
await finally nonlocal yield
break for not
Les keywords sont des mots réservés, aucun objet ne peut avoir un nom qui fait partie des keywords.
[730]:
True = 2
File "<ipython-input-730-da3e5402d318>", line 1
True = 2
^
SyntaxError: can't assign to keyword
[731]:
true = 2 # On peut faire ça, mais c'est maladroit!
[732]:
help("for")
The "for" statement
*******************
The "for" statement is used to iterate over the elements of a sequence
(such as a string, tuple or list) or other iterable object:
for_stmt ::= "for" target_list "in" expression_list ":" suite
["else" ":" suite]
The expression list is evaluated once; it should yield an iterable
object. An iterator is created for the result of the
"expression_list". The suite is then executed once for each item
provided by the iterator, in the order returned by the iterator. Each
item in turn is assigned to the target list using the standard rules
for assignments (see Assignment statements), and then the suite is
executed. When the items are exhausted (which is immediately when the
sequence is empty or an iterator raises a "StopIteration" exception),
the suite in the "else" clause, if present, is executed, and the loop
terminates.
A "break" statement executed in the first suite terminates the loop
without executing the "else" clause’s suite. A "continue" statement
executed in the first suite skips the rest of the suite and continues
with the next item, or with the "else" clause if there is no next
item.
The for-loop makes assignments to the variables(s) in the target list.
This overwrites all previous assignments to those variables including
those made in the suite of the for-loop:
for i in range(10):
print(i)
i = 5 # this will not affect the for-loop
# because i will be overwritten with the next
# index in the range
Names in the target list are not deleted when the loop is finished,
but if the sequence is empty, they will not have been assigned to at
all by the loop. Hint: the built-in function "range()" returns an
iterator of integers suitable to emulate the effect of Pascal’s "for i
:= a to b do"; e.g., "list(range(3))" returns the list "[0, 1, 2]".
Note: There is a subtlety when the sequence is being modified by the
loop (this can only occur for mutable sequences, e.g. lists). An
internal counter is used to keep track of which item is used next,
and this is incremented on each iteration. When this counter has
reached the length of the sequence the loop terminates. This means
that if the suite deletes the current (or a previous) item from the
sequence, the next item will be skipped (since it gets the index of
the current item which has already been treated). Likewise, if the
suite inserts an item in the sequence before the current item, the
current item will be treated again the next time through the loop.
This can lead to nasty bugs that can be avoided by making a
temporary copy using a slice of the whole sequence, e.g.,
for x in a[:]:
if x < 0: a.remove(x)
Related help topics: break, continue, while
A part True
, False
et None
, les keywords ne sont pas des objets.
Opérateurs principaux¶
Pas que pour les maths!
[733]:
3.4 + 4
[733]:
7.4
[734]:
3.4 - 4
[734]:
-0.6000000000000001
[735]:
3.4 * 4
[735]:
13.6
[736]:
3.4 / 4
[736]:
0.85
[737]:
11 // 4
[737]:
2
[744]:
(11).__floordiv__(4)
[744]:
2
[745]:
11 % 4
[745]:
3
[746]:
(11 // 4) * 4 + (11 % 4)
[746]:
11
[747]:
11 // 4 * 4 + 11 % 4 # Moins facile à lire
[747]:
11
[748]:
3.4 > 4
[748]:
False
[743]:
3.4 < 4
[743]:
True
[260]:
3.4 <= 4
[260]:
True
[261]:
3.4 >= 4
[261]:
False
[749]:
2 < 5 < 10
[749]:
True
[750]:
3 > 2 >= 10
[750]:
False
[751]:
3.4 == 4
[751]:
False
[265]:
3.4 != 4
[265]:
True
[266]:
4 == 4
[266]:
True
Importer¶
Batteries included
Nous avons pour l’instant vu que nous avions à disposition des fonctions built-in, des keywords et des opérateurs. Un mécanisme permet d”importer des objets supplémentaires dans le code, comme des fonctions.
[752]:
import math
math
est un module de la standard library. La standard library est une grande collection de modules livrés avec Python, c’est pourquoi on associe souvent l’expression Batteries Included au langage.
[753]:
%whos
Variable Type Data/Info
------------------------------
math module <module 'math' (built-in)>
true int 2
math
est le module dédié aux opérations mathématiques. En exécutant import math
, on a rajouté au namespace le nom math
, celui-ci faisant référence à un objet de type module
.
[754]:
print(dir(math))
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']
On peut voir que le module contient les attributs sqrt
(racine carrée) et pi
. On accède à ces attributs en rajoutant un .
après math
.
[756]:
math.sqrt
[756]:
<function math.sqrt(x, /)>
[757]:
math.pi
[757]:
3.141592653589793
[758]:
type(math.sqrt)
[758]:
builtin_function_or_method
[759]:
type(math.pi)
[759]:
float
[760]:
math.sqrt(math.pi)
[760]:
1.7724538509055159
import math as m
importe le module math
avec pour référence m
et non math
. Cette méthode peut être utilisé lorsque le nom du module est très long, ou si ce nom va être répété à de très nombreuses reprises. Trois examples classiques étant import numpy as np
, import pandas as pd
, import matplotlib.pyplot as plt
.
[761]:
%reset -f
[762]:
import math as m
[763]:
math
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-763-a984292e3e46> in <module>
----> 1 math
NameError: name 'math' is not defined
[764]:
m
[764]:
<module 'math' (built-in)>
[765]:
%whos
Variable Type Data/Info
------------------------------
m module <module 'math' (built-in)>
[768]:
m.sqrt(m.pi)
[768]:
1.7724538509055159
[769]:
%reset -f
Modules et packages¶
Batteries included in batteries
Les modules sont matérialisés par des fichiers textes dont l’extension est ``.py``. Lorsqu’on importe un module, le code qu’il contient est exécuté. Les objets qui sont créés dans ce module sont donc accessibles.
[771]:
%%writefile dummymodule.py
"""Un tout petit module."""
print("hello world")
x = 2
Overwriting dummymodule.py
[772]:
import dummymodule
[773]:
dummymodule.x
[773]:
2
[774]:
print(dir(dummymodule))
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']
[775]:
dummymodule.__doc__
[775]:
'Un tout petit module.'
[776]:
help(dummymodule)
Help on module dummymodule:
NAME
dummymodule - Un tout petit module.
DATA
x = 2
FILE
d:\googledrive\code\intropython\source\dummymodule.py
Lorsqu’il tente d’importer quelque chose, Python cherche dans une liste de dossier qu’on retrouve dans la référence sys.path
. Cette liste contient le dossier courant.
[777]:
import sys
sys.path
[777]:
['D:\\GoogleDrive\\Code\\IntroPython\\source',
'C:\\Users\\maxim\\Miniconda3\\python37.zip',
'C:\\Users\\maxim\\Miniconda3\\DLLs',
'C:\\Users\\maxim\\Miniconda3\\lib',
'C:\\Users\\maxim\\Miniconda3',
'',
'C:\\Users\\maxim\\AppData\\Roaming\\Python\\Python37\\site-packages',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages\\win32',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages\\win32\\lib',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages\\Pythonwin',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages\\IPython\\extensions',
'C:\\Users\\maxim\\.ipython']
Les modules de la standard library écrits en Python (il y a aussi des modules écrits en C
) sont visibles sur GitHub. Dans ce dossier on trouve aussi des sous-dossiers qui contiennent des fichiers .py
et un fichier __init__.py
. Ces dossiers-là sont des packages qu’on peut aussi importer.
[778]:
import email
[779]:
email.__package__
[779]:
'email'
[780]:
email.__path__
[780]:
['C:\\Users\\maxim\\Miniconda3\\lib\\email']
[781]:
email.__file__
[781]:
'C:\\Users\\maxim\\Miniconda3\\lib\\email\\__init__.py'
[782]:
import email.mime
[783]:
dir(email.mime)
[783]:
['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__path__',
'__spec__',
'audio',
'base',
'nonmultipart']
[784]:
email.mime.__package__
[784]:
'email.mime'
[787]:
import email.mime.audio as audio
[788]:
type(audio)
[788]:
module
[789]:
audio.__file__
[789]:
'C:\\Users\\maxim\\Miniconda3\\lib\\email\\mime\\audio.py'
Voici une très courte liste de modules utiles.
[790]:
import sys
sys.executable
[790]:
'C:\\Users\\maxim\\Miniconda3\\python.exe'
[791]:
sys.version
[791]:
'3.7.3 (default, Apr 24 2019, 15:29:51) [MSC v.1915 64 bit (AMD64)]'
[792]:
import time
t1 = time.time()
time.sleep(2)
t2 = time.time()
print(t2 - t1)
2.0007686614990234
[793]:
import datetime
guess = datetime.datetime(2019, 9, 10, 10, 10, 30)
now = datetime.datetime.now()
diff = now - guess
print("Je me suis trompé de", diff.seconds, "secondes.")
Je me suis trompé de 2415 secondes.
[794]:
import random
random.random()
[794]:
0.4721376731418996
[795]:
random.randint(10, 20)
[795]:
15
[798]:
random.choice(["a", "b", "c"])
[798]:
'b'
[801]:
random.sample([1, 2, 3, 4], 3)
[801]:
[2, 3, 1]
[353]:
random.gauss(10, 3)
[353]:
10.973576155873744
[802]:
import pathlib
current_directory = pathlib.Path.cwd()
current_directory
[802]:
WindowsPath('D:/GoogleDrive/Code/IntroPython/source')
[803]:
list(current_directory.glob("*"))
[803]:
[WindowsPath('D:/GoogleDrive/Code/IntroPython/source/.ipynb_checkpoints'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/conf.py'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/dummymodule.py'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/environnement.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/exercices.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/images'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/index.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/installation.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/introduction.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/intropython.ipynb'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/intropython_extended.ipynb'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/python.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/scientific_libs.ipynb'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/utilisation.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/_static'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/__pycache__')]
[805]:
import collections
collections.Counter("aaabbbbbbbbbbbbbcccddddee").most_common()
[805]:
[('b', 13), ('d', 4), ('a', 3), ('c', 3), ('e', 2)]
[806]:
%reset -f
!del /f dummymodule.py
Conditions if
et booléens¶
Avec des si
Le bloc sous une instruction if
est exécuté entièrement si la condition (expression) qui suit le if
est évaluée comme vraie.
[807]:
if True:
print("Executed")
Executed
[808]:
if False:
print("Not executed")
On peut rajouter d’autres conditions avec les keywords elif
et else
.
[810]:
age = 27
if age < 18:
print("Sorry you're not allowed to drive.")
elif 18 <= age < 80:
print("Let's drive!")
elif 80 <= age < 122:
print("Please please please strop driving please!")
else:
print("Is that you Jeanne?")
Let's drive!
On peut assigner le résultat d’un comparaison à une référence et on peut combiner plusieurs comparaisons avec les keywords and
et or
.
[811]:
condition1 = 18 <= 10 < 80
condition2 = 18 <= 54 < 80
condition1 and condition2
[811]:
False
[816]:
True and True
[816]:
True
[817]:
True or True
[817]:
True
[818]:
False and False
[818]:
False
[819]:
False or False
[819]:
False
[820]:
False and True
[820]:
False
[821]:
False or True
[821]:
True
True
et False
sont des booléens.
[823]:
print(type(condition1), type(condition2))
<class 'bool'> <class 'bool'>
[824]:
%reset -f
Texte¶
Du blabla
Le texte est représenté par des objets de type string
. On encadre le texte des symboles "
ou '
ou """
ou '''
pour créer un objet string
.
[826]:
"Bob"
[826]:
'Bob'
[827]:
'Bob'
[827]:
'Bob'
[828]:
"""Bob"""
[828]:
'Bob'
[829]:
'''Bob'''
[829]:
'Bob'
[831]:
type("Bob")
[831]:
str
[832]:
print("'")
'
[833]:
print('"')
"
On peut les additionner et les démultiplitier.
[834]:
"Bob" + "Bill"
[834]:
'BobBill'
[835]:
"Bob" * 3
[835]:
'BobBobBob'
[836]:
3 * "Bob"
[836]:
'BobBobBob'
[837]:
"Bob" - "Bill"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-837-a8c1ff178148> in <module>
----> 1 "Bob" - "Bill"
TypeError: unsupported operand type(s) for -: 'str' and 'str'
[838]:
"Bob" / 3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-838-f7f9e058c01b> in <module>
----> 1 "Bob" / 3
TypeError: unsupported operand type(s) for /: 'str' and 'int'
Il est possible de créer une string
vide.
[839]:
""
[839]:
''
[840]:
"Bob" + "" + "Bill"
[840]:
'BobBill'
Le caractère backslash ```` déclenche un comportement spécial: l’interpréteur regarde le caractère qui suit et voit si la combinaison des deux caractères forme ou non un caractère spécial, si oui, celui-ci est représenté.
[841]:
# \n --> Retour à la ligne
# \t --> Tabulation
print("Bob\tBill\nBill\tBob")
Bob Bill
Bill Bob
Le caractère \
a donc un effet particulier. Cela rend plus difficile la représentation d’un chemin vers un dossier ou un fichier sous Windows. On annule l’effet particulier de \
en rajoutant le préfixe r
(pour raw) devant le premier guillemet de la string
.
[842]:
print(r"user\me\names.txt") # OK
user\me\names.txt
[843]:
print("user\me\names.txt") # not OK
user\me
ames.txt
On peut vérifier si deux strings
sont égales avec l”opérateur égalité ==
. Pour vérifier si une partie d’une string
se trouve dans une autre, on utilise le keyword in
.
[845]:
"Bob" == "Bill"
[845]:
False
[846]:
"Bob" != "Bill"
[846]:
True
[847]:
"Bill" in "Bob and Bill"
[847]:
True
[848]:
"John" in "Bob and Bill"
[848]:
False
[849]:
"Bill" not in "Bob and Bill"
[849]:
False
Les strings
possèdent beaucoup de méthodes pour les analyser et transformer. Lorsqu’une méthode a pour objectif de transformer une string
elle retourne un nouvel objet string
qui est version transformée de la string
initiale. Elles ne le modifient pas directement l’objet initial. En effet, les ``strings`` sont des objets immutables.
[850]:
name = "bob"
print(dir(name))
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
[851]:
name.upper()
[851]:
'BOB'
[852]:
name
[852]:
'bob'
[853]:
name.title()
[853]:
'Bob'
[854]:
name.replace("o", "a")
[854]:
'bab'
[855]:
txt = " espaces avant et espaces après "
txt.strip()
[855]:
'espaces avant et espaces après'
strip
enlève par défaut les espaces au début et à la fin de la string
. Les espaces comprennent les sauts de ligne et les tabulations.
[856]:
sentence = "quel beau temps"
words = sentence.split()
words
[856]:
['quel', 'beau', 'temps']
[857]:
sentence.split("l")
[857]:
['que', ' beau temps']
[860]:
" ".join(words)
[860]:
'quel beau temps'
On peut enchaîner les méthodes tant qu’on fait attention à ce que la méthode qui suit accepte est bien une méthode de l’objet retourné par la méthode qui précède. Dans l’exemple ci-dessous, la méthode strip
est bien un méthode de l’objet sentence
qui est de type string
, elle retourne un objet string
qui possède bien une méthode replace
, celle-ci retourne un nouvel objet string
qui possède bien une méthode split
, celle-ci retourne finalement une liste.
[862]:
modified_sentence = sentence.strip("qs").replace("e", "i").split()
modified_sentence
[862]:
['uil', 'biau', 'timp']
[863]:
%reset -f
F-String¶
Du joli blabla
Les f-strings
sont des objets de type string
qu’on peut formater. Il faut rajouter un préfixe f
devant le premier guillemet, puis ensuite, à l’intérieur, il faut entourer de symboles {}
les expressions qu’on souhaite formater afin qu’elles soient directement évaluées par Python, qui construit notre objet string
.
[864]:
age = 28
f"J'ai {age} ans"
[864]:
"J'ai 28 ans"
[865]:
type(age)
[865]:
int
[866]:
"J'ai " + age + " ans"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-866-1c648e4e0b43> in <module>
----> 1 "J'ai " + age + " ans"
TypeError: can only concatenate str (not "int") to str
[869]:
age = 28
majorite = 18
if age > majorite:
print(f"Tu as {age - majorite} ans de plus que la majorité qui est à {majorite} ans.")
Tu as 10 ans de plus que la majorité qui est à 18 ans.
Cette manière de formater les strings
est assez récente (Python 3.6) et est vraiment pratique et facile à utiliser. Il existe deux autres manières de formater des strings
qu’on retrouve encore souvent sur internet.
[870]:
%reset -f
Convertir float
, int
et str
¶
Nombre <—> Texte <—> Nombre
Les fonctions int
, float
et str
tentent de convertir les objets qu’on leur donne. Elles retournent une erreur ``ValueError`` si elles échouent, et l’objet converti si elles réussissent.
[871]:
int(3.7)
[871]:
3
[872]:
float(4)
[872]:
4.0
[873]:
str(3)
[873]:
'3'
[874]:
int("Bob")
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-874-b70fd5693779> in <module>
----> 1 int("Bob")
ValueError: invalid literal for int() with base 10: 'Bob'
[875]:
x = "3"
print(type(x))
x = int(x)
print(type(x))
<class 'str'>
<class 'int'>
[876]:
"Bob" + 3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-876-45c29619c55a> in <module>
----> 1 "Bob" + 3
TypeError: can only concatenate str (not "int") to str
[877]:
"Bob " + str(3)
[877]:
'Bob 3'
[878]:
# print convertit automatiquement les objets en string.
print("Bob", 3)
Bob 3
[879]:
%reset -f
Indexing et Slicing¶
A piece of cake
Certains objets comme les objets de type string
sont des séquences. On peut obtenir n’importe quel item de l’objet grâce à son index numérique (indexing), on peut aussi obtenir directement plusieurs items (slicing). Le premier item d’une séquence a toujours l’index 0 en Python (0-based).
[880]:
txt = "John"
[881]:
txt[0]
[881]:
'J'
[882]:
txt[1]
[882]:
'o'
[883]:
txt[2]
[883]:
'h'
[884]:
txt[3]
[884]:
'n'
[885]:
txt[4]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-885-e9d47c8188c4> in <module>
----> 1 txt[4]
IndexError: string index out of range
L’index -1
est un raccourci pour indiquer le dernier caractère. -2
indique l’avant-dernier caractère, etc.
[886]:
txt[-1]
[886]:
'n'
[887]:
txt[-2]
[887]:
'h'
[888]:
txt[-3]
[888]:
'o'
[889]:
txt[-4]
[889]:
'J'
[890]:
txt[-5]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-890-e5b69442cb73> in <module>
----> 1 txt[-5]
IndexError: string index out of range
On peut connaître le nombre d’items d’une séquence avec la fonction ``len``. Lorsqu’on passe un objet string
à la fonction len
, on obtient le nombre de caractères de l’objet.
[891]:
len(txt)
[891]:
4
[892]:
len("")
[892]:
0
[897]:
len(" ")
[897]:
1
Attention au comportement spécial du backslash \
.
[894]:
len("\n")
[894]:
1
[895]:
len(r"\n")
[895]:
2
[896]:
len("Bob and Bill")
[896]:
12
[898]:
len(3.4)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-898-a9f429718b04> in <module>
----> 1 len(3.4)
TypeError: object of type 'float' has no len()
On ne peut pas modifier un item d’une string
à partir de son index. En fait, il est simplement impossible de modifier un objet string
, il faut forcément en créer un nouveau. Un objet qui ne peut pas être modifié est dit immutable.
[899]:
txt[0] = "A"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-899-dc27665d30a4> in <module>
----> 1 txt[0] = "A"
TypeError: 'str' object does not support item assignment
Pour obtenir plusieurs caractères d’un objet string
, on utilise la syntaxe [start:stop:step]
où start
et stop
sont des index, et step
l’écart entre deux caractères. Par défaut, start
est le premier index ([0]
), stop
est le dernier index, et step
est égal à 1. L’élément à l’index ``start`` est inclus dans l’objet string
retourné, l’élément à l’index ``stop`` est exclus.
[902]:
numbers = "0123456789"
numbers[3::]
[902]:
'3456789'
[903]:
numbers[3:]
[903]:
'3456789'
[905]:
numbers[50::]
[905]:
''
[906]:
numbers[:8]
[906]:
'01234567'
[907]:
numbers[3:8]
[907]:
'34567'
[908]:
numbers[3:3]
[908]:
''
[909]:
numbers[3:50]
[909]:
'3456789'
[910]:
numbers[::2]
[910]:
'02468'
La syntaxe ::-1
permet d’inverser la list
originale.
[911]:
numbers[::-1]
[911]:
'9876543210'
[912]:
numbers[::-2]
[912]:
'97531'
[913]:
numbers[3:8:3]
[913]:
'36'
[914]:
numbers[3:8:-1]
[914]:
''
[915]:
numbers[8:3]
[915]:
''
[916]:
numbers[8:3:-1]
[916]:
'87654'
[917]:
%reset -f
Listes et Tuples¶
Maîtres de l’ordre
Les objets de type list
et tuple
sont des containers. Ces deux types d’objet sont, comme les objets de type string
, des séquences. Ils peuvent contenir n’importe quel type d’objet.
Les lists
et les tuples
sont très pratiques et utilisés partout dans le langage Python.
[918]:
shopping = ["banana", "beer", "chocolate", "tomato", "salad"]
[919]:
type(shopping)
[919]:
list
[920]:
print(dir(list))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
L’objet retourné par la fonction dir
est une list
.
[921]:
type(dir(list))
[921]:
list
On peut accéder à chaque item d’une list
par son index numérique.
[922]:
shopping[0]
[922]:
'banana'
[923]:
shopping[-1]
[923]:
'salad'
[924]:
shopping[1:4:2]
[924]:
['beer', 'tomato']
[925]:
shopping[::-1]
[925]:
['salad', 'tomato', 'chocolate', 'beer', 'banana']
[926]:
len(shopping)
[926]:
5
[927]:
len([])
[927]:
0
Alors qu’on définit une list
en entourant ses items de []
, on définit un tuple en entourant ses items de ()
.
[928]:
shopping = ("banana", "beer", "chocolate", "tomato", "salad")
[929]:
type(shopping)
[929]:
tuple
On accède aux items d’un tuple
comme on accède aux items d’une list
.
[930]:
shopping[0]
[930]:
'banana'
[931]:
shopping[-1]
[931]:
'salad'
[932]:
shopping[1:4:2]
[932]:
('beer', 'tomato')
[933]:
shopping[::-1]
[933]:
('salad', 'tomato', 'chocolate', 'beer', 'banana')
[934]:
len(shopping)
[934]:
5
[935]:
len(())
[935]:
0
Une list
ou dans un tuple
peuvent faire référence à n’importe quel type d’objet.
[936]:
import math
big_mess = [2, 2.33, "string", print, math, ["Bob", "Bill"]]
big_mess
[936]:
[2,
2.33,
'string',
<function print>,
<module 'math' (built-in)>,
['Bob', 'Bill']]
[938]:
import math
big_mess = (2, 2.33, "string", print, math, ["Bob", "Bill"])
big_mess
[938]:
(2,
2.33,
'string',
<function print>,
<module 'math' (built-in)>,
['Bob', 'Bill'])
Les ``lists`` et les ``tuples`` ne contiennent aucun objet, ils contiennent en fait des références à d’autres objets.
[939]:
pi = 3.14
[940]:
l = [pi]
[941]:
# l'objet `3.14` a deux post-its posés sur lui: `pi` et `l[0]`.
print(f"""\
{'ID de l:':15}{id(l)}
{'ID de pi:':15}{id(pi)}
{'ID de l[0]:':15}{id(l[0])}""")
ID de l: 1921982303368
ID de pi: 1921965186936
ID de l[0]: 1921965186936
Comme les strings
, les tuples
sont des immutables.
[944]:
shopping[0] = 55
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-944-7be9fb42ab76> in <module>
----> 1 shopping[0] = 55
TypeError: 'tuple' object does not support item assignment
Au contraire, les lists
sont des mutables. Il est donc autorisé de remplacer ou supprimer les éléments qu’elles contiennent, ou d’en rajouter.
[945]:
shopping = ["banana", "beer", "chocolate", "tomato", "salad"]
print(id(shopping), shopping)
1921962349576 ['banana', 'beer', 'chocolate', 'tomato', 'salad']
[946]:
shopping[0] = "apple"
print(id(shopping), shopping)
1921962349576 ['apple', 'beer', 'chocolate', 'tomato', 'salad']
[948]:
shopping.append("cucumber")
print(id(shopping), shopping)
1921962349576 ['apple', 'beer', 'chocolate', 'tomato', 'salad', 'cucumber', 'cucumber']
[949]:
shopping.append("pasta")
print(id(shopping), shopping)
1921962349576 ['apple', 'beer', 'chocolate', 'tomato', 'salad', 'cucumber', 'cucumber', 'pasta']
[950]:
del shopping[-1]
print(id(shopping), shopping)
1921962349576 ['apple', 'beer', 'chocolate', 'tomato', 'salad', 'cucumber', 'cucumber']
[951]:
shopping.extend(["egg", "bread", "jam"])
print(id(shopping), shopping)
1921962349576 ['apple', 'beer', 'chocolate', 'tomato', 'salad', 'cucumber', 'cucumber', 'egg', 'bread', 'jam']
[952]:
shopping.insert(0, "salt")
print(id(shopping), shopping)
1921962349576 ['salt', 'apple', 'beer', 'chocolate', 'tomato', 'salad', 'cucumber', 'cucumber', 'egg', 'bread', 'jam']
Les lists
et les tuples
supportent les opérateurs in
, not in
, ==
, !=
, +
et *
.
[953]:
"egg" in shopping
[953]:
True
[954]:
"rice" not in shopping
[954]:
True
[955]:
shopping == ["egg", "chocolate"]
[955]:
False
[956]:
shopping != ["egg", "chocolate"]
[956]:
True
[957]:
["Bob"] + ["Bill"]
[957]:
['Bob', 'Bill']
[958]:
["Bob", "Bill"] * 3
[958]:
['Bob', 'Bill', 'Bob', 'Bill', 'Bob', 'Bill']
On peut transformer les lists
en tuples
et vice versa avec les fonctions tuple
et list
.
[959]:
l = ["Bob", "Bill"]
t = tuple(l)
print(type(t), t)
<class 'tuple'> ('Bob', 'Bill')
[960]:
t = ["Bob", "Bill"]
l = list(t)
print(type(l), l)
<class 'list'> ['Bob', 'Bill']
Attention, lorsqu’on référence une list
existante l1 avec un nouveam nom l2, les modifications de l2 modifient l1. Comme déjà évoqué, les noms en Python ne sont que des références à des objets. Dans le cas ci-dessous, la liste [1, 2, 3]
est un objet unique auquel on peut accéder par les deux références/noms l1
et l2
.
[961]:
l1 = [1, 2, 3]
l2 = l1
l2[0] = 99
print(l1, l2)
[99, 2, 3] [99, 2, 3]
[962]:
l1[1] = 999
print(l1, l2)
[99, 999, 3] [99, 999, 3]
[963]:
l1 is l2
[963]:
True
Copier une list
est une action qu’on va vouloir faire de temps en temps. On utilise la fonction deepcopy
du module copy
.
[964]:
import copy
l1 = [1, 2, 3]
l2 = copy.deepcopy(l1)
l2[0] = 999
print(l1, l2)
[1, 2, 3] [999, 2, 3]
[965]:
l1 is l2
[965]:
False
[967]:
import copy
l1 = [[1], 2, 3]
l2 = copy.deepcopy(l1)
l2[0][0] = 999
print(l1, l2)
[[1], 2, 3] [[999], 2, 3]
[968]:
l1[0] is l2[0]
[968]:
False
Même si les objets de type list
et tuple
sont similaires, en général on ne les utilise pas de la même manière. Les lists
vont plutôt contenir des objets du même type (exemple: liste de courses). Les tuples
vont contenir une suite d’objets dont on est sûr à l’avance qu’elle n’aura pas besoin d’être modifiée (exemple: prénom et nom).
[969]:
%reset -f
Dictionnaires¶
Ils font la paire
Les objets de type dict
sont des containers et sont mutables. Ils sont constitués de paires key-value. Les keys doivent être des objets immutables, les values peuvent être n’importe quel type d’objet. Les dictionnaires sont très présents dans le langage Python.
[970]:
phonebook = {
"Bob": 383,
"Bill": 509,
"Donald": 102
}
phonebook
[970]:
{'Bob': 383, 'Bill': 509, 'Donald': 102}
[971]:
type(phonebook)
[971]:
dict
On accède à une value que contient un dict
par sa key.
[972]:
phonebook["Bob"]
[972]:
383
[973]:
phonebook[383]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-973-9abd3fa563ad> in <module>
----> 1 phonebook[383]
KeyError: 383
[974]:
phonebook["John"]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-974-3f89bbc7ecee> in <module>
----> 1 phonebook["John"]
KeyError: 'John'
[975]:
# Rajouter une key-value entrée
phonebook["John"] = 984
phonebook
[975]:
{'Bob': 383, 'Bill': 509, 'Donald': 102, 'John': 984}
[976]:
phonebook["John"]
[976]:
984
[977]:
# Vérifier si une key est dans le dict
"John" in phonebook
[977]:
True
[978]:
# Cela ne fonctionne que sur les keys, par sur les values
984 in phonebook
[978]:
False
[979]:
# Supprimer une paire key-value
del phonebook["John"]
phonebook["John"]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-979-5eb835ec0b00> in <module>
1 # Supprimer une paire key-value
2 del phonebook["John"]
----> 3 phonebook["John"]
KeyError: 'John'
[980]:
# Les dicts ne sont pas des séquences.
phonebook[0]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-980-6cd85588bf81> in <module>
1 # Les dicts ne sont pas des séquences.
----> 2 phonebook[0]
KeyError: 0
[981]:
# La longueur d'un dict est le nombre de paires key-value qu'il contient.
len(phonebook)
[981]:
3
[982]:
# Seuls des immutables peuvent être des keys
mess = {
"str": "Les strings sont immutables",
12: "Les ints sont immutables",
3.53: "Les floats sont immutables",
(12, 24): "Les tuples sont immutables"
}
mess
[982]:
{'str': 'Les strings sont immutables',
12: 'Les ints sont immutables',
3.53: 'Les floats sont immutables',
(12, 24): 'Les tuples sont immutables'}
[983]:
# Les lists sont mutables, elles ne peuvent pas être des keys
mess[["list as a key"]] = 1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-983-1f3405e3d82b> in <module>
1 # Les lists sont mutables, elles ne peuvent pas être des keys
----> 2 mess[["list as a key"]] = 1
TypeError: unhashable type: 'list'
[984]:
# Les values d'un dict peuvent être de n'importe quel type.
import math
d = {
3.13: math,
"print": print,
"l": [1, 2, 3],
123: "123",
"nested_dicts": {"another_one": {"and_a_last_one": "here I am!"}},
"sets": {1, 3, 5},
True: False
}
d
[984]:
{3.13: <module 'math' (built-in)>,
'print': <function print>,
'l': [1, 2, 3],
123: '123',
'nested_dicts': {'another_one': {'and_a_last_one': 'here I am!'}},
'sets': {1, 3, 5},
True: False}
[985]:
d["nested_dicts"]["another_one"]["and_a_last_one"]
[985]:
'here I am!'
[986]:
%reset -f
Fonctions¶
Don’t Repeat Yourself
On a déjà vu quelques unes des fonctions intégrées directement dans Python (built-in functions). On peut en fait créer nos propres fonctions. Cela est bien utile lorsqu’on souhaite réutiliser un bout de code, et/ou lorsqu’on souhaite faciliter la lecture/compréhension du code en le scindant en blocs logiques.
On crée un objet function
avec le keyword ``def``. Une fonction retourne toujours un objet. On peut spécifier l’objet que doit retourner une fonction en le précédant du keyword ``return``. Si on ne spécifie rien, la fonction retourne None
. L’exécution de la fonction se termine dès lors qu’elle retourne un objet.
[987]:
def return_three():
return 3
[988]:
type(return_three)
[988]:
function
Le code sous la définition de la fonction return_three
est exécuté lorsqu’on appelle la fonction avec les symboles ()
.
[989]:
return_three()
[989]:
3
Une fonction peut avoir des paramètres. On les précise sur la ligne de définition de la fonction. Ils peuvent ensuite être utilisés dans la fonction. La fonction add
a deux paramètres x
et y
.
[990]:
def add(x, y):
return x + y
La fonction add
est appelée avec les arguments 3
et 4
.
[991]:
add(3, 4)
[991]:
7
[992]:
result = add(3, 4)
[993]:
result
[993]:
7
On a des fois pas besoin qu’une fonction retourne un objet spécifique. Elle retourne alors None
.
[994]:
def say_hi_many_times(nb_of_times):
print("Hi " * nb_of_times)
[995]:
say_hi_many_times(10)
Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi
[996]:
output = say_hi_many_times(10)
Hi Hi Hi Hi Hi Hi Hi Hi Hi Hi
[997]:
output is None
[997]:
True
L’objet string
entouré de """
et positionné au début du bloc de la fonction est la docstring de la fonction.
[998]:
def add(x, y):
"""Additioner deux objets."""
return x + y
[999]:
help(add)
Help on function add in module __main__:
add(x, y)
Additioner deux objets.
Certains paramètres peuvent avoir des valeurs par défaut. Lorsqu’on appelle la fonction, on n’est donc pas obligé de préciser la valeur de ce paramètre (ce qui équivaut à dire qu’on n’est pas obligé de fournir un argument pour ce paramètre). Dans ce cas-là, la valeur par défaut est utilisée lors de l’exécution du code. Lorsqu’on souhaite utiliser une autre valeur que la valeur par défaut, on peut soit fournir l’argument si on respecte l’ordre des paramètres, on peut aussi fournir l’argument en nommant le paramètre (``param=arg``).
[1000]:
print?
[1001]:
def function_with_defaults(a, b, c=0):
return a + b + c
function_with_defaults(1, 4)
[1001]:
5
[1002]:
function_with_defaults(1, 4, c=50)
[1002]:
55
[1003]:
function_with_defaults(1, 4, 5)
[1003]:
10
[1004]:
function_with_defaults(c=50, 1, 4)
File "<ipython-input-1004-e132c12cc603>", line 1
function_with_defaults(c=50, 1, 4)
^
SyntaxError: positional argument follows keyword argument
[1005]:
function_with_defaults(c=50, b=4, a=1)
[1005]:
55
Une fonction peut appeler une autre fonction.
[1006]:
def first():
print("first function called")
second()
def second():
print("second function called")
[1007]:
first()
first function called
second function called
Lorsqu’on exécute le bloc de définition d’une fonction (la ligne def
et le bloc indenté qui suit), Python se prépare à exécuter la fonction mais ne l’exécute pas encore, elle ne le sera que lorsqu’elle sera appelée. A part les erreurs de syntaxe et d’indentation que Python détecte lors de cette étape de préparation, toutes les autres erreurs sont détectées seulement lorsque la fonction est appelée.
[1008]:
# unknown n'est pas une variable définie, malgré ça, on peut exécuter ce code
def dont_even_dare():
return unknown
[1009]:
# On obtient une erreur lorsqu'on appelle la fonction dont_even_dare
dont_even_dare()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1009-a64b5e067227> in <module>
1 # On obtient une erreur lorsqu'on appelle la fonction dont_even_dare
----> 2 dont_even_dare()
<ipython-input-1008-c0383c067b3c> in dont_even_dare()
1 # unknown n'est pas une variable définie, malgré ça, on peut exécuter ce code
2 def dont_even_dare():
----> 3 return unknown
NameError: name 'unknown' is not defined
L’exécution d’une fonction se passe dans son scope local. Les objets référencés par la fonction (firstname = "Bob"
) sont déférencés lorsque l’exécution prend fin. On ne peut plus y avoir accès après ça.
[1010]:
def function_scope():
firstname = "Bob"
function_scope()
firstname
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1010-88897d2c37bf> in <module>
2 firstname = "Bob"
3 function_scope()
----> 4 firstname
NameError: name 'firstname' is not defined
Même la référence de l’objet retourné par la fonction est supprimée à la fin de l’exécution.
[603]:
def function_scope():
firstname = "Bob"
return firstname
function_scope()
firstname
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-603-adec7aecec8c> in <module>
3 return firstname
4 function_scope()
----> 5 firstname
NameError: name 'firstname' is not defined
Pour préserver l’objet retourné on lui associe un nom.
[604]:
firstname = function_scope()
firstname
[604]:
'Bob'
x
ci-dessous n’est ni un paramètre de la fonction look_outside
ni un objet créé dans la fonction. Lorsqu’on exécute look_outside
, Python regarde d’aborde dans le scope local de la fonction s’il existe une référence x
. Comme il ne la trouve pas, Il cherche alors dans le scope global de la fonction.
Dans le premier cas ci-dessous, aucune référence x
n’est créée en dehors du scope de la fonction. Une erreur est donc déclenchée.
[605]:
def look_outside(b):
return x + b
look_outside(20)
[605]:
23.4
Ici, comme la référence x
est créée (ajoutée au namespace) avant l’exécution de look_outside
, celle-ci peut la trouver et s’exécute donc jusqu’à son terme.
[606]:
def look_outside(b):
return x + b
x = 10
look_outside(20)
[606]:
30
[607]:
def look_inside_first(b):
x = 0
return x + b
x = 10
look_inside_first(20)
[607]:
20
Attention aux objets mutables passés comme argument à une fonction. Lorsqu’on passe un argument à une fonction, celle-ci utilise directement l’objet référencé.
[608]:
def is_same_objet(n):
print(n is name)
name = "Bob"
is_same_objet(name)
True
Si on passe un objet mutable comme argument d’une fonction, cette fonction va utiliser directement cet objet. S’il est modifié au cours de l’exécution de la fonction, c’est le bien mutable source qui est modifié, même s’il n’a pas la même référence dans la fonction.
[609]:
def change_mutable(mutable):
mutable[0] = "John"
return mutable
[610]:
names = ["Bill", "Rachel", "Bob"]
new_names = change_mutable(names)
print("new_names:", new_names, "\nnames:", names)
new_names: ['John', 'Rachel', 'Bob']
names: ['John', 'Rachel', 'Bob']
Si ce comportement n’est pas souhaité, on peut soit appeler la fonction avec une copie du mutable, soit créer une copie directement dans le bloc de la fonction.
[611]:
names = ["Bill", "Rachel", "Bob"]
new_names = change_mutable(names[:])
print("new_names:", new_names, "\nnames:", names)
new_names: ['John', 'Rachel', 'Bob']
names: ['Bill', 'Rachel', 'Bob']
[612]:
%reset -f
Unpacking, Packing¶
Déballer son sac
On peut facilement extraire un à un les éléments d’un objet iterable (séquences et containers) pour assigner de nouvelles variables avec la méthode dite de l”unpacking. Cela se fait le plus souvent à partir d’un tuple
.
[613]:
t = ("Bob", 27, ["Sarah", "Jim"])
t
comprend trois éléments, ces trois éléments sont assignés dans l’ordre aux variables name
, age
et siblings
.
[614]:
name, age, siblings = t
print(name, age, siblings)
Bob 27 ['Sarah', 'Jim']
Mais on peut aussi le faire avec d’autres types d’objet.
[615]:
l = ["Bob", 27, ["Sarah", "Jim"]]
name, age, siblings = l
print(name, age, siblings)
Bob 27 ['Sarah', 'Jim']
[616]:
s = "abc"
a, b, c = s
print(a, b, c)
a b c
[617]:
x = 3.4
pint, pdec = 3.4
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-617-5c0c97d6e39b> in <module>
1 x = 3.4
----> 2 pint, pdec = 3.4
TypeError: cannot unpack non-iterable float object
Packing est l’action par laquelle plusieurs objets sont rassemblés dans un tuple
.
[618]:
"Bob", 27, ["Sarah", "Jim"]
[618]:
('Bob', 27, ['Sarah', 'Jim'])
[619]:
packed = "Bob", 27, ["Sarah", "Jim"]
packed
[619]:
('Bob', 27, ['Sarah', 'Jim'])
[620]:
type(packed)
[620]:
tuple
Lorsqu’on essaie de retourner plusieurs objets dans une fonction, ceux-ci sont en fait toujours rassemblés dans un ``tuple``. Packing est à l’oeuvre.
[621]:
def person_info():
return "Anna", 26 # équivalent à return ("Anna", 26)
output = person_info()
type(output)
[621]:
tuple
On peut utiliser l”unpacking pour assigner à des variables les objets retournés par la fonction person_info
.
[622]:
name, age = person_info()
print(name, age)
Anna 26
[623]:
%reset -f
Boucle for
et fonctions associées¶
May the for be with you
Les boucles ``for`` sont très simples et utiles en Python. Elles permettent de répéter un bloc d’instructions un certain nombre de fois. L’objet qui suit le keyword in
doit être un iterable.
[1011]:
for i in range(3):
print(i)
0
1
2
[625]:
range?
range
est une fonction qui accepte 3 arguments start
, stop
et step
. Par défaut start
est égal à 0 et step
à 1. Cette fonction ne retourne pas une liste mais un obtjet de type range
. Il s’agit d’un objet un peu spécial. Lorsqu’on exécute range(1_000_001)
, considérant le code de la cellule précédente, on pourrait penser que cette instruction génère des nombres de 0 à 1000000, mais non. Cet objet est une sorte de distributeur, il ne fait rien tant qu’on ne lui
demande rien. Lorsqu’on lui demande quelque chose, comme dans une boucle for
, il s’active et commence à distribuer. Dans le cas de range(1_000_001)
, l’objet distribue des ints
en commençant à 0, un par un, jusqu’à 1000000.
[1012]:
for i in range(3):
print(id(i))
140734432776992
140734432777024
140734432777056
[627]:
type(range(3))
[627]:
range
Pour créer une liste croissante d’entiers, on peut convertir un objet range
en liste avec la fonction list
.
[628]:
list(range(3))
[628]:
[0, 1, 2]
La boucle for
de Python est en fait similaire à une boucle for each
, elle extrait chaque élément de l”iterable sur laquelle elle agit.
[1013]:
names = ["Bob", "Bill", "Sarah", "Anna"]
for name in names:
print(name)
Bob
Bill
Sarah
Anna
On peut pratiquer l”unpacking dans la ligne de la boucle for
.
[1014]:
fullnames = [("Rachel", "Miller"), ("Bob", "Smith"), ("Anna", "Johnson"), ("Bill", "Davis")]
for first_name, last_name in fullnames:
print(first_name, last_name)
Rachel Miller
Bob Smith
Anna Johnson
Bill Davis
On peut utiliser la boucle for
pour itérer au travers des objets de type string
, list
, tuple
, dict
, set
(et plus encore).
[1015]:
txt = "abcdefghi"
for letter in txt:
print(letter)
a
b
c
d
e
f
g
h
i
[1016]:
names = ("Bob", "Bill", "Sarah", "Anna")
for name in names:
print(name)
Bob
Bill
Sarah
Anna
[1017]:
phonebook = {
"Bob": 383,
"Bill": 509,
"Donald": 102
}
for k in phonebook:
print(k)
Bob
Bill
Donald
[1018]:
for k, v in phonebook.items():
print(k, v)
Bob 383
Bill 509
Donald 102
[1019]:
for v in phonebook.values():
print(v)
383
509
102
On peut inverser l’ordre des itérations en appelant la fonction reversed
sur l”iterable.
[1020]:
for name in reversed(names):
print(name)
Anna
Sarah
Bill
Bob
Un peu comme la fonction range
, la fonction reversed
ne retourne pas une list
mais un objet qualifié d”iterator. Il s’agit aussi d’un distributeur, il distribue dans ce cas-là les éléments de l”iterable en commençant par le dernier. Comme on peut itérer sur un iterator, ce type d’objet est aussi un iterable.
[639]:
reversed(names)
[639]:
<reversed at 0x1bf7f0fbfd0>
[640]:
reversed(names)[0]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-640-0f87fe6cd96c> in <module>
----> 1 reversed(names)[0]
TypeError: 'reversed' object is not subscriptable
Les iterators sont des objets qui se tarissent. Après leur avoir demandé de distribuer leur « contenu » une fois, ils ne peuvent plus le redistribuer.
[641]:
reversed_names = reversed(names)
list(reversed_names)
[641]:
['Anna', 'Sarah', 'Bill', 'Bob']
[642]:
list(reversed_names)
[642]:
[]
reserved_names
est maintenant un iterator vide, on ne peut plus le réutiliser. (A noter que l’objet retourné par range
ressemble à un iterator mais n’en est en fait pas un, on peut donc le réutiliser).
[1021]:
for name in sorted(names):
print(name)
Anna
Bill
Bob
Sarah
[1023]:
for name in sorted(names, key=len):
print(name)
Bob
Bill
Anna
Sarah
sorted
ne retourne pas un iterator mais directement une list
.
[645]:
sorted(names)
[645]:
['Anna', 'Bill', 'Bob', 'Sarah']
[646]:
type(sorted(names))
[646]:
list
La fonction ``enumerate`` fournit sous la forme d’un ``tuple`` un compteur et les les éléments de l’iterable avec laquelle elle est appelée.
[1024]:
for i, name in enumerate(names):
print(f"Iteration {i}: {name}")
Iteration 0: Bob
Iteration 1: Bill
Iteration 2: Sarah
Iteration 3: Anna
[1025]:
for i, name in enumerate(reversed(names)):
print(f"Iteration {i}: {name}")
Iteration 0: Anna
Iteration 1: Sarah
Iteration 2: Bill
Iteration 3: Bob
L’objet que la fonction enumerate
retourne est un iterator.
[649]:
enumerated_names = enumerate(names)
[650]:
list(enumerated_names)
[650]:
[(0, 'Bob'), (1, 'Bill'), (2, 'Sarah'), (3, 'Anna')]
[651]:
list(enumerated_names)
[651]:
[]
La function zip
permet d’itérer sur plusieurs iterables en extrayant leur élément en même temps. L’objet qu’elle retourne est un iterator.
[1026]:
ages = [25, 28, 23, 21]
first_letters = ["B", "R", "B", "A"]
[1027]:
for name, first_letter, age in zip(names, first_letters, ages):
print(name, first_letter, age)
Bob B 25
Bill R 28
Sarah B 23
Anna A 21
[1028]:
for i, (name, first_letter, age) in enumerate(zip(names, first_letters, ages)):
print(i, name, first_letter, age)
0 Bob B 25
1 Bill R 28
2 Sarah B 23
3 Anna A 21
Mais que se passe-t-il exactement lorsqu’on exécute une boucle for
? A chaque itération, un élément de l”iterable est référencé par le nom défini juste après le keyword for
. On peut voir que les objets extraits d’une list
sont exactement les objets auxquels elle fait référence.
On a souvent besoin de modifier un à un les éléments d’une ``list`` (exemple: mettre des noms en capitales). la méthode recommandée est la suivante: * On crée une list
vide avec l = []
* A chaque itération on vient rajouter un élément à cette list
vide avec la méthode append
(l.append(new_item)
)
[1029]:
names = ["Bob", "Bill", "Sarah", "Rachel"]
upper_names = []
for name in names:
upper_names.append(name.upper())
upper_names
[1029]:
['BOB', 'BILL', 'SARAH', 'RACHEL']
On peut inclure des conditions if
dans le bloc d’une boucle for
pour préciser les actions à effectuer.
[1030]:
for name in names:
if "S" in name:
if "a" in name:
if "r" in name:
print(f"I think you're Sarah, correct? \nYes I'm {name}!")
I think you're Sarah, correct?
Yes I'm Sarah!
Cela génère des fois du code qui est très indenté et pas facile à lire, on peut utiliser les *keywords* ``continue`` et ``break`` pour contrôler l’exécution de la boucle. continue
entraîne le démarrage direct de la prochaine itération, ainsi, tout le code qui est situé sous continue
est passé si continue
est exécuté. break
arrête totalement l’exécution de la boucle.
[657]:
for name in names:
if "S" not in name:
continue
if "a" not in name:
continue
if "r" not in name:
continue
# Exécuté si les trois conditions ci-dessus sont fausses
print(f"I think you're Sarah, correct? \nYes I'm {name}!")
break
I think you're Sarah, correct?
Yes I'm Sarah!
[1032]:
for name in names:
if name == "Sarah":
continue
print(name) # Exécuté si name != "Sarah"
Bob
Bill
Rachel
[1033]:
for name in names:
if name == "Sarah":
break # Stoppe la boucle lorsque name == "Sarah"
print(name)
Bob
Bill
Contrairement aux références créés dans le scope d’une fonction, les références créés dans un bloc for
ne sont pas supprimées à la fin de l’exécution du bloc. La référence name
est donc toujours accessible et sa valeur est celle de la dernière itération. Ici on voit que sa valeur est 'Sarah'
, ce qui a effectivement stoppé l’exécution de la boucle.
[1034]:
name
[1034]:
'Sarah'
Lire et écrire dans un fichier¶
Cours Primaire
On peut facilement lire le contenu d’un fichier texte et écrire du texte dans un fichier. Pour cela, on utilise: * la fonction open
qui permet d’ouvrir un fichier et de le lire ou d’y écrire quelque chose * le keyword with
qui signale l’utilisation d’un objet qualifié de context manager (ici, la fonction open
). L’emploi de ce genre d’objet avec un bloc ``with`` permet de s’assurer de la fermeture de la ressource ouverte (ici, un fichier), même si une erreur se produit dans
l’exécution du bloc de code
[1035]:
%%writefile data.txt
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
Writing data.txt
La fonction open
ouvre le fichier en mode lecture ("r"
pour read) et retourne un objet référencé par f
(on peut choisir n’importe quel nom) et lié au fichier en cours de lecture.
[1036]:
with open("data.txt", "r") as f:
print(type(f))
print(dir(f))
<class '_io.TextIOWrapper'>
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'write_through', 'writelines']
On peut lire le contenu d’un fichier ligne par ligne en itérant sur l’objet retourné par la fonction open
. Cet objet est en fait un iterator. Chaque ligne du fichier se termine par un caractère retour à la ligne. On peut donner l’argument end=""
à la fonction print
pour qu’elle ne saute pas trop de lignes.
[1037]:
with open("data.txt", "r") as f:
for line in f:
print(line)
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
[1039]:
with open("data.txt", "r") as f:
for line in f:
print(line, end="")
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
Comme f
est un iterator, on ne peut itérer dessus qu’un seule fois.
[665]:
with open("data.txt", "r") as f:
for line in f:
print(line, end="")
for line in f:
print(line, end="")
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
L’avantage de lire un fichier ligne par ligne est double: * On ne charge pas l’ensemble des données du fichier dans la mémoire * On peut arrêter sa lecture avant la fin si on cherche quelque chose de précis
[666]:
with open("data.txt", "r") as f:
for i, line in enumerate(f):
if "Bob" in line:
print(f"I've found Bob @line {i + 1} !!!")
break # Stoppe la lecture du fichier
I've found Bob @line 3 !!!
[667]:
with open("data.txt", "r") as f:
for i, line in enumerate(f):
line = line.strip()
if line.startswith("Bob"):
age = line.split(",")[1]
break
print(f"Bob is {age} yo.")
Bob is 28 yo.
Pour écrire dans un nouveau fichier, la démarche est tout à fait similare. Au lieu de passer l’argument r
à la fonction open
, on lui passe l’argument w
(pour write). La méthode write
n’accepte comme argument que des objets de type string
. Au besoin, on convertit les objets (float
) par exemple en string
en les passant comme argument à la fonction str
. Il faut faire attention à inclure le caractère spécial "\n"
pour marquer les sauts de ligne.
[671]:
names = ["Sarah", "Rachel", "Bob", "Bill"]
with open("new_data.txt", "w") as f:
for name in names:
f.write(name + "\n")
[672]:
!type new_data.txt
Sarah
Rachel
Bob
Bill
[673]:
!del /f new_data.txt data.txt
%reset -f
Traceback¶
Suivie à la trace
Le traceback est le long message que Python affiche lorsqu’une erreur survient pendant l’exécution du code. Le traceback se lit de bas en haut. Il est très utile pour débugger un script.
[674]:
def first_level(a, b):
return second_level(a, b)
def second_level(a, b):
return third_level(a, b)
def third_level(a, b):
return a + b
[675]:
first_level(1, 2)
[675]:
3
L’addition des paramètres a
et b
est réalisée par la fonction third_level
. On va déclencher une TypeError
en tentant d’additioner une string
avec un int
, ce qui est impossible.
[676]:
"Bob" + 1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-676-1e6b406786db> in <module>
----> 1 "Bob" + 1
TypeError: can only concatenate str (not "int") to str
[677]:
first_level("Bob", 1)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-677-26206af4b823> in <module>
----> 1 first_level("Bob", 1)
<ipython-input-674-0c177701bd8c> in first_level(a, b)
1 def first_level(a, b):
----> 2 return second_level(a, b)
3
4
5 def second_level(a, b):
<ipython-input-674-0c177701bd8c> in second_level(a, b)
4
5 def second_level(a, b):
----> 6 return third_level(a, b)
7
8
<ipython-input-674-0c177701bd8c> in third_level(a, b)
8
9 def third_level(a, b):
---> 10 return a + b
TypeError: can only concatenate str (not "int") to str
Le traceback nous indique que l’erreur s’est produite à la ligne return a + b
du code dans la fonction third_level
. Il afficher ensuite (de bas en haut) les appels successifs aux trois fonctions imbriquées.
[678]:
%reset -f
Numpy, Pandas et Matplotlib en express¶
Numpy
, pandas
et Matplotlib
sont trois librairies incontournables de l’écosystème scientifique de Python: * Numpy
permet de créer des tableaux à plusieurs dimensions contenant des nombres, et de les transformer facilement à l’aide de nombreuses formules mathématique, elle a l’avantage d’accélérer les calculs comparé à Python, * pandas
permet de créer des tableaux contenant des objets de type différent, et d’effectuer des opérations très similaires à celles qu’on peut
effectuer avec Excel, * Matplotlib
permet de créer des figures et de les personnaliser dans les moindres détails.
Ces trois librairies, développées depuis des années et des années, sont très fournies. L’objectif de ce tutoriel est de donner une impression de ce qu’il est possible de faire avec elles.
Numpy¶
On importe Numpy
de la manière suivante, établie par convention.
[68]:
import numpy as np
Les calculs réalisés avec Numpy
sont plus rapides que les calculs effectués avec du pur Python.
[69]:
%timeit sum(range(100_000))
%timeit np.sum(np.arange(100_000, dtype=np.int64))
2.14 ms ± 103 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
77.5 µs ± 615 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
On a maintenant à disposition des dizaines et des dizaines de fonctions pour réaliser des opérations mathématiques.
[3]:
print(dir(np))
['ALLOW_THREADS', 'AxisError', 'BUFSIZE', 'CLIP', 'ComplexWarning', 'DataSource', 'ERR_CALL', 'ERR_DEFAULT', 'ERR_IGNORE', 'ERR_LOG', 'ERR_PRINT', 'ERR_RAISE', 'ERR_WARN', 'FLOATING_POINT_SUPPORT', 'FPE_DIVIDEBYZERO', 'FPE_INVALID', 'FPE_OVERFLOW', 'FPE_UNDERFLOW', 'False_', 'Inf', 'Infinity', 'MAXDIMS', 'MAY_SHARE_BOUNDS', 'MAY_SHARE_EXACT', 'MachAr', 'ModuleDeprecationWarning', 'NAN', 'NINF', 'NZERO', 'NaN', 'PINF', 'PZERO', 'RAISE', 'RankWarning', 'SHIFT_DIVIDEBYZERO', 'SHIFT_INVALID', 'SHIFT_OVERFLOW', 'SHIFT_UNDERFLOW', 'ScalarType', 'Tester', 'TooHardError', 'True_', 'UFUNC_BUFSIZE_DEFAULT', 'UFUNC_PYVALS_NAME', 'VisibleDeprecationWarning', 'WRAP', '_NoValue', '_UFUNC_API', '__NUMPY_SETUP__', '__all__', '__builtins__', '__cached__', '__config__', '__doc__', '__file__', '__git_revision__', '__loader__', '__mkl_version__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_add_newdoc_ufunc', '_arg', '_distributor_init', '_globals', '_mat', '_mklinit', '_pytesttester', 'abs', 'absolute', 'absolute_import', 'add', 'add_docstring', 'add_newdoc', 'add_newdoc_ufunc', 'alen', 'all', 'allclose', 'alltrue', 'amax', 'amin', 'angle', 'any', 'append', 'apply_along_axis', 'apply_over_axes', 'arange', 'arccos', 'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctan2', 'arctanh', 'argmax', 'argmin', 'argpartition', 'argsort', 'argwhere', 'around', 'array', 'array2string', 'array_equal', 'array_equiv', 'array_repr', 'array_split', 'array_str', 'asanyarray', 'asarray', 'asarray_chkfinite', 'ascontiguousarray', 'asfarray', 'asfortranarray', 'asmatrix', 'asscalar', 'atleast_1d', 'atleast_2d', 'atleast_3d', 'average', 'bartlett', 'base_repr', 'binary_repr', 'bincount', 'bitwise_and', 'bitwise_not', 'bitwise_or', 'bitwise_xor', 'blackman', 'block', 'bmat', 'bool', 'bool8', 'bool_', 'broadcast', 'broadcast_arrays', 'broadcast_to', 'busday_count', 'busday_offset', 'busdaycalendar', 'byte', 'byte_bounds', 'bytes0', 'bytes_', 'c_', 'can_cast', 'cast', 'cbrt', 'cdouble', 'ceil', 'cfloat', 'char', 'character', 'chararray', 'choose', 'clip', 'clongdouble', 'clongfloat', 'column_stack', 'common_type', 'compare_chararrays', 'compat', 'complex', 'complex128', 'complex64', 'complex_', 'complexfloating', 'compress', 'concatenate', 'conj', 'conjugate', 'convolve', 'copy', 'copysign', 'copyto', 'core', 'corrcoef', 'correlate', 'cos', 'cosh', 'count_nonzero', 'cov', 'cross', 'csingle', 'ctypeslib', 'cumprod', 'cumproduct', 'cumsum', 'datetime64', 'datetime_as_string', 'datetime_data', 'deg2rad', 'degrees', 'delete', 'deprecate', 'deprecate_with_doc', 'diag', 'diag_indices', 'diag_indices_from', 'diagflat', 'diagonal', 'diff', 'digitize', 'disp', 'divide', 'division', 'divmod', 'dot', 'double', 'dsplit', 'dstack', 'dtype', 'e', 'ediff1d', 'einsum', 'einsum_path', 'emath', 'empty', 'empty_like', 'equal', 'errstate', 'euler_gamma', 'exp', 'exp2', 'expand_dims', 'expm1', 'extract', 'eye', 'fabs', 'fastCopyAndTranspose', 'fft', 'fill_diagonal', 'find_common_type', 'finfo', 'fix', 'flatiter', 'flatnonzero', 'flexible', 'flip', 'fliplr', 'flipud', 'float', 'float16', 'float32', 'float64', 'float_', 'float_power', 'floating', 'floor', 'floor_divide', 'fmax', 'fmin', 'fmod', 'format_float_positional', 'format_float_scientific', 'format_parser', 'frexp', 'frombuffer', 'fromfile', 'fromfunction', 'fromiter', 'frompyfunc', 'fromregex', 'fromstring', 'full', 'full_like', 'fv', 'gcd', 'generic', 'genfromtxt', 'geomspace', 'get_array_wrap', 'get_include', 'get_printoptions', 'getbufsize', 'geterr', 'geterrcall', 'geterrobj', 'gradient', 'greater', 'greater_equal', 'half', 'hamming', 'hanning', 'heaviside', 'histogram', 'histogram2d', 'histogram_bin_edges', 'histogramdd', 'hsplit', 'hstack', 'hypot', 'i0', 'identity', 'iinfo', 'imag', 'in1d', 'index_exp', 'indices', 'inexact', 'inf', 'info', 'infty', 'inner', 'insert', 'int', 'int0', 'int16', 'int32', 'int64', 'int8', 'int_', 'int_asbuffer', 'intc', 'integer', 'interp', 'intersect1d', 'intp', 'invert', 'ipmt', 'irr', 'is_busday', 'isclose', 'iscomplex', 'iscomplexobj', 'isfinite', 'isfortran', 'isin', 'isinf', 'isnan', 'isnat', 'isneginf', 'isposinf', 'isreal', 'isrealobj', 'isscalar', 'issctype', 'issubclass_', 'issubdtype', 'issubsctype', 'iterable', 'ix_', 'kaiser', 'kron', 'lcm', 'ldexp', 'left_shift', 'less', 'less_equal', 'lexsort', 'lib', 'linalg', 'linspace', 'little_endian', 'load', 'loads', 'loadtxt', 'log', 'log10', 'log1p', 'log2', 'logaddexp', 'logaddexp2', 'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'logspace', 'long', 'longcomplex', 'longdouble', 'longfloat', 'longlong', 'lookfor', 'ma', 'mafromtxt', 'mask_indices', 'mat', 'math', 'matmul', 'matrix', 'matrixlib', 'max', 'maximum', 'maximum_sctype', 'may_share_memory', 'mean', 'median', 'memmap', 'meshgrid', 'mgrid', 'min', 'min_scalar_type', 'minimum', 'mintypecode', 'mirr', 'mod', 'modf', 'moveaxis', 'msort', 'multiply', 'nan', 'nan_to_num', 'nanargmax', 'nanargmin', 'nancumprod', 'nancumsum', 'nanmax', 'nanmean', 'nanmedian', 'nanmin', 'nanpercentile', 'nanprod', 'nanquantile', 'nanstd', 'nansum', 'nanvar', 'nbytes', 'ndarray', 'ndenumerate', 'ndfromtxt', 'ndim', 'ndindex', 'nditer', 'negative', 'nested_iters', 'newaxis', 'nextafter', 'nonzero', 'not_equal', 'nper', 'npv', 'numarray', 'number', 'obj2sctype', 'object', 'object0', 'object_', 'ogrid', 'oldnumeric', 'ones', 'ones_like', 'outer', 'packbits', 'pad', 'partition', 'percentile', 'pi', 'piecewise', 'place', 'pmt', 'poly', 'poly1d', 'polyadd', 'polyder', 'polydiv', 'polyfit', 'polyint', 'polymul', 'polynomial', 'polysub', 'polyval', 'positive', 'power', 'ppmt', 'print_function', 'printoptions', 'prod', 'product', 'promote_types', 'ptp', 'put', 'put_along_axis', 'putmask', 'pv', 'quantile', 'r_', 'rad2deg', 'radians', 'random', 'rank', 'rate', 'ravel', 'ravel_multi_index', 'real', 'real_if_close', 'rec', 'recarray', 'recfromcsv', 'recfromtxt', 'reciprocal', 'record', 'remainder', 'repeat', 'require', 'reshape', 'resize', 'result_type', 'right_shift', 'rint', 'roll', 'rollaxis', 'roots', 'rot90', 'round', 'round_', 'row_stack', 's_', 'safe_eval', 'save', 'savetxt', 'savez', 'savez_compressed', 'sctype2char', 'sctypeDict', 'sctypeNA', 'sctypes', 'searchsorted', 'select', 'set_numeric_ops', 'set_printoptions', 'set_string_function', 'setbufsize', 'setdiff1d', 'seterr', 'seterrcall', 'seterrobj', 'setxor1d', 'shape', 'shares_memory', 'short', 'show_config', 'sign', 'signbit', 'signedinteger', 'sin', 'sinc', 'single', 'singlecomplex', 'sinh', 'size', 'sometrue', 'sort', 'sort_complex', 'source', 'spacing', 'split', 'sqrt', 'square', 'squeeze', 'stack', 'std', 'str', 'str0', 'str_', 'string_', 'subtract', 'sum', 'swapaxes', 'sys', 'take', 'take_along_axis', 'tan', 'tanh', 'tensordot', 'test', 'testing', 'tile', 'timedelta64', 'trace', 'tracemalloc_domain', 'transpose', 'trapz', 'tri', 'tril', 'tril_indices', 'tril_indices_from', 'trim_zeros', 'triu', 'triu_indices', 'triu_indices_from', 'true_divide', 'trunc', 'typeDict', 'typeNA', 'typecodes', 'typename', 'ubyte', 'ufunc', 'uint', 'uint0', 'uint16', 'uint32', 'uint64', 'uint8', 'uintc', 'uintp', 'ulonglong', 'unicode', 'unicode_', 'union1d', 'unique', 'unpackbits', 'unravel_index', 'unsignedinteger', 'unwrap', 'ushort', 'vander', 'var', 'vdot', 'vectorize', 'version', 'void', 'void0', 'vsplit', 'vstack', 'warnings', 'where', 'who', 'zeros', 'zeros_like']
L’objet le plus important est le ndarray
, pour tableau à n dimensions.
[71]:
arr = np.array([1, 2, 3])
type(arr)
[71]:
numpy.ndarray
[72]:
arr = np.array([[1, 2, 3], [4, 5, 6]])
arr
[72]:
array([[1, 2, 3],
[4, 5, 6]])
[73]:
arr.shape
[73]:
(2, 3)
[74]:
arr.T
[74]:
array([[1, 4],
[2, 5],
[3, 6]])
[8]:
arr.T.shape
[8]:
(3, 2)
[9]:
np.arange(10, 50, 3)
[9]:
array([10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49])
[10]:
np.zeros(shape=10)
[10]:
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
[75]:
np.zeros(shape=(2, 3))
[75]:
array([[0., 0., 0.],
[0., 0., 0.]])
[12]:
np.zeros(shape=(2, 3, 1, 5))
[12]:
array([[[[0., 0., 0., 0., 0.]],
[[0., 0., 0., 0., 0.]],
[[0., 0., 0., 0., 0.]]],
[[[0., 0., 0., 0., 0.]],
[[0., 0., 0., 0., 0.]],
[[0., 0., 0., 0., 0.]]]])
[13]:
np.ones(shape=(2, 5))
[13]:
array([[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]])
[14]:
arr = np.array([[1, 2, 3], [10, 20, 30]]).T
arr
[14]:
array([[ 1, 10],
[ 2, 20],
[ 3, 30]])
On accède à un élément d’un objet ndarray
en précisant sa position (en partant de 0) suivant les axes de l’objet, séparés de virgules. Pour un tableau à deux dimensions (une matrice), le premier axe est celui des lignes, le deuxième celui des colonnes.
[76]:
arr[0, 0]
[76]:
1
[16]:
arr[2, 1]
[16]:
30
Pour accéder à tous les éléments suivant un axe (les colonnes, les lignes, etc.), on utilise le symbole :
.
[77]:
arr[:, 0]
[77]:
array([1, 4])
[78]:
arr[:, 1]
[78]:
array([2, 5])
[19]:
arr[0, :]
[19]:
array([ 1, 10])
On peut effectuer des opérations mathématiques sur tous les éléments d’un ndarray
.
[79]:
arr + 1
[79]:
array([[2, 3, 4],
[5, 6, 7]])
[80]:
arr * 3
[80]:
array([[ 3, 6, 9],
[12, 15, 18]])
Les ndarray
sont des objets mutables.
[22]:
arr[0, 0] = -1
arr
[22]:
array([[-1, 10],
[ 2, 20],
[ 3, 30]])
[23]:
arr[0, :] = 1
arr
[23]:
array([[ 1, 1],
[ 2, 20],
[ 3, 30]])
[24]:
arr[:, 1] = 3
arr
[24]:
array([[1, 3],
[2, 3],
[3, 3]])
[25]:
arr[:, 1] = arr[:, 1] * arr[:, 0]
arr
[25]:
array([[1, 3],
[2, 6],
[3, 9]])
On peut sélectionner des éléments d’un ndarray
suivant une condition.
[26]:
arr > 5
[26]:
array([[False, False],
[False, True],
[False, True]])
[82]:
arr[arr > 5]
[82]:
array([6])
On peut piocher dans les dizaines de fonction dont Numpy
dispose pour transformer les données
[83]:
np.sum(arr)
[83]:
21
axis=0
signifie que l’addition est réalisée suivant le dimension 0
, ce sont donc les lignes qui sont additionnées. axis=1
indique que ce sont les colonnes qui sont additionnées.
[84]:
np.sum(arr, axis=0)
[84]:
array([5, 7, 9])
[85]:
np.sum(arr, axis=1)
[85]:
array([ 6, 15])
pandas¶
On importe pandas
de la manière suivante, établie par convention.
[86]:
import pandas as pd
L’objet le plus important est le DataFrame
(tableau de données). On peut en créer de plusieurs manières différentes.
[87]:
df = pd.DataFrame({
"name": ["Rachel", "Bob", "Bill", "Anna", "Leila", "John"],
"age": [23, 25, 24, 30, 19, 26],
"height": [1.85, 1.79, 1.82, 1.72, 1.95, 1.65]
})
[88]:
type(df)
[88]:
pandas.core.frame.DataFrame
[89]:
df
[89]:
name | age | height | |
---|---|---|---|
0 | Rachel | 23 | 1.85 |
1 | Bob | 25 | 1.79 |
2 | Bill | 24 | 1.82 |
3 | Anna | 30 | 1.72 |
4 | Leila | 19 | 1.95 |
5 | John | 26 | 1.65 |
[90]:
df.head()
[90]:
name | age | height | |
---|---|---|---|
0 | Rachel | 23 | 1.85 |
1 | Bob | 25 | 1.79 |
2 | Bill | 24 | 1.82 |
3 | Anna | 30 | 1.72 |
4 | Leila | 19 | 1.95 |
[91]:
df.tail()
[91]:
name | age | height | |
---|---|---|---|
1 | Bob | 25 | 1.79 |
2 | Bill | 24 | 1.82 |
3 | Anna | 30 | 1.72 |
4 | Leila | 19 | 1.95 |
5 | John | 26 | 1.65 |
[92]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 3 columns):
name 6 non-null object
age 6 non-null int64
height 6 non-null float64
dtypes: float64(1), int64(1), object(1)
memory usage: 272.0+ bytes
[93]:
df.describe()
[93]:
age | height | |
---|---|---|
count | 6.000000 | 6.000000 |
mean | 24.500000 | 1.796667 |
std | 3.619392 | 0.104243 |
min | 19.000000 | 1.650000 |
25% | 23.250000 | 1.737500 |
50% | 24.500000 | 1.805000 |
75% | 25.750000 | 1.842500 |
max | 30.000000 | 1.950000 |
[94]:
df.shape
[94]:
(6, 3)
Les entrées d’un tableau sont son index (ses lignes) et ses colonnes.
[40]:
df.index
[40]:
RangeIndex(start=0, stop=6, step=1)
Pour l’instant l’index du DataFrame
est juste un indice numérique démarrant à 0. On peut définir un index plus intéressant en utilisant la colonne name
.
[95]:
df = df.set_index("name")
df
[95]:
age | height | |
---|---|---|
name | ||
Rachel | 23 | 1.85 |
Bob | 25 | 1.79 |
Bill | 24 | 1.82 |
Anna | 30 | 1.72 |
Leila | 19 | 1.95 |
John | 26 | 1.65 |
[42]:
df.columns
[42]:
Index(['age', 'height'], dtype='object')
On peut indexer
le tableau avec les méthodes loc
et iloc
.
[96]:
df.loc["Bob"]
[96]:
age 25.00
height 1.79
Name: Bob, dtype: float64
[97]:
df.loc["Bob", "height"]
[97]:
1.79
[98]:
df.iloc[1, 1]
[98]:
1.79
On peut sélectionner une colonne entière pour l’utiliser et la modifier
[99]:
df["height"]
[99]:
name
Rachel 1.85
Bob 1.79
Bill 1.82
Anna 1.72
Leila 1.95
John 1.65
Name: height, dtype: float64
[100]:
df["height"] = df["height"] + 0.2
df.head()
[100]:
age | height | |
---|---|---|
name | ||
Rachel | 23 | 2.05 |
Bob | 25 | 1.99 |
Bill | 24 | 2.02 |
Anna | 30 | 1.92 |
Leila | 19 | 2.15 |
On peut ajouter des colonnes.
[101]:
df["age_plus_10"] = df["age"] + 10
df.head()
[101]:
age | height | age_plus_10 | |
---|---|---|---|
name | |||
Rachel | 23 | 2.05 | 33 |
Bob | 25 | 1.99 | 35 |
Bill | 24 | 2.02 | 34 |
Anna | 30 | 1.92 | 40 |
Leila | 19 | 2.15 | 29 |
Lorsqu’on les données d’une colonne sont des nombres, on a en fait à disposition toutes les fonctions de Numpy
. On a aussi bien d’autres fonctions (des méthodes en fait) disponibles.
[102]:
df["age"].sum()
[102]:
147
On peut facilement lire et écrire des fichiers (.csv
par exemple) avec pandas
.
[103]:
%%writefile data.csv
name,age,height,gender
Sarah,27,1.67,F
Bob,28,1.89,M
Rachel,24,1.81,F
Bill,22,1.73,M
John,26,1.67,M
Leila,19,1.78,F
Writing data.csv
[104]:
people = pd.read_csv("data.csv")
people
[104]:
name | age | height | gender | |
---|---|---|---|---|
0 | Sarah | 27 | 1.67 | F |
1 | Bob | 28 | 1.89 | M |
2 | Rachel | 24 | 1.81 | F |
3 | Bill | 22 | 1.73 | M |
4 | John | 26 | 1.67 | M |
5 | Leila | 19 | 1.78 | F |
On peut compter le nombre d’éléments identiques dans une colonne avec la méthode value_counts
. Cette méthode est disponible pour les objets de type Series
qui représentent en fait les colonnes. La méthode retourne aussi un objet de type `Series.
[105]:
people["gender"].value_counts()
[105]:
M 3
F 3
Name: gender, dtype: int64
On peut grouper les données et agréger les groupes obtenus à l’aide de fonctions.
[106]:
people.groupby("gender").agg({"age": ["max", "min"], "height":["mean", "std"]})
[106]:
age | height | |||
---|---|---|---|---|
max | min | mean | std | |
gender | ||||
F | 27 | 19 | 1.753333 | 0.073711 |
M | 28 | 22 | 1.763333 | 0.113725 |
On peut sélectionner des données suivant des conditions.
[107]:
people["gender"] == "F"
[107]:
0 True
1 False
2 True
3 False
4 False
5 True
Name: gender, dtype: bool
[109]:
girls = people[people["gender"] == "F"]
girls
[109]:
name | age | height | gender | |
---|---|---|---|---|
0 | Sarah | 27 | 1.67 | F |
2 | Rachel | 24 | 1.81 | F |
5 | Leila | 19 | 1.78 | F |
On peut supprimer une colonne avec la méthode drop
.
[110]:
girls = girls.drop(columns=["gender"])
girls
[110]:
name | age | height | |
---|---|---|---|
0 | Sarah | 27 | 1.67 |
2 | Rachel | 24 | 1.81 |
5 | Leila | 19 | 1.78 |
[111]:
girls.to_csv("girls.csv", index=False)
[112]:
!type girls.csv
name,age,height
Sarah,27,1.67
Rachel,24,1.81
Leila,19,1.78
[113]:
!del /f data.csv girls.csv
Matplotlib¶
On importe Matplotlib
de la manière suivante, établie par convention.
[114]:
import matplotlib.pyplot as plt
Matplotlib
a un mode similaire à celui de Matlab
, on enchaîne juste des appels à des fonctions pour réaliser des actions sur le même objet.
[115]:
plt.scatter([1, 3, 10], [-2, 3, 0])
plt.title("Titre")
plt.xlabel("X axis")
plt.savefig("comme_matlab.png")

[116]:
!comme_matlab.png
On va préférer la méthode par laquelle on modifier directement la figure au travers des objets qui la constituent.
[117]:
fig, ax = plt.subplots()
ax.scatter([1, 3, 10], [-2, 3, 0])
ax.set_title("Titre")
ax.set_xlabel("X Axis")
fig.savefig("avec_des_objets.jpg")

[118]:
!image_with_matplotlib.png
'image_with_matplotlib.png' n'est pas reconnu en tant que commande interne
ou externe, un programme ex‚cutable ou un fichier de commandes.
On peut utiliser Matplotlib
directement mais on retrouve en fait la librairie un peu partout, dans pandas
notamment.
[119]:
ax = df.plot(x="age", y="height", kind="scatter", title="Titre")
fig = ax.get_figure()
fig.savefig("avec_pandas.pdf")

[120]:
!avec_pandas.pdf
[67]:
!del /f comme_matlab.png avec_des_objets.jpg avec_pandas.pdf
Introduction pas à pas de Python [version étendue]¶
Ce notebook constitue une rapide introduction à Python. Le langage est découvert en exécutant un grand nombre de simples et courtes lignes de code. L’objectif est d’entrapercevoir la manière dont Python fonctionne afin d’éviter de tomber dans les pièges classiques des débutants.
Cette approche est inspirée des tutoriels Python by immersion et Python Epiphanies créés par Stuart Williams.
Indentation¶
Tout est dans le style
Python a été conçu de manière à être facile à lire, partant du principe qu’on passe plus de temps à lire du code qu’à en écrire. Cela se matérialise par l’importance de l”indentation (décalage d’une ligne de code vers la droite). L’indentation du code détermine en partie sa structure. Dans l’exemple ci-dessous, les deux instructions print
sont situées au même niveau d’indentation.
[1]:
print("Hey")
print("you")
Hey
you
Le code est exécuté normalement. La deuxième instruction du code suivant a été décalée de deux espaces, le code ne peut tout simplement plus être exécuté car cette structure n’a aucun sens pour Python.
[2]:
print("Hey")
print("you")
File "<ipython-input-2-a6cbeba02af8>", line 2
print("you")
^
IndentationError: unexpected indent
L’utilité de l’indentation est plus évidente dans le cas de blocs logiques, tel qu’un bloc for
. Le code ci-dessous s’exécute correctement.
[3]:
for i in [1, 2, 3]:
print(i) # L'indentation est de 4 espaces en général.
1
2
3
Alors que le code ci-dessous ne peut pas être exécuté, les instructions sous la ligne for
n’étant pas indentées. Comme on peut le voir, c’est le code du dessus qui est le plus facile à lire, l’indentation nous aide à comprendre la structure logique du code.
[4]:
for i in [1, 2, 3]:
print(i)
File "<ipython-input-4-3e96090b8527>", line 2
print(i)
^
IndentationError: expected an indented block
Afin de définir les boucles et autres structures de contrôle, d’autres langages utilisent des symbôles ({}
, ()
). En choisissant d’utiliser l’indentation au lieu de symboles, Python a faix le choix du code épuré.
Commentaires et pass
¶
Fainéant
Les commentaires sont des lignes qui débutent par un signe #
. Ces lignes-là sont ignorées par l’interpréteur.
[5]:
# Ceci est un commentaire.
[6]:
# On
# peut
# les
# enchaîner
[7]:
###### Ceci aussi est un commentaire.
On peut rajouter un commentaire à la suite d’un code valide (séparé de deux espaces par convention).
[8]:
3.14 # Ceci est aussi un commentaire.
[8]:
3.14
Le keyword pass fait comme son nom l’indique, il passe son tour.
[9]:
pass
Objets¶
Tout est un objet!
Exécuter les instructions simples ci-dessous peut faire penser que Python ne fait rien de plus qu’une simple calculette.
[10]:
3.4
[10]:
3.4
[11]:
3.4 + 4
[11]:
7.4
Pourtant, beaucoup de choses se passent!
[12]:
dir(3.4)
[12]:
['__abs__',
'__add__',
'__bool__',
'__class__',
'__delattr__',
'__dir__',
'__divmod__',
'__doc__',
'__eq__',
'__float__',
'__floordiv__',
'__format__',
'__ge__',
'__getattribute__',
'__getformat__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__int__',
'__le__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__new__',
'__pos__',
'__pow__',
'__radd__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rmod__',
'__rmul__',
'__round__',
'__rpow__',
'__rsub__',
'__rtruediv__',
'__set_format__',
'__setattr__',
'__sizeof__',
'__str__',
'__sub__',
'__subclasshook__',
'__truediv__',
'__trunc__',
'as_integer_ratio',
'conjugate',
'fromhex',
'hex',
'imag',
'is_integer',
'real']
La fonction dir
retourne une liste de tous les attributs d’un objet. La liste ci-dessus montre donc tous les attributs de l’objet 3.4
. Les derniers attributs affichés, dont is_integer
, nous indiquent que Python a compris que 3.4
est un nombre. On peut le vérifier en inspectant la valeur de l’attribut __class__
. Pour accéder à la valeur d’un attribut, on rajout un point ``.`` après l’objet suivi du nom de l’attribut.
[13]:
3.4.__class__
[13]:
float
float
est un type d’objet qui représente les nombres réels. Mais plutôt que de vérifier le type d’un objet en accédant à son attribut *__class__*, on peut utiliser la fonction type
. Cette fonction, comme dir
, fait partie des fonctions dites built-in car faisant parties intégrantes de Python (il n’y a pas besoin d’importer quoi que ce soit pour les appeler). On va voir plusieurs de ces fonctions dans la suite du notebook.
[14]:
type(3.4)
[14]:
float
On a donc vu que lorsqu’on exécute 3.4
, Python ne fait pas juste qu’afficher ce nombre mais l’interprète comme un objet de type float
doté de nombreux attributs. Que se passe-t-il alors lorsqu’on exécute 3.4 + 4
?
[15]:
3.4.__add__(4)
[15]:
7.4
L’addition que nous avons écrite est en fait exécutée par l’instruction ci-dessus. Python voit que nous avons écrit un +
après 3.4
, il cherche alors l’attribut dunder (racourci de double underscore donné aux attributs qui débutent et terminent par __
) __add__
dans la liste des attributs de 3.4
. L’attribut __add__
des floats
est en fait une méthode qui est capable de faire une action, dans ce cas-là, une addition. Lorsque Python trouve l’attribut
__add__
de 3.4
, il l’appelle avec l”argument 4
. La valeur retournée est le résultat de l’addition.
Python n’est donc évidemment pas qu’une simple calculatrice. Lorsqu’il évalue les expressions qu’on lui donne, il crée des objets dotés d’un type et d’attributs. Ces attributs sont soit équivalent à des propriétés, comme __class__
, soit des méthodes, comme __add__
. Cette mécanique est présente partout dans le langage, celui-ci travaille en effet beaucoup pour nous. Il faut aussi noter que Python s’occupe d’allouer et libérer la mémoire à notre place.
[16]:
type(3.4.__add__)
[16]:
method-wrapper
Chaque objet créé a sa propre identité définie comme son adresse physique dans la mémoire.
[17]:
id(3.4)
[17]:
2556173076280
[18]:
id(3.4)
[18]:
2556173076352
[19]:
id(4)
[19]:
140734432777120
dir
et id
sont des fonctions qu’on n’utilise jamais dans un script mais qui sont très utiles pour apprendre Python et inspecter des objets.
[20]:
type(dir)
[20]:
builtin_function_or_method
[21]:
type(id)
[21]:
builtin_function_or_method
Une fonction est aussi un objet (tout est un objet!) avec ses propres attributs, un type et une identité.
[22]:
dir(dir)
[22]:
['__call__',
'__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__name__',
'__ne__',
'__new__',
'__qualname__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__self__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__text_signature__']
[23]:
id(dir)
[23]:
2556130333032
[24]:
id(dir)
[24]:
2556130333032
Erreurs¶
Antifragilité
Lorsqu’on écrit du code en Python et qu’on l’exécute pour la première fois, il est habituel de voir un message d’erreur apparaître. Ces erreurs nous indiquent ce qui ne fonctionne pas dans le code, et nous oriente pour résoudre le bug qui a causé l’erreur. Python dispose d’un grand nombre de genres d’erreur, à force de les déclencher on s’habitue à les reconnaître et on comprend de plus en plus rapidement d’où vient le problème.
[25]:
type(1)
type(2) # L'espace déclenche l'erreur
File "<ipython-input-25-f456bc8300ff>", line 2
type(2) # L'espace déclenche l'erreur
^
IndentationError: unexpected indent
[26]:
1
3 +)°^ 3 # Cette suite de symboles n'a aucun sens
File "<ipython-input-26-57174e9a2a28>", line 2
3 +)°^ 3 # Cette suite de symboles n'a aucun sens
^
SyntaxError: invalid syntax
Les erreurs IntendationError
et SyntaxError
nous indique que le code n’a pas été écrit correctement. L’interpréteur analyse tout le code qu’on a écrit, s’il détecte ce genre d’erreur, il nous l’indique et n’exécute aucune ligne de code. Toutes (?) les autres erreurs qu’on peut avoir surviennent pendant l’exécution du code (runtime).
print
et help
¶
Pour y voir plus clair
print
affiche les objets passés à la fonction. help
affiche l’aide d’un objet.
[27]:
print(3.4, 4, 5, 1, 0)
3.4 4 5 1 0
[28]:
help(print)
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
On va beaucoup utiliser la fonction print
. Le début de sa définition est print(value, ..., sep=' ')
:
value, ...
:print
accepte un nombre illimité d’arguments séparés par une virgule
[31]:
print(3.4, 4, 6, 103, 3030, 0, 0)
3.4 4 6 103 3030 0 0
sep=' '
: l’espacement par défaut entre deux valeurs affichées parprint
est un espace, on peut le changer en spécifiant le paramètresep
[32]:
print(3.4, 4, 6, 103, 3030, 0, 0, sep=" :) ")
3.4 :) 4 :) 6 :) 103 :) 3030 :) 0 :) 0
[33]:
# "\n" est interprété comme un retour à la ligne
print(3.4, 4, 6, 103, 3030, 0, 0, sep="\n")
3.4
4
6
103
3030
0
0
[34]:
# "\t" est interprété comme une tabulation
print(3.4, 4, 6, 103, 3030, 0, 0, sep="\t")
3.4 4 6 103 3030 0 0
Keywords¶
Un peu de liant
Avec l’indentation, les keywords structurent le langage. On utilisera principalement les suivants:
- Le vide:
None
- Booléens:
True
,False
- Opérations booléennes:
and
,or
,not
- Appartenance:
in
,not in
- Identification:
is
,is not
- Boucles:
for
,while
break
,continue
,pass
, - Conditions:
if
,elif
,else
- Fonctions:
def
,return
- Import:
import
,from
,as
- Déférencement:
del
- Pas d’action:
pass
[35]:
help("keywords")
Here is a list of the Python keywords. Enter any keyword to get more help.
False class from or
None continue global pass
True def if raise
and del import return
as elif in try
assert else is while
async except lambda with
await finally nonlocal yield
break for not
Les keywords sont des mots réservés, aucun objet ne peut avoir un nom qui fait partie des keywords.
[36]:
True = 2
File "<ipython-input-36-da3e5402d318>", line 1
True = 2
^
SyntaxError: can't assign to keyword
[37]:
true = 2 # On peut faire ça, mais c'est maladroit!
[38]:
and = 3
File "<ipython-input-38-f117365eecb9>", line 1
and = 3
^
SyntaxError: invalid syntax
[39]:
AND = 3 # Toujours autant maladroit!
[40]:
help(for)
File "<ipython-input-40-c96d1524574d>", line 1
help(for)
^
SyntaxError: invalid syntax
[41]:
help("for")
The "for" statement
*******************
The "for" statement is used to iterate over the elements of a sequence
(such as a string, tuple or list) or other iterable object:
for_stmt ::= "for" target_list "in" expression_list ":" suite
["else" ":" suite]
The expression list is evaluated once; it should yield an iterable
object. An iterator is created for the result of the
"expression_list". The suite is then executed once for each item
provided by the iterator, in the order returned by the iterator. Each
item in turn is assigned to the target list using the standard rules
for assignments (see Assignment statements), and then the suite is
executed. When the items are exhausted (which is immediately when the
sequence is empty or an iterator raises a "StopIteration" exception),
the suite in the "else" clause, if present, is executed, and the loop
terminates.
A "break" statement executed in the first suite terminates the loop
without executing the "else" clause’s suite. A "continue" statement
executed in the first suite skips the rest of the suite and continues
with the next item, or with the "else" clause if there is no next
item.
The for-loop makes assignments to the variables(s) in the target list.
This overwrites all previous assignments to those variables including
those made in the suite of the for-loop:
for i in range(10):
print(i)
i = 5 # this will not affect the for-loop
# because i will be overwritten with the next
# index in the range
Names in the target list are not deleted when the loop is finished,
but if the sequence is empty, they will not have been assigned to at
all by the loop. Hint: the built-in function "range()" returns an
iterator of integers suitable to emulate the effect of Pascal’s "for i
:= a to b do"; e.g., "list(range(3))" returns the list "[0, 1, 2]".
Note: There is a subtlety when the sequence is being modified by the
loop (this can only occur for mutable sequences, e.g. lists). An
internal counter is used to keep track of which item is used next,
and this is incremented on each iteration. When this counter has
reached the length of the sequence the loop terminates. This means
that if the suite deletes the current (or a previous) item from the
sequence, the next item will be skipped (since it gets the index of
the current item which has already been treated). Likewise, if the
suite inserts an item in the sequence before the current item, the
current item will be treated again the next time through the loop.
This can lead to nasty bugs that can be avoided by making a
temporary copy using a slice of the whole sequence, e.g.,
for x in a[:]:
if x < 0: a.remove(x)
Related help topics: break, continue, while
A part True
, False
et None
, les keywords ne sont pas des objets.
[42]:
type(and)
File "<ipython-input-42-5d36ea62b089>", line 1
type(and)
^
SyntaxError: invalid syntax
[43]:
print(type(True), type(False), type(None))
<class 'bool'> <class 'bool'> <class 'NoneType'>
Opérateurs principaux¶
Pas que pour les maths!
[44]:
3.4 + 4
[44]:
7.4
[45]:
3.4 - 4
[45]:
-0.6000000000000001
[46]:
3.4 * 4
[46]:
13.6
[47]:
3.4 / 4
[47]:
0.85
[48]:
11 // 4
[48]:
2
[49]:
(11).__floordiv__(4)
[49]:
2
[50]:
11 % 4
[50]:
3
[51]:
(11 // 4) * 4 + (11 % 4)
[51]:
11
[52]:
11 // 4 * 4 + 11 % 4 # Moins facile à lire
[52]:
11
[53]:
3.4 > 4
[53]:
False
[54]:
3.4 < 4
[54]:
True
[55]:
3.4 <= 4
[55]:
True
[56]:
3.4 >= 4
[56]:
False
[57]:
2 < 5 < 10
[57]:
True
[58]:
3 > 2 >= 10
[58]:
False
[59]:
3.4 == 4
[59]:
False
[60]:
3.4.__eq__(4)
[60]:
False
[61]:
3.4 != 4
[61]:
True
[62]:
4 == 4
[62]:
True
Les symboles opérateurs ne sont pas des objets.
[63]:
type(==)
File "<ipython-input-63-eab1840bb5e3>", line 1
type(==)
^
SyntaxError: invalid syntax
Nommer¶
Poser des post-its par-ci, par-là
Jusqu’à présent les objets qui ont été utilisés sont supprimés aussitôt générés par Python. On peut les garder à notre disposition en leur assignant un nom.
[64]:
height = 1.73
[65]:
height
[65]:
1.73
[66]:
type(height)
[66]:
float
[67]:
id(height)
[67]:
2556173077552
[68]:
id(height)
[68]:
2556173077552
[69]:
print(height, height, height)
1.73 1.73 1.73
Regardez bien les trois instructions ci-dessous, quelle est la valeur finale de ``x``?
[70]:
x = 2
y = x
y = 4
[71]:
print(x, y)
2 4
On aurait pu croire que x
valait 4
à la fin des trois instructions, mais non. Les noms en Python sont juste des références aux objets, on peut les comparer à des post-its.
[72]:
x = 2
# L'objet 2 est créé, un post-it avec inscrit *x* lui est aposé.
print("x =", x, " / id(x):", id(x))
y = x
# Un second post-it est aposé sur l'objet 2, il y est inscrit *y*.
print("x =", x, " / id(x):", id(x), " --- y =", y, " / id(y):", id(y))
# Aucun objet n'est créé lors de cette instruction.
y = 4
# L'objet 4 est créé, comme un nom ne peut référé qu'à un seul objet
# le post-it *y* qui était sur l'objet 2 est supprimé et un nouveau
# post-it *y* est aposé sur l'objet 4.
print("x =", x, " / id(x):", id(x), " --- y =", y, " / id(y):", id(y))
x = 2 / id(x): 140734432777056
x = 2 / id(x): 140734432777056 --- y = 2 / id(y): 140734432777056
x = 2 / id(x): 140734432777056 --- y = 4 / id(y): 140734432777120
Une fois que des références aux objets ont été créées, celles-ci sont rajoutées au namespace. On peut en voir le contenu avec la fonction dir
.
[73]:
print(dir())
['AND', 'In', 'Out', '_', '_10', '_11', '_12', '_13', '_14', '_15', '_16', '_17', '_18', '_19', '_20', '_21', '_22', '_23', '_24', '_29', '_30', '_44', '_45', '_46', '_47', '_48', '_49', '_50', '_51', '_52', '_53', '_54', '_55', '_56', '_57', '_58', '_59', '_60', '_61', '_62', '_65', '_66', '_67', '_68', '_8', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '_dh', '_i', '_i1', '_i10', '_i11', '_i12', '_i13', '_i14', '_i15', '_i16', '_i17', '_i18', '_i19', '_i2', '_i20', '_i21', '_i22', '_i23', '_i24', '_i25', '_i26', '_i27', '_i28', '_i29', '_i3', '_i30', '_i31', '_i32', '_i33', '_i34', '_i35', '_i36', '_i37', '_i38', '_i39', '_i4', '_i40', '_i41', '_i42', '_i43', '_i44', '_i45', '_i46', '_i47', '_i48', '_i49', '_i5', '_i50', '_i51', '_i52', '_i53', '_i54', '_i55', '_i56', '_i57', '_i58', '_i59', '_i6', '_i60', '_i61', '_i62', '_i63', '_i64', '_i65', '_i66', '_i67', '_i68', '_i69', '_i7', '_i70', '_i71', '_i72', '_i73', '_i8', '_i9', '_ih', '_ii', '_iii', '_oh', 'exit', 'get_ipython', 'height', 'i', 'quit', 'true', 'x', 'y']
Dans une console IPython ou dans un notebook, le namespace est occupé par beaucoup de références qui ne nous intéressent pas. Les commandes magiques %who
et whos
permettent de voir uniquement celles que nous avons créées nous-mêmes.
[74]:
%who
AND height i true x y
[75]:
%whos
Variable Type Data/Info
-----------------------------
AND int 3
height float 1.73
i int 3
true int 2
x int 2
y int 4
Ainsi, lorsqu’on l’on fait référence à un objet par un de ses noms, Python cherche dans le namespace la référence de cet objet et utilise la donnée qui lui est associée.
[76]:
height
[76]:
1.73
[77]:
poids
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-77-adb260374773> in <module>
----> 1 poids
NameError: name 'poids' is not defined
Comme poids
n’est pas une référence du namespace (on peut aussi dire que la variable poids
n’a pas encore été définie), l’interpréteur retourne une erreur.
On peut déréférencer un élément du namespace avec une instruction utilisant le keyword del
.
[78]:
# On enlève le post-it *height* de l'objet 1.73,
# celui-ci n'est plus accessible à la suite de ça.
del height
[79]:
%who
AND i true x y
[80]:
del height
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-80-f8c467be9fdb> in <module>
----> 1 del height
NameError: name 'height' is not defined
[81]:
height
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-81-5107fc8585e9> in <module>
----> 1 height
NameError: name 'height' is not defined
On peut déférencer tous les éléments du namespace avec la commande magique %reset -f
.
[82]:
%reset -f
[83]:
%whos
Interactive namespace is empty.
Importer¶
Batteries included
Nous avons pour l’instant vu que nous avions à disposition des fonctions built-in, des keywords et des opérateurs. Un mécanisme permet d”importer des objets supplémentaires dans le code, comme des fonctions.
[84]:
import math
math
est un module de la standard library. La standard library est une grande collection de modules livrés avec Python, c’est pourquoi on associe souvent l’expression Batteries Included au langage.
[85]:
%whos
Variable Type Data/Info
------------------------------
math module <module 'math' (built-in)>
math
est le module dédié aux opérations mathématiques. En exécutant import math
, on a rajouté au namespace le nom math
, celui-ci faisant référence à un objet de type module
.
[86]:
type(math)
[86]:
module
[87]:
id(math)
[87]:
2556140748616
[88]:
print(dir(math))
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']
On peut voir que le module contient les attributs sqrt
(racine carrée) et pi
. On accède à ces attributs en rajoutant un .
après math
.
[89]:
math.sqrt
[89]:
<function math.sqrt(x, /)>
[90]:
math.pi
[90]:
3.141592653589793
[91]:
type(math.sqrt)
[91]:
builtin_function_or_method
[92]:
type(math.pi)
[92]:
float
[93]:
math.sqrt(math.pi)
[93]:
1.7724538509055159
On peut importer un module et les objets qu’il contient de plusieurs manières:
from math import *
importe tout le contenu du modulemath
. Cette méthode n’est pas recommandée car elle vient rajouter beaucoup de références au namespace.
[94]:
%reset -f
[95]:
from math import *
[96]:
%who
acos acosh asin asinh atan atan2 atanh ceil copysign
cos cosh degrees e erf erfc exp expm1 fabs
factorial floor fmod frexp fsum gamma gcd hypot inf
isclose isfinite isinf isnan ldexp lgamma log log10 log1p
log2 modf nan pi pow radians remainder sin sinh
sqrt tan tanh tau trunc
[97]:
sqrt(pi)
[97]:
1.7724538509055159
import math as m
importe le modulemath
avec pour référencem
et nonmath
. Cette méthode peut être utilisé lorsque le nom du module est très long, ou si ce nom va être répété à de très nombreuses reprises. Trois examples classiques étantimport numpy as np
,import pandas as pd
,import matplotlib.pyplot as plt
.
[98]:
%reset -f
[99]:
import math as m
[100]:
math
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-100-a984292e3e46> in <module>
----> 1 math
NameError: name 'math' is not defined
[101]:
m
[101]:
<module 'math' (built-in)>
[102]:
%whos
Variable Type Data/Info
------------------------------
m module <module 'math' (built-in)>
[103]:
m.sqrt(m.pi)
[103]:
1.7724538509055159
from math import sqrt, pi
importe seulement les objetssqrt
etpi
.
[104]:
%reset -f
[105]:
from math import sqrt, pi
[106]:
%whos
Variable Type Data/Info
--------------------------------------------------
pi float 3.141592653589793
sqrt builtin_function_or_method <built-in function sqrt>
[107]:
sqrt(pi)
[107]:
1.7724538509055159
from math import sqrt as racinecarree
importe directement la fonctionsqrt
du modulemath
sous la référenceracinecarree
.
[108]:
%reset -f
[109]:
from math import sqrt as racinecarree
[110]:
%whos
Variable Type Data/Info
------------------------------------------------------
racinecarree builtin_function_or_method <built-in function sqrt>
[111]:
racinecarree(4)
[111]:
2.0
[112]:
sqrt(4)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-112-317e033d29d5> in <module>
----> 1 sqrt(4)
NameError: name 'sqrt' is not defined
Des méthodes d’import montrées ci-dessus, c’est la première, ``import math``, qui doit être préférée. Elle permet en effet de savoir depuis quel module proviennent les fonctions qu’on utilise.
[113]:
%reset -f
Modules et packages¶
Batteries included in batteries
Les modules sont matérialisés par des fichiers textes dont l’extension est ``.py``. Lorsqu’on importe un module, le code qu’il contient est exécuté. Les objets qui sont créés dans ce module sont donc accessibles.
[114]:
%%writefile dummymodule.py
"""Un tout petit module."""
print("hello world")
x = 2
Writing dummymodule.py
[115]:
import dummymodule
hello world
[116]:
dummymodule.x
[116]:
2
!!! Attention !!! Importer un module déjà importé ne réexécute pas son code.
[117]:
import dummymodule
[118]:
dummymodule.x
[118]:
2
Si on modifie le code d’un module qu’on utilise pendant qu’on écrit un script, il faut penser à forcer son import. On peut utiliser le module importlib
pour importer à chaque fois le module modifié.
[119]:
import importlib
importlib.reload(dummymodule)
hello world
[119]:
<module 'dummymodule' from 'D:\\GoogleDrive\\Code\\IntroPython\\source\\dummymodule.py'>
Les modules sont aussi des objets (tout est un objet).
[120]:
type(dummymodule)
[120]:
module
[121]:
dir(dummymodule)
[121]:
['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'x']
[122]:
dummymodule.__name__
[122]:
'dummymodule'
[123]:
dummymodule.__file__
[123]:
'D:\\GoogleDrive\\Code\\IntroPython\\source\\dummymodule.py'
[124]:
dummymodule.__doc__
[124]:
'Un tout petit module.'
[125]:
help(dummymodule)
Help on module dummymodule:
NAME
dummymodule - Un tout petit module.
DATA
x = 2
FILE
d:\googledrive\code\intropython\source\dummymodule.py
[126]:
dummymodule?
Lorsqu’il tente d’importer quelque chose, Python cherche dans une liste de dossier qu’on retrouve dans la référence sys.path
. Cette liste contient le dossier courant.
[127]:
import sys
sys.path
[127]:
['D:\\GoogleDrive\\Code\\IntroPython\\source',
'C:\\Users\\maxim\\Miniconda3\\python37.zip',
'C:\\Users\\maxim\\Miniconda3\\DLLs',
'C:\\Users\\maxim\\Miniconda3\\lib',
'C:\\Users\\maxim\\Miniconda3',
'',
'C:\\Users\\maxim\\AppData\\Roaming\\Python\\Python37\\site-packages',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages\\win32',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages\\win32\\lib',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages\\Pythonwin',
'C:\\Users\\maxim\\Miniconda3\\lib\\site-packages\\IPython\\extensions',
'C:\\Users\\maxim\\.ipython']
Les modules de la standard library écrits en Python (il y a aussi des modules écrits en C
) sont visibles sur GitHub. Dans ce dossier on trouve aussi des sous-dossiers qui contiennent des fichiers .py
et un fichier __init__.py
. Ces dossiers-là sont des packages qu’on peut aussi importer.
[128]:
import email
[129]:
type(email)
[129]:
module
[130]:
dir(email)
[130]:
['__all__',
'__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__path__',
'__spec__',
'_encoded_words',
'_parseaddr',
'_policybase',
'base64mime',
'charset',
'encoders',
'errors',
'feedparser',
'header',
'iterators',
'message',
'message_from_binary_file',
'message_from_bytes',
'message_from_file',
'message_from_string',
'parser',
'quoprimime',
'utils']
[131]:
email.__package__
[131]:
'email'
[132]:
email.__path__
[132]:
['C:\\Users\\maxim\\Miniconda3\\lib\\email']
[133]:
email.__file__
[133]:
'C:\\Users\\maxim\\Miniconda3\\lib\\email\\__init__.py'
[134]:
email.__doc__
[134]:
'A package for parsing, handling, and generating email messages.'
[135]:
help(email)
Help on package email:
NAME
email - A package for parsing, handling, and generating email messages.
PACKAGE CONTENTS
_encoded_words
_header_value_parser
_parseaddr
_policybase
base64mime
charset
contentmanager
encoders
errors
feedparser
generator
header
headerregistry
iterators
message
mime (package)
parser
policy
quoprimime
utils
FUNCTIONS
message_from_binary_file(fp, *args, **kws)
Read a binary file and parse its contents into a Message object model.
Optional _class and strict are passed to the Parser constructor.
message_from_bytes(s, *args, **kws)
Parse a bytes string into a Message object model.
Optional _class and strict are passed to the Parser constructor.
message_from_file(fp, *args, **kws)
Read a file and parse its contents into a Message object model.
Optional _class and strict are passed to the Parser constructor.
message_from_string(s, *args, **kws)
Parse a string into a Message object model.
Optional _class and strict are passed to the Parser constructor.
DATA
__all__ = ['base64mime', 'charset', 'encoders', 'errors', 'feedparser'...
FILE
c:\users\maxim\miniconda3\lib\email\__init__.py
[136]:
import email.message
[137]:
type(email.message)
[137]:
module
[138]:
email.message.__file__
[138]:
'C:\\Users\\maxim\\Miniconda3\\lib\\email\\message.py'
[139]:
email.message.__package__
[139]:
'email'
[140]:
import email.mime
[141]:
email.mime.__package__
[141]:
'email.mime'
[142]:
dir(email.mime)
[142]:
['__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__path__',
'__spec__']
[143]:
help(email.mime)
Help on package email.mime in email:
NAME
email.mime
PACKAGE CONTENTS
application
audio
base
image
message
multipart
nonmultipart
text
FILE
c:\users\maxim\miniconda3\lib\email\mime\__init__.py
[144]:
from email.mime import audio
[145]:
type(audio)
[145]:
module
[146]:
audio.__file__
[146]:
'C:\\Users\\maxim\\Miniconda3\\lib\\email\\mime\\audio.py'
Voici une très courte liste de modules utiles.
[147]:
import sys
sys.executable
[147]:
'C:\\Users\\maxim\\Miniconda3\\python.exe'
[148]:
sys.version
[148]:
'3.7.3 (default, Apr 24 2019, 15:29:51) [MSC v.1915 64 bit (AMD64)]'
[149]:
import os
os.getcwd()
[149]:
'D:\\GoogleDrive\\Code\\IntroPython\\source'
[150]:
import time
t1 = time.time()
time.sleep(2)
t2 = time.time()
print(t2 - t1)
2.0006837844848633
[151]:
import datetime
guess = datetime.datetime(2019, 9, 10, 10, 5, 14)
now = datetime.datetime.now()
diff = now - guess
print("Je me suis trompé de", diff.seconds, "secondes.")
Je me suis trompé de 48127 secondes.
[152]:
import random
random.random()
[152]:
0.1681579307440323
[153]:
random.randint(10, 20)
[153]:
15
[154]:
random.choice(["a", "b", "c"])
[154]:
'a'
[155]:
random.sample([1, 2, 3, 4], 3)
[155]:
[1, 3, 2]
[156]:
random.gauss(10, 3)
[156]:
7.155043539141076
[157]:
import pathlib
current_directory = pathlib.Path.cwd()
current_directory
[157]:
WindowsPath('D:/GoogleDrive/Code/IntroPython/source')
[158]:
list(current_directory.glob("*"))
[158]:
[WindowsPath('D:/GoogleDrive/Code/IntroPython/source/.ipynb_checkpoints'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/conf.py'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/dummymodule.py'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/environnement.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/exercices.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/images'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/index.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/installation.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/introduction.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/intropython.ipynb'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/intropython.py'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/intropython_extended.ipynb'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/python.py'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/python.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/rstfiles.txt'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/scientific_libs.ipynb'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/scientific_libs.py'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/text.txt'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/TODOs.txt'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/utilisation.rst'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/_build'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/_static'),
WindowsPath('D:/GoogleDrive/Code/IntroPython/source/__pycache__')]
[159]:
import collections
collections.Counter("aaabbbbbbbbbbbbbcccddddee").most_common()
[159]:
[('b', 13), ('d', 4), ('a', 3), ('c', 3), ('e', 2)]
[160]:
%reset -f
!del /f dummymodule.py
Conditions if
et booléens¶
Avec des si
Le bloc sous une instruction if
est exécuté entièrement si la condition (expression) qui suit le if
est évaluée comme vraie.
[161]:
if True:
print("Executed")
Executed
[162]:
if False:
print("Not executed")
[163]:
diameter = 2
if diameter > 1:
print(diameter)
2
On peut rajouter d’autres conditions avec les keywords elif
et else
.
[164]:
age = 150
if age < 18:
print("Sorry you're not allowed to drive.")
elif 18 <= age < 80:
print("Let's drive!")
elif 80 <= age < 122:
print("Please please please strop driving please!")
else:
print("Is that you Jeanne?")
Is that you Jeanne?
On peut assigner le résultat d’un comparaison à une référence et on peut combiner plusieurs comparaisons avec les keywords and
et or
?
[165]:
age1 = 10
age2 = 54
condition1 = 18 <= age1 < 80
condition2 = 18 <= age2 < 80
condition1 and condition2
[165]:
False
[166]:
True and True
[166]:
True
[167]:
True or True
[167]:
True
[168]:
False and False
[168]:
False
[169]:
False or False
[169]:
False
[170]:
False and True
[170]:
False
[171]:
True and False
[171]:
False
[172]:
False or True
[172]:
True
[173]:
True or False
[173]:
True
[174]:
print(type(condition1), type(condition2))
<class 'bool'> <class 'bool'>
True
et False
sont des booléens. Dans les versions précédentes, 1
était utilisé pour représenter ce qui était vrai, et 0
pour ce qui était faux. On hérite de ce comportement aujourd’hui.
[175]:
True + 3
[175]:
4
[176]:
False + False
[176]:
0
Tous les objets de Python sont en fait soit vrai ou faux. On peut vérifier s’ils sont truthy ou falsy avec la fonction bool
.
[177]:
bool(3.14)
[177]:
True
[178]:
if 3.14:
print("3.14")
3.14
[179]:
bool(0)
[179]:
False
[180]:
if 0:
print("0")
[181]:
bool(1)
[181]:
True
[182]:
if 1:
print("1")
1
[183]:
bool(print)
[183]:
True
[184]:
if print:
print("Never do that please!")
Never do that please!
[185]:
bool(None)
[185]:
False
[186]:
if not None:
print("Pas vide")
Pas vide
[187]:
%reset -f
Texte¶
Du blabla
Le texte est représenté par des objets de type string
. On encadre le texte des symboles "
ou '
ou """
ou '''
pour créer un objet string
.
[188]:
"Bob"
[188]:
'Bob'
[189]:
'Bob'
[189]:
'Bob'
[190]:
"""Bob"""
[190]:
'Bob'
[191]:
'''Bob'''
[191]:
'Bob'
[192]:
type("Bob")
[192]:
str
[193]:
print("'")
'
[194]:
print('"')
"
On peut les additionner et les démultiplitier.
[195]:
"Bob" + "Bill"
[195]:
'BobBill'
[196]:
"Bob" * 3
[196]:
'BobBobBob'
[197]:
3 * "Bob"
[197]:
'BobBobBob'
[198]:
"Bob" - "Bill"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-198-a8c1ff178148> in <module>
----> 1 "Bob" - "Bill"
TypeError: unsupported operand type(s) for -: 'str' and 'str'
[199]:
"Bob" / 3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-199-f7f9e058c01b> in <module>
----> 1 "Bob" / 3
TypeError: unsupported operand type(s) for /: 'str' and 'int'
Il est possible de créer une string
vide.
[200]:
""
[200]:
''
[201]:
"Bob" + "" + "Bill"
[201]:
'BobBill'
Le caractère backslash ```` déclenche un comportement spécial: l’interpréteur regarde le caractère qui suit et voit si la combinaison des deux caractères forme ou non un caractère spécial, si oui, celui-ci est représenté.
[202]:
# \n --> Retour à la ligne
# \t --> Tabulation
print("Bob\tBill\nBill\tBob")
Bob Bill
Bill Bob
Le caractère \
a donc un effet particulier. Cela rend plus difficile la représentation d’un chemin vers un dossier ou un fichier sous Windows. On annule l’effet particulier de \
en rajoutant le préfixe r
(pour raw) devant le premier guillemet de la string
.
[203]:
print(r"user\me\names.txt") # OK
user\me\names.txt
[204]:
print("user\me\names.txt") # not OK
user\me
ames.txt
On peut créer une string
sur plusieurs lignes: * en insérant des \n
dans le texte pour représenter les sauts de ligne * en créant l’objet string
directement avec trois guillemets """
La deuxième option rajoute automatiquement des \n
à chaque saut de ligne.
[205]:
print("bla bla\nbla bla\nbla bla")
bla bla
bla bla
bla bla
[206]:
"""bla bla
bla bla
bla bla"""
[206]:
'bla bla\nbla bla\nbla bla'
[207]:
print("""bla bla
bla bla
bla bla""")
bla bla
bla bla
bla bla
Si on doit écrire un long texte dans le code, on peut couper la définition du texte avec un \
ajouté à la fin de chaque ligne.
[208]:
"Ceci est un long texte. Cela peut être un message \
d'erreur. Lorsqu'un si long texte est défini, il est utile de \
le couper avec un backslash à la fin de chaque ligne \
pour éviter d'avoir une instruction qui aille \
à l'encontre de PEP8."
[208]:
"Ceci est un long texte. Cela peut être un message d'erreur. Lorsqu'un si long texte est défini, il est utile de le couper avec un backslash à la fin de chaque ligne pour éviter d'avoir une instruction qui aille à l'encontre de PEP8."
On peut vérifier si deux strings
sont égales avec l”opérateur égalité ==
. Pour vérifier si une partie d’une string
se trouve dans une autre, on utilise le keyword in
.
[209]:
"Bob" == "Bill"
[209]:
False
[210]:
"Bob".__eq__("Bill")
[210]:
False
[211]:
"Bob" != "Bill"
[211]:
True
[212]:
"Bob" == 'Bob' == """Bob"""
[212]:
True
[213]:
"Bill" in "Bob and Bill"
[213]:
True
[214]:
"Bob and Bill".__contains__("Bill")
[214]:
True
[215]:
"John" in "Bob and Bill"
[215]:
False
[216]:
"Bill" not in "Bob and Bill"
[216]:
False
Les strings
possèdent beaucoup de méthodes pour les analyser et transformer. Lorsqu’une méthode a pour objectif de transformer une string
elle retourne un nouvel objet string
qui est version transformée de la string
initiale. Elles ne le modifient pas directement l’objet initial. En effet, les ``strings`` sont des objets immutables.
[217]:
name = "bob"
print(dir(name))
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
[218]:
name.upper()
[218]:
'BOB'
[219]:
name
[219]:
'bob'
[220]:
name.title()
[220]:
'Bob'
[221]:
name.replace("o", "a")
[221]:
'bab'
[222]:
txt = " espaces avant et espaces après "
txt.strip()
[222]:
'espaces avant et espaces après'
strip
enlève par défaut les espaces au début et à la fin de la string
. Les espaces comprennent les sauts de ligne et les tabulations.
[223]:
import string
string.whitespace
[223]:
' \t\n\r\x0b\x0c'
[224]:
txt.rstrip()
[224]:
' espaces avant et espaces après'
[225]:
txt.lstrip()
[225]:
'espaces avant et espaces après '
[226]:
sentence = "quel beau temps"
words = sentence.split()
words
[226]:
['quel', 'beau', 'temps']
[227]:
help(str.split)
Help on method_descriptor:
split(self, /, sep=None, maxsplit=-1)
Return a list of the words in the string, using sep as the delimiter string.
sep
The delimiter according which to split the string.
None (the default value) means split according to any whitespace,
and discard empty strings from the result.
maxsplit
Maximum number of splits to do.
-1 (the default value) means no limit.
[228]:
sentence.split("l")
[228]:
['que', ' beau temps']
[229]:
" ".join(words)
[229]:
'quel beau temps'
[230]:
help(str.join)
Help on method_descriptor:
join(self, iterable, /)
Concatenate any number of strings.
The string whose method is called is inserted in between each given string.
The result is returned as a new string.
Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
On peut enchaîner les méthodes tant qu’on fait attention à ce que la méthode qui suit accepte est bien une méthode de l’objet retourné par la méthode qui précède. Dans l’exemple ci-dessous, la méthode strip
est bien un méthode de l’objet sentence
qui est de type string
, elle retourne un objet string
qui possède bien une méthode replace
, celle-ci retourne un nouvel objet string
qui possède bien une méthode split
, celle-ci retourne finalement une liste.
[231]:
modified_sentence = sentence.strip("qs").replace("e", "i").split()
modified_sentence
[231]:
['uil', 'biau', 'timp']
[232]:
%reset -f
F-String¶
Du joli blabla
Les f-strings
sont des objets de type string
qu’on peut formater. Il faut rajouter un préfixe f
devant le premier guillemet, puis ensuite, à l’intérieur, il faut entourer de symboles {}
les expressions qu’on souhaite formater afin qu’elles soient directement évaluées par Python, qui construit notre objet string
.
[233]:
age = 28
f"J'ai {age} ans"
[233]:
"J'ai 28 ans"
[234]:
type(age)
[234]:
int
[235]:
"J'ai " + age + " ans"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-235-1c648e4e0b43> in <module>
----> 1 "J'ai " + age + " ans"
TypeError: can only concatenate str (not "int") to str
[236]:
age = 28
limite_d_age = 25
if age > limite_d_age:
print(
f"Tu as {age} ans, la limite d'âge étant à {limite_d_age}, tu"
f" as donc {age - limite_d_age} ans de plus que la limite. Tout"
f" va maintenant être plus cher pour toi!"
)
Tu as 28 ans, la limite d'âge étant à 25, tu as donc 3 ans de plus que la limite. Tout va maintenant être plus cher pour toi!
Cette manière de formater les strings
est assez récente (Python 3.6) et est vraiment pratique et facile à utiliser. Il existe deux autres manières de formater des strings
qu’on retrouve encore souvent sur internet.
[237]:
"J'ai %s ans" % (age)
[237]:
"J'ai 28 ans"
[238]:
"J'ai {} ans".format(age)
[238]:
"J'ai 28 ans"
[239]:
%reset -f
Convertir float
, int
et str
¶
Nombre <—> Texte <—> Nombre
Les fonctions int
, float
et str
tentent de convertir les objets qu’on leur donne. Elles retournent une erreur ``ValueError`` si elles échouent, et l’objet converti si elles réussissent.
[240]:
int(3.7)
[240]:
3
[241]:
float(4)
[241]:
4.0
[242]:
str(3)
[242]:
'3'
[243]:
int("Bob")
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-243-b70fd5693779> in <module>
----> 1 int("Bob")
ValueError: invalid literal for int() with base 10: 'Bob'
[244]:
x = "3"
print(type(x))
x = int(x)
print(type(x))
<class 'str'>
<class 'int'>
[245]:
"Bob" + 3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-245-45c29619c55a> in <module>
----> 1 "Bob" + 3
TypeError: can only concatenate str (not "int") to str
[246]:
"Bob " + str(3)
[246]:
'Bob 3'
[247]:
# print convertit automatiquement les objets en string.
print("Bob", 3)
Bob 3
[248]:
%reset -f
Indexing et Slicing¶
A piece of cake
Certains objets comme les objets de type string
sont des séquences. On peut obtenir n’importe quel item de l’objet grâce à son index numérique (indexing), on peut aussi obtenir directement plusieurs items (slicing). Le premier item d’une séquence a toujours l’index 0 en Python (0-based).
[249]:
txt = "Bill"
[250]:
txt[0]
[250]:
'B'
[251]:
txt.__getitem__(0)
[251]:
'B'
[252]:
txt[1]
[252]:
'i'
[253]:
txt[2]
[253]:
'l'
[254]:
txt[3]
[254]:
'l'
[255]:
txt[4]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-255-e9d47c8188c4> in <module>
----> 1 txt[4]
IndexError: string index out of range
[256]:
txt[-1]
[256]:
'l'
[257]:
txt[-2]
[257]:
'l'
[258]:
txt[-3]
[258]:
'i'
[259]:
txt[-4]
[259]:
'B'
[260]:
txt[-5]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-260-e5b69442cb73> in <module>
----> 1 txt[-5]
IndexError: string index out of range
On peut connaître le nombre d’items d’une séquence avec la fonction ``len``.
[261]:
help(len)
Help on built-in function len in module builtins:
len(obj, /)
Return the number of items in a container.
Lorsqu’on passe un objet string
à la fonction len
, on obtient le nombre de caractères de l’objet.
[262]:
len(txt)
[262]:
4
[263]:
txt.__len__()
[263]:
4
[264]:
len("")
[264]:
0
[265]:
len(" ")
[265]:
1
Attention au comportement spécial du backslash \
.
[266]:
len("\t")
[266]:
1
[267]:
len("\n")
[267]:
1
[268]:
len("\t")
[268]:
1
[269]:
len(r"\n")
[269]:
2
[270]:
len("Bob and Bill")
[270]:
12
[271]:
len(3.4)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-271-a9f429718b04> in <module>
----> 1 len(3.4)
TypeError: object of type 'float' has no len()
On ne peut pas modifier un item d’une string
à partir de son index. En fait, il est simplement impossible de modifier un objet string
, il faut forcément en créer un nouveau. Un objet qui ne peut pas être modifié est dit immutable.
[272]:
txt[0] = "A"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-272-dc27665d30a4> in <module>
----> 1 txt[0] = "A"
TypeError: 'str' object does not support item assignment
Pour obtenir plusieurs caractères d’un objet string
, on utilise la syntaxe [start:stop:step]
où start
et stop
sont des index, et step
l’écart entre deux caractères. Par défaut, start
est le premier index ([0]
), stop
est le dernier index, et step
est égal à 1. L’élément à l’index ``start`` est inclus dans l’objet string
retourné, l’élément à l’index ``stop`` est exclus.
[273]:
numbers = "013456789"
numbers[3::]
[273]:
'456789'
[274]:
numbers[3:]
[274]:
'456789'
[275]:
numbers[-1:]
[275]:
'9'
[276]:
numbers[50::]
[276]:
''
[277]:
numbers[:8]
[277]:
'01345678'
[278]:
numbers[3:8]
[278]:
'45678'
[279]:
numbers[3:4]
[279]:
'4'
[280]:
numbers[3:3]
[280]:
''
[281]:
numbers[3:50]
[281]:
'456789'
[282]:
numbers[::1]
[282]:
'013456789'
[283]:
numbers[::]
[283]:
'013456789'
[284]:
numbers[::2]
[284]:
'03579'
[285]:
numbers[::100]
[285]:
'0'
[286]:
numbers[::-1]
[286]:
'987654310'
[287]:
numbers[::-2]
[287]:
'97530'
[288]:
numbers[3:8:3]
[288]:
'47'
[289]:
numbers[3:8:-1]
[289]:
''
[290]:
numbers[8:3]
[290]:
''
[291]:
numbers[8:3:-1]
[291]:
'98765'
[292]:
numbers[:3:-1]
[292]:
'98765'
[293]:
numbers[-3:-8:-1]
[293]:
'76543'
[294]:
numbers.__getitem__(slice(-3, -8, -1))
[294]:
'76543'
[295]:
%reset -f
Listes et Tuples¶
Maîtres de l’ordre
Les objets de type list
et tuple
sont des containers. Ces deux types d’objet sont, comme les objets de type string
, des séquences. Ils peuvent contenir n’importe quel type d’objet.
Les lists
et les tuples
sont très pratiques et utilisés partout dans le langage Python.
[296]:
shopping = ["banana", "beer", "chocolate", "tomato", "salad"]
[297]:
type(shopping)
[297]:
list
[298]:
print(dir(list))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
L’objet retourné par la fonction dir
est une list
.
[299]:
type(dir(list))
[299]:
list
On peut accéder à chaque item d’une list
par son index numérique.
[300]:
shopping[0]
[300]:
'banana'
[301]:
shopping.__getitem__(0)
[301]:
'banana'
L’index -1
est un raccourci pour indiquer le dernier item. -2
indique l’avant-dernier item, etc.
[302]:
shopping[-1]
[302]:
'salad'
[303]:
shopping[1:4:2]
[303]:
['beer', 'tomato']
La syntaxe ::-1
permet d’inverser la list
originale.
[304]:
shopping[::-1]
[304]:
['salad', 'tomato', 'chocolate', 'beer', 'banana']
[305]:
len(shopping)
[305]:
5
[306]:
len([])
[306]:
0
Alors qu’on définit une list
en entourant ses items de []
, on définit un tuple en entourant ses items de ()
.
[307]:
shopping = ("banana", "beer", "chocolate", "tomato", "salad")
[308]:
type(shopping)
[308]:
tuple
On accède aux items d’un tuple
comme on accède aux items d’une list
.
[309]:
shopping[0]
[309]:
'banana'
[310]:
shopping[-1]
[310]:
'salad'
[311]:
shopping[1:4:2]
[311]:
('beer', 'tomato')
[312]:
shopping[::-1]
[312]:
('salad', 'tomato', 'chocolate', 'beer', 'banana')
[313]:
shopping(mess)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-313-b1aa1c29f1d5> in <module>
----> 1 shopping(mess)
NameError: name 'mess' is not defined
[314]:
len(())
[314]:
0
Une list
ou dans un tuple
peuvent faire référence à n’importe quel type d’objet.
[315]:
import math
big_mess = [2, 2.33, "string", print, math, ["Bob", "Bill"]]
big_mess
[315]:
[2,
2.33,
'string',
<function print>,
<module 'math' (built-in)>,
['Bob', 'Bill']]
[316]:
import math
big_mess = (2, 2.33, "string", print, math, ["Bob", "Bill"])
big_mess
[316]:
(2,
2.33,
'string',
<function print>,
<module 'math' (built-in)>,
['Bob', 'Bill'])
Les ``lists`` et les ``tuples`` ne contiennent aucun objet, ils contiennent en fait des références à d’autres objets.
[317]:
pi = 3.14
[318]:
l = [pi]
[319]:
print(f"""\
{'ID de l:':15}{id(l)}
{'ID de pi:':15}{id(pi)}
{'ID de l[0]:':15}{id(l[0])}
""")
ID de l: 2556174822024
ID de pi: 2556174976056
ID de l[0]: 2556174976056
Comme les strings
, les tuples
sont des immutables.
[320]:
ages[0] = 55
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-320-3794d71e2f53> in <module>
----> 1 ages[0] = 55
NameError: name 'ages' is not defined
Au contraire, les lists
sont des mutables. Il est donc autorisé de changer les éléments qu’elles contiennent, d’en supprimer ou d’en rajouter.
[321]:
shopping = ["banana", "beer", "chocolate", "tomato", "salad"]
print(id(shopping), shopping)
2556174979400 ['banana', 'beer', 'chocolate', 'tomato', 'salad']
[322]:
shopping[0] = "apple"
print(id(shopping), shopping)
2556174979400 ['apple', 'beer', 'chocolate', 'tomato', 'salad']
[323]:
shopping.append("cucumber")
print(id(shopping), shopping)
2556174979400 ['apple', 'beer', 'chocolate', 'tomato', 'salad', 'cucumber']
[324]:
shopping.append("pasta")
print(id(shopping), shopping)
2556174979400 ['apple', 'beer', 'chocolate', 'tomato', 'salad', 'cucumber', 'pasta']
[325]:
del shopping[-1]
print(id(shopping), shopping)
2556174979400 ['apple', 'beer', 'chocolate', 'tomato', 'salad', 'cucumber']
[326]:
del shopping[-2:]
print(id(shopping), shopping)
2556174979400 ['apple', 'beer', 'chocolate', 'tomato']
[327]:
shopping.extend(["egg", "bread", "jam"])
print(id(shopping), shopping)
2556174979400 ['apple', 'beer', 'chocolate', 'tomato', 'egg', 'bread', 'jam']
[328]:
shopping.insert(0, "salt")
print(id(shopping), shopping)
2556174979400 ['salt', 'apple', 'beer', 'chocolate', 'tomato', 'egg', 'bread', 'jam']
Les lists
et les tuples
supportent les opérateurs in
, not in
, ==
, !=
, +
et *
.
[329]:
"egg" in shopping
[329]:
True
[330]:
"rice" not in shopping
[330]:
True
[331]:
shopping == ["egg", "chocolate"]
[331]:
False
[332]:
shopping != ["egg", "chocolate"]
[332]:
True
[333]:
["egg"] == ("egg")
[333]:
False
[334]:
["egg", "chocolate"][0] == ("egg", "chocolate")[0]
[334]:
True
[335]:
["Bob"] == ["Bill"]
[335]:
False
[336]:
[[["Bob"]]]
[336]:
[[['Bob']]]
[337]:
[[["Bob"]]][0][0][0]
[337]:
'Bob'
[338]:
[[["Bob"]]] == [[["Bill"]]]
[338]:
False
[339]:
["Bob"] + ["Bill"]
[339]:
['Bob', 'Bill']
[340]:
("Bob",) + ("Bill",)
[340]:
('Bob', 'Bill')
[341]:
["Bob", "Bill"] * 3
[341]:
['Bob', 'Bill', 'Bob', 'Bill', 'Bob', 'Bill']
[342]:
("Bob", "Bill") * 3
[342]:
('Bob', 'Bill', 'Bob', 'Bill', 'Bob', 'Bill')
[343]:
[] * 3
[343]:
[]
[344]:
[[]] * 3
[344]:
[[], [], []]
On peut transformer les lists
en tuples
et vice versa avec les fonctions tuple
et list
.
[345]:
l = ["Bob", "Bill"]
t = tuple(l)
print(type(t), t)
<class 'tuple'> ('Bob', 'Bill')
[346]:
t = ["Bob", "Bill"]
l = list(t)
print(type(l), l)
<class 'list'> ['Bob', 'Bill']
Les méthodes count
et index
peuvent être utiles. Elles sont communes aux lists
et tuples
.
[347]:
[1, 2, 3, 3, 3, 4, 5].count(3)
[347]:
3
[348]:
[1, 2, 3, 3, 3, 4, 5].count(6)
[348]:
0
[349]:
[1, 2, 3, 3, 3, 4, 5].index(4)
[349]:
5
[350]:
[1, 2, 3, 3, 3, 4, 5].index(3)
[350]:
2
[351]:
[1, 2, 3, 3, 3, 4, 5].index(6)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-351-4c30a0c42992> in <module>
----> 1 [1, 2, 3, 3, 3, 4, 5].index(6)
ValueError: 6 is not in list
La fonction sorted
permet de trier une list
ou un tuple
. Elle retourne un nouvel objet qui est une list
.
[352]:
sorted([3, 1, 2])
[352]:
[1, 2, 3]
Par défaut les items sont retournés dans l’ordre ascendant, on inverser l’ordre avec le paramètre reverse
.
[353]:
sorted((3, 1, 2), reverse=True)
[353]:
[3, 2, 1]
On peut trier des strings
mais il faut faire attention à aux différences entre minuscules, capitales et nombres.
[354]:
sorted(["Donald", "Bob", "Bill"])
[354]:
['Bill', 'Bob', 'Donald']
[355]:
"D" > "B"
[355]:
True
[356]:
"D" > "d"
[356]:
False
[357]:
"z" > "A"
[357]:
True
[358]:
"Z" > "0"
[358]:
True
Le paramètre key
accepte comme argument une fonction, cette fonction est appliquée à chaque item de l’objet initial et le tri est effectué sur la base des résultats de la fonction. Dans l’exemple ci-dessous, les strings
que contient le tuple
sont ordonnées en fonction de leur longueur grâce à la built-in fonction len
.
[359]:
sorted(("Donald", "Bob", "Bill"), key=len)
[359]:
['Bob', 'Bill', 'Donald']
[360]:
sorted(("Donald", "Bob", "Bill"), key=len, reverse=True)
[360]:
['Donald', 'Bill', 'Bob']
[361]:
sorted?
Attention, lorsqu’on référence une list
existante l1 avec un nouveam nom l2, les modifications de l2 modifient l1. Comme déjà évoqué, les noms en Python ne sont que des références à des objets. Dans le cas ci-dessous, la liste [1, 2, 3]
est un objet unique auquel on peut accéder par les deux références/noms l1
et l2
.
[362]:
l1 = [1, 2, 3]
l2 = l1
l2[0] = 99
print(l1, l2)
[99, 2, 3] [99, 2, 3]
[363]:
l1[1] = 999
print(l1, l2)
[99, 999, 3] [99, 999, 3]
[364]:
id(l1) != id(l2)
[364]:
False
Copier une list
est une action qu’on va vouloir faire de temps en temps, il existe deux méthodes. La première effectue une shallow copy
de la list
source, c’est-à-dire, seuls les références aux objets que contient la list
sont copiées.
[365]:
l1 = [1, 2, 3]
l2 = l1[:] # ou l1.copy()
l2[0] = 999
print(l1, l2)
[1, 2, 3] [999, 2, 3]
[366]:
id(l1) != id(l2)
[366]:
True
Mais si la list
fait référence à des containers, les références aux objets qu’ils contiennent ne sont pas copiées.
[367]:
# Première méthode
l1 = [[1], 2, 3]
l2 = l1
l2[0][0] = 999
print(l1, l2)
[[999], 2, 3] [[999], 2, 3]
[368]:
# Deuxième méthode
l1 = [[1], 2, 3]
l2 = l1[:] # ou l1.copy()
l2[0][0] = 999
print(l1, l2)
[[999], 2, 3] [[999], 2, 3]
[369]:
id(l1) != id(l2)
[369]:
True
[370]:
id(l1[0]) != id(l2[0])
[370]:
False
On peut effectuer une deep copy pour palier au problème précédent. Cette méthode crée autant d’objet que contient la list
source.
[371]:
import copy
l1 = [[1], 2, 3]
l2 = copy.deepcopy(l1)
l2[0][0] = 999
print(l1, l2)
[[1], 2, 3] [[999], 2, 3]
[372]:
id(l1[0]) != id(l2[0])
[372]:
True
Même si les objets de type list
et tuple
sont similaires, en général on ne les utilise pas de la même manière. Les lists
vont plutôt contenir des objets du même type (exemple: liste de courses). Les tuples
vont contenir une suite d’objets dont on est sûr à l’avance qu’elle n’aura pas besoin d’être modifiée (exemple: prénom et nom).
[373]:
%reset -f
Sets¶
Vraiment uniques
Les objets de type set
sont des containers et sont mutables. Ce ne sont pas des séquences, on ne peut donc pas accéder à leurs item par leur index numérique. Ce type d’objet ne peut contenir que des objets uniques.
[374]:
s = {1, 2, 3}
[375]:
type(s)
[375]:
set
[376]:
print(dir(s))
['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
On peut supprimer les objets dupliqués d’une list
ou d’un tuple
en le transformat en objet set
. Attention, l’ordre est items des objets sources n’est pas préservé en les transformant en set
.
[377]:
shopping1 = ["banana", "apple", "strawberry", "banana"]
shopping1 = set(shopping1)
shopping1
[377]:
{'apple', 'banana', 'strawberry'}
[378]:
len(shopping1)
[378]:
3
[379]:
shopping1[0]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-379-e297a6d4a0ea> in <module>
----> 1 shopping1[0]
TypeError: 'set' object is not subscriptable
[380]:
shopping1.add("orange")
shopping1
[380]:
{'apple', 'banana', 'orange', 'strawberry'}
[381]:
"orange" in shopping1
[381]:
True
Avec les sets
on peut facilement effectuer des opérations de la théorie des ensembles (set theory
) comme l’intersection, l’union, la différence, ou la différence symétrique.
[382]:
shopping2 = {"banana", "apple", "mango"}
shopping_intersect = shopping1.intersection(shopping2)
shopping_intersect
[382]:
{'apple', 'banana'}
[383]:
shopping_merged = shopping1.union(shopping2)
shopping_merged
[383]:
{'apple', 'banana', 'mango', 'orange', 'strawberry'}
On peut ensuite convertir un objet set
en objet list
avec la fonction list
.
[384]:
shopping_merged = list(shopping_merged)
shopping_merged
[384]:
['strawberry', 'banana', 'apple', 'orange', 'mango']
[385]:
%reset -f
Dictionnaires¶
Ils font la paire
Les objets de type dict
sont des containers et sont mutables. Tout comme les objets de type set
, les dicts
ne sont pas des séquences. Ils sont constitués de paires key-value. Les keys doivent être des objets immutables, les values peuvent être n’importe quel type d’objet. Les dictionnaires sont très présents dans le langage Python.
[386]:
phonebook = {
"Bob": 383,
"Bill": 509,
"Donald": 102
}
phonebook
[386]:
{'Bob': 383, 'Bill': 509, 'Donald': 102}
[387]:
type(phonebook)
[387]:
dict
[388]:
print(dir(phonebook))
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
[389]:
phonebook["Bob"]
[389]:
383
[390]:
phonebook[383]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-390-9abd3fa563ad> in <module>
----> 1 phonebook[383]
KeyError: 383
[391]:
phonebook["John"]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-391-3f89bbc7ecee> in <module>
----> 1 phonebook["John"]
KeyError: 'John'
[392]:
# Rajouter une key-value entrée
phonebook["John"] = 984
phonebook
[392]:
{'Bob': 383, 'Bill': 509, 'Donald': 102, 'John': 984}
[393]:
phonebook["John"]
[393]:
984
[394]:
# Vérifier si une key est dans le dict
"John" in phonebook
[394]:
True
[395]:
# Cela ne fonctionne que sur les keys, par sur les values
984 in phonebook
[395]:
False
[396]:
# Supprimer une paire key-value
del phonebook["John"]
phonebook["John"]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-396-5eb835ec0b00> in <module>
1 # Supprimer une paire key-value
2 del phonebook["John"]
----> 3 phonebook["John"]
KeyError: 'John'
[397]:
# Les dicts ne sont pas des séquences.
phonebook[0]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-397-6cd85588bf81> in <module>
1 # Les dicts ne sont pas des séquences.
----> 2 phonebook[0]
KeyError: 0
[398]:
# La longueur d'un dict est le nombre de paires key-value qu'il contient.
len(phonebook)
[398]:
3
[399]:
# Seuls des immutables peuvent être des keys
mess = {
"str": "Les strings sont immutables",
12: "Les ints sont immutables",
3.53: "Les floats sont immutables",
(12, 24): "Les tuples sont immutables"
}
[400]:
# Les lists sont mutables, elles ne peuvent pas être des keys
mess[["list as a key"]] = 1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-400-1f3405e3d82b> in <module>
1 # Les lists sont mutables, elles ne peuvent pas être des keys
----> 2 mess[["list as a key"]] = 1
TypeError: unhashable type: 'list'
[401]:
# Ici la key est un tuple, on sait que les tuples sont immutables.
# Mais ce tuple-là elle contient une liste, qui est un objet mutable
# La key n'est donc pas considérée comme une key valide.
mess[(["tuple as a key"], ["containing a list"])] = 1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-401-28cb6015e709> in <module>
2 # Mais ce tuple-là elle contient une liste, qui est un objet mutable
3 # La key n'est donc pas considérée comme une key valide.
----> 4 mess[(["tuple as a key"], ["containing a list"])] = 1
TypeError: unhashable type: 'list'
[402]:
# Une autre manière de créer un dict.
{"name": ["Bob", "Bill"], "age": [24, 23]} == dict(name=["Bob", "Bill"], age=[24, 23])
[402]:
True
[403]:
# Les values d'un dict peuvent être de n'importe quel type.
import math
d = {
3.13: math,
"print": print,
"l": [1, 2, 3],
123: "123",
"nested_dicts": {"another_one": {"and_a_last_one": "here I am!"}},
"sets": {1, 3, 5},
True: False
}
d
[403]:
{3.13: <module 'math' (built-in)>,
'print': <function print>,
'l': [1, 2, 3],
123: '123',
'nested_dicts': {'another_one': {'and_a_last_one': 'here I am!'}},
'sets': {1, 3, 5},
True: False}
[404]:
d["nested_dicts"]["another_one"]["and_a_last_one"]
[404]:
'here I am!'
La méthode get
est pratique, elle retourne la value de la key passée comme argument. Si la key n’est pas trouvée, elle retourne None
par défaut ou la valeur du deuxième argument passé.
[405]:
d.get(123, "found")
[405]:
'123'
[406]:
d.get(456, "not found")
[406]:
'not found'
[407]:
d.get?
On peut obtenir une list
des keys ou des values en convertissant les objets retournés par les méthodes keys
et values
avec la fonction list
.
[408]:
list(d.keys())
[408]:
[3.13, 'print', 'l', 123, 'nested_dicts', 'sets', True]
[409]:
list(d.values())
[409]:
[<module 'math' (built-in)>,
<function print>,
[1, 2, 3],
'123',
{'another_one': {'and_a_last_one': 'here I am!'}},
{1, 3, 5},
False]
La méthode pop
enlève la key spécifiée et retourne la valeur correspondante.
[410]:
d.pop(3.13)
[410]:
<module 'math' (built-in)>
[411]:
d
[411]:
{'print': <function print>,
'l': [1, 2, 3],
123: '123',
'nested_dicts': {'another_one': {'and_a_last_one': 'here I am!'}},
'sets': {1, 3, 5},
True: False}
[412]:
%reset -f
Fonctions¶
Don’t Repeat Yourself
On a déjà vu quelques unes des fonctions intégrées directement dans Python (built-in functions). On peut en fait créer nos propres fonctions. Cela est bien utile lorsqu’on souhaite réutiliser un bout de code, et/ou lorsqu’on souhaite faciliter la lecture/compréhension du code en le scindant en blocs logiques. On crée un objet function
avec le keyword ``def``.
[413]:
def say_hi():
print("hi")
[414]:
type(say_hi)
[414]:
function
[415]:
print(dir(say_hi))
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
Le code sous la définition de la fonction say_hi
est exécuté lorsqu’on l’appelle.
[416]:
say_hi()
hi
Une fonction peut avoir des paramètres. On les précise sur la ligne de définition de la fonction. Ils peuvent ensuite être utilisés dans la fonction. La fonction add
a deux paramètres x
et y
.
[417]:
def add(x, y):
print(x + y)
La fonction add
est appelée avec les arguments 3
et 4
.
[418]:
add(3, 4)
7
[419]:
result = add(3, 4)
7
[420]:
result
[421]:
result is None
[421]:
True
Une fonction ne retourne qu’un seul et unique objet, qui est ``None`` par défaut. On définit l’objet retourné en le précédant du keyword ``return``.
[422]:
def add(x, y):
return x + y
[423]:
result = add(3, 4)
[424]:
result
[424]:
7
L’objet string
entouré de """
et positionné au début du bloc de la fonction est la docstring de la fonction.
[425]:
def add(x, y):
"""Additioner deux objets."""
return x + y
[426]:
add.__doc__
[426]:
'Additioner deux objets.'
[427]:
help(add)
Help on function add in module __main__:
add(x, y)
Additioner deux objets.
Comme une fonction est un objet, on peut la manipuler comme les autres objets.
[428]:
add2 = add
[429]:
add2(2, 3)
[429]:
5
[430]:
# del add enlève le nom add du namespace
del add
add2(0, 2)
[430]:
2
Certains paramètres peuvent avoir des valeurs par défaut. Lorsqu’on appelle la fonction, on n’est donc pas obligé de préciser la valeur de ce paramètre (ce qui équivaut à dire qu’on n’est pas obligé de fournir un argument pour ce paramètre). Dans ce cas-là, la valeur par défaut est utilisée lors de l’exécution du code. Lorsqu’on souhaite utiliser une autre valeur que la valeur par défaut, on peut soit fournir l’argument si on respecte l’ordre des paramètres, on peut aussi fournir l’argument en nommant le paramètre (``param=arg``).
[431]:
print?
[432]:
def function_with_defaults(a, b, c=0):
return a + b + c
function_with_defaults(1, 4)
[432]:
5
[433]:
function_with_defaults(1, 4, c=50)
[433]:
55
[434]:
function_with_defaults(1, 4, 5)
[434]:
10
[435]:
function_with_defaults(c=50, 1, 4)
File "<ipython-input-435-e132c12cc603>", line 1
function_with_defaults(c=50, 1, 4)
^
SyntaxError: positional argument follows keyword argument
[436]:
function_with_defaults(c=50, b=4, a=1)
[436]:
55
Une fonction peut appeler une autre fonction.
[437]:
def first():
print("first function called")
second()
def second():
print("second function called")
[438]:
first()
first function called
second function called
Lorsqu’on exécute le bloc de définition d’une fonction (la ligne def
et le bloc indenté qui suit), Python se prépare à exécuter la fonction mais ne l’exécute pas encore, elle ne le sera que lorsqu’elle sera appelée. A part les erreurs de syntaxe (SyntaxError
) que Python détecte lors de cette étape de préparation, toutes les autres erreurs sont détectées seulement lorsque la fonction est appelée.
[439]:
unknown
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-439-2d7bb5572221> in <module>
----> 1 unknown
NameError: name 'unknown' is not defined
[440]:
# unknown n'est pas une variable définie, malgré ça, on peut exécuter ce code
def dont_even_dare():
return unknown
[441]:
# On obtient une erreur lorsqu'on appelle la fonction dont_even_dare
dont_even_dare()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-441-a64b5e067227> in <module>
1 # On obtient une erreur lorsqu'on appelle la fonction dont_even_dare
----> 2 dont_even_dare()
<ipython-input-440-c0383c067b3c> in dont_even_dare()
1 # unknown n'est pas une variable définie, malgré ça, on peut exécuter ce code
2 def dont_even_dare():
----> 3 return unknown
NameError: name 'unknown' is not defined
L’exécution d’une fonction se passe dans son scope local. Les objets référencés par la fonction (firstname = "Bob"
) sont déférencés lorsque l’exécution prend fin. On ne peut plus y avoir accès après ça.
[442]:
def function_scope():
firstname = "Bob"
function_scope()
firstname
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-442-88897d2c37bf> in <module>
2 firstname = "Bob"
3 function_scope()
----> 4 firstname
NameError: name 'firstname' is not defined
Même la référence de l’objet retourné par la fonction est supprimée à la fin de l’exécution.
[443]:
def function_scope():
firstname = "Bob"
return firstname
function_scope()
firstname
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-443-adec7aecec8c> in <module>
3 return firstname
4 function_scope()
----> 5 firstname
NameError: name 'firstname' is not defined
Pour préserver l’objet retourné on lui associe un nom.
[444]:
firstname = function_scope()
firstname
[444]:
'Bob'
x
ci-dessous n’est ni un paramètre de la fonction look_outside
ni un objet créé dans la fonction. Lorsqu’on exécute look_outside
, Python regarde d’aborde dans le scope local de la fonction s’il existe une référence x
. Comme il ne la trouve pas, Il cherche alors dans le scope global de la fonction.
Dans le premier cas ci-dessous, aucune référence x
n’est créée en dehors du scope de la fonction. Une erreur est donc déclenchée.
[445]:
def look_outside(b):
return x + b
look_outside(20)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-445-0e88279aaa1d> in <module>
2 return x + b
3
----> 4 look_outside(20)
<ipython-input-445-0e88279aaa1d> in look_outside(b)
1 def look_outside(b):
----> 2 return x + b
3
4 look_outside(20)
NameError: name 'x' is not defined
Ici, comme la référence x
est créée (ajoutée au namespace) avant l’exécution de look_outside
, celle-ci peut la trouver et s’exécute dont jusqu’à son terme.
[446]:
def look_outside(b):
return x + b
x = 10
look_outside(20)
[446]:
30
[447]:
def look_inside_first(b):
x = 0
return x + b
x = 10
look_inside_first(20)
[447]:
20
Dans le cas ci-dessous, outer_function
est appelée avec b=20
. Lors de son exécution, la fonction inner_function
est d’abord définie. Puis outer_function
tente de retourner x + inner_function() + b
. Elle trouve une référence à x
dans le scope global. Elle exécute ensuite inner_function
. Cette dernière doit retourne la référence y
, sauf que cette référence n’est définie nulle part. Une erreur est donc déclenchée.
[448]:
def outer_function(b):
def inner_function():
return y
return x + inner_function() + b
x = 10
outer_function(20)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-448-8a9c831108bd> in <module>
5
6 x = 10
----> 7 outer_function(20)
<ipython-input-448-8a9c831108bd> in outer_function(b)
2 def inner_function():
3 return y
----> 4 return x + inner_function() + b
5
6 x = 10
<ipython-input-448-8a9c831108bd> in inner_function()
1 def outer_function(b):
2 def inner_function():
----> 3 return y
4 return x + inner_function() + b
5
NameError: name 'y' is not defined
Le code va s’exécuter correctement tant que la référence à y
est créée avant ou pendant l’exécution de inner_function
.
[449]:
def outer_function(b):
y = 100
def inner_function():
return y
return x + inner_function() + b
x = 10
outer_function(20)
[449]:
130
[450]:
def outer_function(b):
def inner_function():
y = 100
return y
return x + inner_function() + b
x = 10
outer_function(20)
[450]:
130
[451]:
def outer_function(b):
def inner_function():
return y
y = 100
return x + inner_function() + b
x = 10
outer_function(20)
[451]:
130
[452]:
def outer_function(b):
def inner_function():
return y
return x + inner_function() + b
x = 10
y = 100
outer_function(20)
[452]:
130
Attention aux objets mutables passés comme argument à une fonction. Lorsqu’on passe un argument à une fonction, celle-ci utilise directement l’objet référencé.
[453]:
def is_same_objet(i, f, s, l, t, se, d):
print(id(i) == id(io))
print(id(f) == id(fo))
print(id(s) == id(so))
print(id(l) == id(lo))
print(id(t) == id(to))
print(id(se) == id(seo))
print(id(d) == id(do))
io, fo, so, lo, to, seo, do = 1, 2.2, "", list(), tuple(), set(), dict()
is_same_objet(io, fo, so, lo, to, seo, do)
True
True
True
True
True
True
True
Si on passe un objet mutable comme argument d’une fonction, cette fonction va utiliser directement cet objet. S’il est modifié au cours de l’exécution de la fonction, c’est le bien mutable source qui est modifié, même s’il n’a pas la même référence dans la fonction.
[454]:
def achtung_mutable(mutable):
mutable[0] = "John"
return mutable
[455]:
new_names = achtung_mutable(names)
new_names
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-455-82436503c9a1> in <module>
----> 1 new_names = achtung_mutable(names)
2 new_names
NameError: name 'names' is not defined
[456]:
# names a aussi été modifié!
names
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-456-2e452de3783e> in <module>
1 # names a aussi été modifié!
----> 2 names
NameError: name 'names' is not defined
Si ce comportement n’est pas souhaité, on peut soit appeler la fonction avec une copie du mutable, soit créer une copie directement dans le bloc de la fonction.
[457]:
names = ["Bob", "Bill"]
new_names = achtung_mutable(names[:])
print(new_names, names)
['John', 'Bill'] ['Bob', 'Bill']
[458]:
def achtung_mutable(mutable):
mutable = mutable[:]
mutable[0] = "John"
return mutable
names = ["Bob", "Bill"]
new_names = achtung_mutable(names[:])
print(new_names, names)
['John', 'Bill'] ['Bob', 'Bill']
[459]:
%reset -f
Unpacking, Packing¶
Déballer son sac
On peut facilement extraire un à un les éléments d’un objet iterable (séquences et containers) pour assigner de nouvelles variables avec la méthode dite de l”unpacking. Cela se fait le plus souvent à partir d’un tuple
.
[460]:
t = ("Bob", 27, ["Sarah", "Jim"])
[461]:
type(t)
[461]:
tuple
t
comprend trois éléments, ces trois éléments sont assignés dans l’ordre aux variables name
, age
et siblings
.
[462]:
name, age, siblings = t
print(name, age, siblings)
Bob 27 ['Sarah', 'Jim']
Mais on peut aussi le faire avec d’autres types d’objet (avec plus ou moins de succès).
[463]:
l = ["Bob", 27, ["Sarah", "Jim"]]
name, age, siblings = l
print(name, age, siblings)
Bob 27 ['Sarah', 'Jim']
[464]:
s = "abc"
a, b, c = s
print(a, b, c)
a b c
[465]:
# Attention! Les keys des dictionnaires ne sont ordonnées
# que depuis la version 3.6.
d = {"name": "Bob", "surname": "Smith"}
n, s = d
print(n, s)
name surname
[466]:
# Mauvaise idée!
s = {"a", "b", "c"}
a, b, c = s
print(a, b, c)
c a b
[467]:
x = 3.4
pint, pdec = 3.4
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-467-5c0c97d6e39b> in <module>
1 x = 3.4
----> 2 pint, pdec = 3.4
TypeError: cannot unpack non-iterable float object
Il est préférable que le nombre de variable à assigner soit égal au nombre d’éléments qui constituent l”iterable.
[468]:
a, b = ("a")
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-468-3d0636e9e44a> in <module>
----> 1 a, b = ("a")
ValueError: not enough values to unpack (expected 2, got 1)
[469]:
a, b = ("a", "b", "c")
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-469-059e52dab1eb> in <module>
----> 1 a, b = ("a", "b", "c")
ValueError: too many values to unpack (expected 2)
Si ce n’est pas le cas, on peut rajouter un symbole *
devant le nom d’une des variables pour déclencher un comportement spécial.
[470]:
l = ("First value of interest", "big", "mess", "after", "that")
myvalue, *mess = l
print(myvalue, mess)
First value of interest ['big', 'mess', 'after', 'that']
On peut utiliser l”unpacking et le symbole *
pour passer tous les éléments extraits d’un iterable à une fonction.
[471]:
names = ["Rachel", "Sarah", "Bob", "Bill"]
print(names)
['Rachel', 'Sarah', 'Bob', 'Bill']
[472]:
print(*names)
Rachel Sarah Bob Bill
[473]:
*names
File "<ipython-input-473-189f4c92eaa6>", line 4
SyntaxError: can't use starred expression here
Packing est l’action par laquelle plusieurs objets sont rassemblés dans un tuple
.
[474]:
"Bob", 27, ["Sarah", "Jim"]
[474]:
('Bob', 27, ['Sarah', 'Jim'])
[475]:
packed = "Bob", 27, ["Sarah", "Jim"]
packed
[475]:
('Bob', 27, ['Sarah', 'Jim'])
[476]:
type(packed)
[476]:
tuple
Lorsqu’on essaie de retourner plusieurs objets dans une fonction, ceux-ci sont en fait toujours rassemblés dans un ``tuple``. Packing est à l’oeuvre.
[477]:
def person_info():
return "Anna", 26 # --> return (name, age)
output = person_info()
type(output)
[477]:
tuple
On peut utiliser l”unpacking pour assigner à des variables les objets retournés par la fonction person_info
.
[478]:
name, age = person_info()
print(name, age)
Anna 26
[479]:
%reset -f
Boucle for
et fonctions associées¶
May the for be with you
Les boucles ``for`` sont très simples et utiles en Python. Elles permettent de répéter un bloc d’instructions un certain nombre de fois. L’objet qui suit le keyword in
doit être un iterable.
[480]:
for i in range(3):
print(i)
0
1
2
[481]:
range?
range
est une fonction qui accepte 3 arguments start
, stop
et step
. Par défaut start
est égal à 0 et step
à 1. Cette fonction ne retourne pas une liste mais un obtjet de type range
. Il s’agit d’un objet un peu spécial. Lorsqu’on exécute range(1_000_001)
, considérant le code de la cellule précédente, on pourrait penser que cette instruction génère des nombres de 0 à 1000000, mais non. Cet objet est une sorte de distributeur, il ne fait rien tant qu’on ne lui
demande rien. Lorsqu’on lui demande quelque chose, comme dans une boucle for
, il s’active et commence à distribuer. Dans le cas de range(1_000_001)
, l’objet distribue des ints
en commençant à 0, un par un, jusqu’à 1000000.
[482]:
for i in range(3):
print(id(i))
140734432776992
140734432777024
140734432777056
[483]:
type(range(3))
[483]:
range
Pour créer une liste croissante d’entiers, on peut convertir un objet range
en liste avec la fonction list
. Alors que range(100_000_001)
occupe très peu de mémoire, liste(range(100_000_001))
va occuper plus de 900Mb (ne pas exécuter ça!).
[484]:
list(range(3))
[484]:
[0, 1, 2]
Pour itérer au travers d’une list
, on pourrait écrire le code suivant: * On génère des ints
avec la fonction range
, le nombre d’ìntsà générer dépend de la longueur de la
listqu'on calcule avec la fonction
len* On accède à chaque item de la
list` par son indice
[485]:
names = ["Sarah", "Rachel", "Bob", "Bill"]
for i in range(len(names)):
print(names[i])
Sarah
Rachel
Bob
Bill
Même si la méthode ci-dessus fonctionne, elle n’est en fait pas pythonic, elle s’inspire de la manière dont on écrit une boucle for
dans d’autres langages. Python propose une manière plus claire d’itérer au travers d’un objet. La boucle for
de Python est en fait similaire à une boucle for each
, elle extrait chaque élément de l”iterable sur laquelle elle agit.
[486]:
for name in names:
print(name)
Sarah
Rachel
Bob
Bill
On peut pratiquer l”unpacking dans la ligne de la boucle for
.
[487]:
fullnames = [("Rachel", "Miller"), ("Bob", "Smith"), ("Anna", "Johnson"), ("Bill", "Davis")]
for first_name, last_name in fullnames:
print(first_name, last_name)
Rachel Miller
Bob Smith
Anna Johnson
Bill Davis
On peut utiliser la boucle for
pour itérer au travers des objets de type string
, list
, tuple
, dict
, set
(et plus encore).
[488]:
txt = "abcdefghi"
for letter in txt:
print(letter)
a
b
c
d
e
f
g
h
i
[489]:
names = ("Bob", "Bill", "Sarah", "Anna")
for name in names:
print(name)
Bob
Bill
Sarah
Anna
[490]:
phonebook = {
"Bob": 383,
"Bill": 509,
"Donald": 102
}
for k in phonebook:
print(k)
Bob
Bill
Donald
[491]:
for k, v in phonebook.items():
print(k, v)
Bob 383
Bill 509
Donald 102
[492]:
for v in phonebook.values():
print(v)
383
509
102
On peut inverser l’ordre des itérations en appelant la fonction reversed
sur l”iterable.
[493]:
for name in reversed(names):
print(name)
Anna
Sarah
Bill
Bob
Un peu comme la fonction range
, la fonction reversed
ne retourne pas une list
mais un objet qualifié d”iterator. Il s’agit aussi d’un distributeur, il distribue dans ce cas-là les éléments de l”iterable en commençant par le dernier. Comme on peut itérer sur un iterator, ce type d’objet est aussi un iterable.
[494]:
reversed(names)
[494]:
<reversed at 0x25327e62048>
[495]:
type(reversed(names))
[495]:
reversed
[496]:
reversed(names)[0]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-496-0f87fe6cd96c> in <module>
----> 1 reversed(names)[0]
TypeError: 'reversed' object is not subscriptable
Les iterators sont des objets qui se tarissent. Après leur avoir demandé de distribuer leur « contenu » une fois, ils ne peuvent plus le redistribuer.
[497]:
reversed_names = reversed(names)
list(reversed_names)
[497]:
['Anna', 'Sarah', 'Bill', 'Bob']
[498]:
list(reversed_names)
[498]:
[]
reserved_names
est maintenant un iterator vide, on ne peut plus le réutiliser. (A noter que l’objet retourné par range
ressemble à un iterator mais n’en est en fait pas un, on peut donc le réutiliser).
[499]:
for name in sorted(names):
print(name)
Anna
Bill
Bob
Sarah
[500]:
for name in sorted(names, key=len):
print(name)
Bob
Bill
Anna
Sarah
sorted
ne retourne pas un iterator mais directement une list
.
[501]:
sorted(names)
[501]:
['Anna', 'Bill', 'Bob', 'Sarah']
[502]:
type(sorted(names))
[502]:
list
La fonction ``enumerate`` fournit sous la forme d’un ``tuple`` un compteur et les les éléments de l’iterable avec laquelle elle est appelée.
[503]:
for i, name in enumerate(names):
print(f"Iteration {i}: {name}")
Iteration 0: Bob
Iteration 1: Bill
Iteration 2: Sarah
Iteration 3: Anna
[504]:
for i, name in enumerate(reversed(names)):
print(f"Iteration {i}: {name}")
Iteration 0: Anna
Iteration 1: Sarah
Iteration 2: Bill
Iteration 3: Bob
L’objet que la fonction enumerate
retourne est un iterator.
[505]:
enumerate(names)
[505]:
<enumerate at 0x25327e98ca8>
[506]:
enumerated_names = enumerate(names)
type(enumerated_names)
[506]:
enumerate
[507]:
list(enumerated_names)
[507]:
[(0, 'Bob'), (1, 'Bill'), (2, 'Sarah'), (3, 'Anna')]
[508]:
list(enumerated_names)
[508]:
[]
La function zip
permet d’itérer sur plusieurs iterables en extrayant leur élément en même temps. L’objet qu’elle retourne est un iterator.
[509]:
ages = [25, 28, 23, 21]
first_letters = ["B", "R", "B", "A"]
[510]:
for name, first_letter, age in zip(names, first_letters, ages):
print(name, first_letter, age, sep=" --- ")
Bob --- B --- 25
Bill --- R --- 28
Sarah --- B --- 23
Anna --- A --- 21
[511]:
for i, (name, first_letter, age) in enumerate(zip(names, first_letters, ages)):
print(i, name, first_letter, age, sep=" --- ")
0 --- Bob --- B --- 25
1 --- Bill --- R --- 28
2 --- Sarah --- B --- 23
3 --- Anna --- A --- 21
[512]:
zipped = zip(names, first_letters, ages)
type(zipped)
[512]:
zip
[513]:
list(zipped)
[513]:
[('Bob', 'B', 25), ('Bill', 'R', 28), ('Sarah', 'B', 23), ('Anna', 'A', 21)]
[514]:
list(zipped)
[514]:
[]
Mais que se passe-t-il exactement lorsqu’on exécute une boucle for
? A chaque itération, un élément de l”iterable est référencé par le nom défini juste après le keyword for
. On peut voir que les objets extraits d’une list
sont exactement les objets auxquels elle fait référence.
[515]:
for i, name in enumerate(names):
print(id(names[i]), id(name), sep=" --- ")
2556174778640 --- 2556174778640
2556174782000 --- 2556174782000
2556174782056 --- 2556174782056
2556174781552 --- 2556174781552
On a souvent besoin de modifier un à un les éléments d’une ``list`` (exemple: mettre des noms en capitales). On peut commettre l’erreur de l’implémenter de la manière suivante.
[516]:
for name in names:
name = name.upper()
names
[516]:
('Bob', 'Bill', 'Sarah', 'Anna')
Cela ne fonctionne pas car l’instruction name = name.upper()
crée un nouvel objet dont la valeur est name.upper()
et qui est associé au nom name
. La référence à l’élément de names
par le nom name
est donc perdue (écrasée, remplacée).
[517]:
for i, name in enumerate(names):
print(f"name(id={id(name)}, valeur=names[{i}])")
name = name.upper()
print(f"name(id={id(name)}, valeur=names[{i}].upper()]", end="\n\n")
name(id=2556174778640, valeur=names[0])
name(id=2556175279808, valeur=names[0].upper()]
name(id=2556174782000, valeur=names[1])
name(id=2556175279808, valeur=names[1].upper()]
name(id=2556174782056, valeur=names[2])
name(id=2556175279808, valeur=names[2].upper()]
name(id=2556174781552, valeur=names[3])
name(id=2556175279808, valeur=names[3].upper()]
Une autre option serait d’accéder à chaque élément de la list
.
[518]:
for i in range(len(names)):
names[i] = names[i].upper()
names
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-518-2bb71f59e7d7> in <module>
1 for i in range(len(names)):
----> 2 names[i] = names[i].upper()
3 names
TypeError: 'tuple' object does not support item assignment
Mais la méthode recommandée est la suivante: * On crée une list
vide avec l = []
* A chaque itération on vient rajouter un élément à cette liste vide avec la méthode append
(l.append(new_item)
)
[519]:
names = ["Bob", "Bill", "Sarah", "Rachel"]
upper_names = []
for name in names:
upper_names.append(name.upper())
upper_names
[519]:
['BOB', 'BILL', 'SARAH', 'RACHEL']
On peut inclure des conditions if
dans le bloc d’une boucle for
pour préciser les actions à effectuer.
[520]:
for name in names:
if "s" in name.lower():
if "a" in name.lower():
if "r" in name.lower():
print(f"I think you're Sarah, correct? Hmm I'm {name}!")
I think you're Sarah, correct? Hmm I'm Sarah!
Cela génère des fois du code qui est très indenté et pas facile à lire, on peut utiliser les *keywords* ``continue`` et ``break`` pour contrôler l’exécution de la boucle. continue
entraîne le démarrage direct de la prochaine itération, ainsi, tout le code qui est situé sous continue
est passé si continue
est exécuté. break
arrête totalement l’exécution de la boucle.
[521]:
for name in names:
if "s" not in name.lower():
continue
if "a" not in name.lower():
continue
if "r" not in name.lower():
continue
# Exécuté si les trois conditions ci-dessus sont fausses
print(f"I think you're Sarah, correct? Hmm I'm {name}!")
break
# On aurait aussi pu écrire le code de la manière suivante:
# for name in names:
# if all(c in name.lower() for c in "sar"):
# print(f"I think you're Sarah, correct? Hmm I'm {name}!")
# break
I think you're Sarah, correct? Hmm I'm Sarah!
[522]:
for name in names:
if name == "Sarah":
continue
print(name) # Exécuté si name != "Sarah"
Bob
Bill
Rachel
[523]:
for name in names:
if name == "Sarah":
break # Stoppe la boucle lorsque name == "Sarah"
print(name)
Bob
Bill
Contrairement aux références créés dans le scope d’une fonction, les références créés dans un bloc for
ne sont pas supprimées à la fin de l’exécution du bloc. La référence name
est donc toujours accessible et sa valeur est celle de la dernière itération. Ici on voit que sa valeur est 'Sarah'
, ce qui a effectivement stoppé l’exécution de la boucle.
[524]:
name
[524]:
'Sarah'
Boucle while
¶
On ne l’arrête plus
La boucle for
en Python est tellement pratique que la boucle while
est au final assez peu utilisée. Les instructions d’une boucle while
sont exécutées tant que la condition qui suit le keyword while
est vraie. On peut aussi utiliser les keywords continue
et break
pour contrôler l’exécution de la boucle.
[525]:
i = 0
while i < 5:
print(i)
i += 1
0
1
2
3
4
Il peut être plus naturel d’écrire une boucle while
au lieu d’une boucle for
lorsque l’on recherche quelque chose dans un iterable.
[526]:
letters = "abcdefghijklmnopqrstuvwxyz"
index_o = 0
while letters[index_o] != "o":
index_o += 1
print(f"La lettre `o` est la lettre n°{index_o + 1} de l'alphabet")
La lettre `o` est la lettre n°15 de l'alphabet
[527]:
for i, l in enumerate(letters):
if l == "u":
index_u = i
break
print(f"La lettre `u` est la lettre n°{index_u + 1} de l'alphabet")
La lettre `u` est la lettre n°21 de l'alphabet
Un des dangers de la boucle ``while`` est qu’on peut créer une boucle qui ne s’arrête jamais. Le code ci-dessous va s’exécuter à l’infini, la condition i < 5
étant toujours vraie (on a oublié d’incrémenter i
). Il faut alors forcer l’arrêt de l’interpréteur Python.
[528]:
# i = 0
# while i < 5:
# print(i)
Lire et écrire dans un fichier¶
Cours Primaire
On peut facilement lire le contenu d’un fichier texte et écrire du texte dans un fichier. Pour cela, on utilise: * la fonction open
qui permet d’ouvrir un fichier et de le lire ou d’y écrire quelque chose * le keyword with
qui signale l’utilisation d’un objet qualifié de context manager (ici, la fonction open
). L’emploi de ce genre d’objet avec un bloc with
permet de s’assurer de la fermeture de la ressource ouverte (ici, un fichier), même si une erreur se produit dans
l’exécution du bloc de code
[529]:
%%writefile data.txt
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
Writing data.txt
La fonction open
ouvre le fichier en mode lecture ("r"
pour read) et retourne un objet référencé par f
(on peut choisir n’importe quel nom) et lié au fichier en cours de lecture.
[530]:
with open("data.txt", "r") as f:
print(type(f))
print(dir(f))
<class '_io.TextIOWrapper'>
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'write_through', 'writelines']
On peut vérifier que le fichier est bien fermé par le context manager.
[531]:
with open("data.txt", "r") as f:
print(f.closed)
print(f.closed)
False
True
Pour l’instant l’exécution des instructions du bloc with
s’est déroulée sans erreur. On peut montrer que le context manager ferme bien le fichier même si une erreur survient dans le bloc.
[532]:
f.closed
[532]:
True
[533]:
with open("data.txt", "r") as f:
print("Fichier ouvert?", not f.closed)
print(f.wrong_attribute_to_trigger_an_error)
print("Rien ne va plus")
Fichier ouvert? True
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-533-69c400717321> in <module>
1 with open("data.txt", "r") as f:
2 print("Fichier ouvert?", not f.closed)
----> 3 print(f.wrong_attribute_to_trigger_an_error)
4 print("Rien ne va plus")
AttributeError: '_io.TextIOWrapper' object has no attribute 'wrong_attribute_to_trigger_an_error'
[534]:
print("Fichier ouvert?", not f.closed)
Fichier ouvert? False
On peut lire le contenu complet du fichier grâce aux méthodes read
et readlines
.
[535]:
with open("data.txt", "r") as f:
whole_content = f.read()
whole_content
[535]:
'name,age,gender\nSarah,27,F\nBob,28,M\nRachel,24,F\nBill,22,M\n'
[536]:
print(whole_content)
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
[537]:
with open("data.txt", "r") as f:
lines = f.readlines()
lines
[537]:
['name,age,gender\n',
'Sarah,27,F\n',
'Bob,28,M\n',
'Rachel,24,F\n',
'Bill,22,M\n']
[538]:
for line in lines:
print(line)
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
Chaque ligne du fichier se termine par un caractère retourne à la ligne. On peut utiliser la méthode rstrip
des objets de type string
pour retirer ce caractère spécial.
[539]:
for line in lines:
print(line.strip())
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
Mais on peut aussi le lire ligne par ligne en itérant sur l’objet retourné par la fonction ``open``. Cet objet est en fait un iterator.
[540]:
with open("data.txt", "r") as f:
for line in f:
print(line.strip())
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
Comme f
est un iterator, on ne peut itérer dessus qu’un seule fois.
[541]:
with open("data.txt", "r") as f:
for line in f:
print(line.strip())
for line in f:
print(line.strip())
name,age,gender
Sarah,27,F
Bob,28,M
Rachel,24,F
Bill,22,M
L’avantage de lire un fichier ligne par ligne est double: * On ne charge pas l’ensemble des données du fichier dans la mémoire * On peut arrêter sa lecture avant la fin si on cherche quelque chose de précis
[542]:
with open("data.txt", "r") as f:
for i, line in enumerate(f):
if "Bob" in line:
print(f"I've found Bob @line {i + 1} !!!")
break # Stoppe la lecture du fichier
I've found Bob @line 3 !!!
[543]:
with open("data.txt", "r") as f:
for i, line in enumerate(f):
line = line.strip()
if line.startswith("Bob"):
age = line.split(",")[1]
break
print(f"Bob is {age} yo.")
Bob is 28 yo.
Pour écrire dans un nouveau fichier, la démarche est tout à fait similare. Au lieu de passer l’argument r
à la fonction open
, on lui passe l’argument w
(pour write). La méthode write
n’accepte comme argument que des objets de type string
. Au besoin, on convertit les objets (float
) par exemple en string
en les passant comme argument à la fonction str
.
[544]:
names = ["Sarah", "Rachel", "Bob", "Bill"]
with open("new_data.txt", "w") as f:
for name in names:
f.write(name)
[545]:
!type new_data.txt
SarahRachelBobBill
Il faut faire attention à inclure le caractère spécial "\n"
pour marquer les sauts de ligne.
[546]:
names = ["Sarah", "Rachel", "Bob", "Bill"]
with open("new_data.txt", "w") as f:
for name in names:
f.write(name)
f.write("\n")
[547]:
!type new_data.txt
Sarah
Rachel
Bob
Bill
[548]:
names = ["Sarah", "Rachel", "Bob", "Bill"]
with open("new_data.txt", "w") as f:
for name in names:
f.write(name + "\n")
[549]:
!type new_data.txt
Sarah
Rachel
Bob
Bill
[550]:
ages = [26, 24, 29, 25]
with open("new_data.txt", "w") as f:
for age in ages:
f.write(str(age) + "\n")
[551]:
!type new_data.txt
26
24
29
25
On peut aussi utiliser la méthode writelines
pour écrire directement l’ensemble des éléments d’un objet dans le fichier.
[552]:
names = ["Sarah", "Rachel", "Bob", "Bill"]
with open("new_data.txt", "w") as f:
f.writelines("\n".join(names))
[553]:
!type new_data.txt
Sarah
Rachel
Bob
Bill
[554]:
!del /f new_data.txt data.txt
%reset -f
Interactions¶
Le client est roi
On peut intéragir avec du code Python lors de son exécution de deux manières différentes: * En passant des arguments à la suite de la commande python myscript.py
exécutée dans la Command Prompt. On peut récupérer la valeur de ces arguments et l’utiliser dans le code * En utilisant la fonction input
qui met en pause l’exécution du code tant que l’utilisateur n’entre pas une valeur.
[555]:
%%writefile argv.py
import sys
age = sys.argv[1]
print(f"You are {age} years old.")
Writing argv.py
[556]:
# Equivalent à `python argv.py 28` dans la command prompt
%run argv.py 28
You are 28 years old.
Avec la première méthode, on peut facilement créer des outils flexibles sous la forme de scripts
[557]:
%%writefile add.py
import sys
a, b = sys.argv[1], sys.argv[2] # ou a, b = sys.argv[1:]
print(float(a) + float(b))
Writing add.py
[558]:
%run add.py 3 4.5
7.5
[559]:
%%writefile showcontent.py
import sys
file = sys.argv[1]
with open(file, "r") as f:
content = f.read()
print(content)
Writing showcontent.py
[560]:
%%writefile names.txt
Anna
Rachel
Bob
Bill
Writing names.txt
[561]:
%run showcontent.py names.txt
Anna
Rachel
Bob
Bill
La fonction input
peut accepter un argument de type string
. Si on lui en fournit un, elle l’affiche puis attent qu’on entre une valeur et tape sur la touche Entrée, l’objet qu’elle retourne est une string
qui contient ce que l’on vient de taper.
[562]:
input()
[562]:
''
[572]:
age = input("How old are you?")
How old are you?25
[573]:
%%writefile input.py
age = input("How old are you?")
print(f"You are {age} years old.")
Writing input.py
[574]:
# Equivalent à `python input.py` dans la command prompt
%run input.py
How old are you?28
You are 28 years old.
[575]:
!del /f input.py argv.py showcontent.py names.txt add.py
%reset -f
Traceback¶
Suivie à la trace
Le traceback est le long message que Python affiche lorsqu’une erreur survient pendant l’exécution du code. Le traceback se lit de bas en haut. Il est très utile pour débugger un script.
[576]:
def first_level(a, b):
return second_level(a, b)
def second_level(a, b):
return third_level(a, b)
def third_level(a, b):
return a + b
[577]:
first_level(1, 2)
[577]:
3
L’addition des paramètres a
et b
est réalisée par la fonction third_level
. On va déclencher une TypeError
en tentant d’additioner une string
avec un int
, ce qui est impossible.
[578]:
"Bob" + 1
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-578-1e6b406786db> in <module>
----> 1 "Bob" + 1
TypeError: can only concatenate str (not "int") to str
[579]:
first_level("Bob", 1)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-579-26206af4b823> in <module>
----> 1 first_level("Bob", 1)
<ipython-input-576-0c177701bd8c> in first_level(a, b)
1 def first_level(a, b):
----> 2 return second_level(a, b)
3
4
5 def second_level(a, b):
<ipython-input-576-0c177701bd8c> in second_level(a, b)
4
5 def second_level(a, b):
----> 6 return third_level(a, b)
7
8
<ipython-input-576-0c177701bd8c> in third_level(a, b)
8
9 def third_level(a, b):
---> 10 return a + b
TypeError: can only concatenate str (not "int") to str
Le traceback nous indique que l’erreur s’est produite à la ligne return a + b
du code dans la fonction third_level
. Il afficher ensuite (de bas en haut) les appels successifs aux trois fonctions imbriquées.
[580]:
%reset -f
Classes¶
Des objets, des objets, des objets
On peut définir nos propres types d’objet avec le keyword class
. La class
définit les attributs et les méthodes des objets qu’elle va engendrer.
[581]:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def __repr__(self):
return f"rectangle of width: {self.width} and height: {self.height}"
def __eq__(self, other_rectangle):
if self.width == other_rectangle.width and self.height == other_rectangle.height:
return True
else:
return False
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
[582]:
type(Rectangle)
[582]:
type
[583]:
print(dir(Rectangle))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'area', 'perimeter']
[584]:
r1 = Rectangle(10, 5)
[585]:
type(r1)
[585]:
__main__.Rectangle
[586]:
print(dir(r1))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'area', 'height', 'perimeter', 'width']
[587]:
r1
[587]:
rectangle of width: 10 and height: 5
[588]:
print(r1.perimeter(), r1.area())
30 50
[589]:
r2 = Rectangle(5, 10)
r2 == r1
[589]:
False
[590]:
r1 > r2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-590-e3bab7ac7782> in <module>
----> 1 r1 > r2
TypeError: '>' not supported between instances of 'Rectangle' and 'Rectangle'
[591]:
%reset -f
[ ]:
Écriture et exécution d’un script¶
Le style¶
Commenter¶
Commenter son code est essentiel. Cela permettra aux personnes liront le code de mieux le comprendre (pour le modifier par exemple) et cela permettra aussi à l’auteur du code de se rappeler plus rapidement des spécificités de celui-ci. Commenter son code c’est donc avant tout se rendre service pour éviter de perdre du temps plus tard.
Ainsi lorsqu’on écrit un script, la première chose à faire est
de le commenter, avant même l’écriture d’une seule ligne de vrai code.
On pourrait rajouter au début de chaque script qu’on écrit la docstring
suivante:
"""Une courte phrase qui dit ce que le script va faire.
Ici, on précise les choses, en détaillant la logique
du script, et l'utilisation qu'on imagine en faire.
Utilisation: Décrire comment utiliser le script.
Références: On peut rajouter ici des références utiles
(liens internet par exemple).
Dépendances: La version de Python et les dépendances
sur lesquelles le script va s'appuyer.
exemple: python 3.7, pandas 0.25
Historique:
- nom (date): création du script
"""
Écrire un script¶
Un script Python est un simple fichier texte dont
l’extension est .py
. Par exemple, le fichier
parsefile.py
est un script Python.
Pour écrire du code dans un script Python, on peut utiliser un éditeur de texte simple comme Notepad. Des éditeurs plus évolués comme Notepad++ permettent de changer la couleur du texte en fonction de ce qu’il représente. On utilisera Spyder qui est un logiciel dédié à l’écriture du code en Python.
Exécuter un script¶
Depuis Spyder¶
On peut ouvrir un script Python dans Spyder puis l’exécuter:
- Ligne par ligne avec la touche
F9
, - Une sélection spécifique (une partie d’une ligne, plusieurs lignes, etc.) avec la touche
F9
, - Une cellule encadrée par le symbole
# %%
avec la combinaisonCtrl + Entrée
, - Entièrement avec la touche
F5
ou en cliquant sur la flèche verte.
Depuis l’Anaconda Prompt¶
Il faut ouvrir la console Anaconda Prompt et s’assurer que l’on se trouve bien dans un environnement conda permettant d’exécuter le script. On activera l’environnement avec:
La commande suivante va exécuter le code contenu dans le fichier parsefile.py
avec la version de Python
installée dans l’environnement parser_env. Il faudra que les dépendances du script (pandas par exemple)
soient installées au préalable dans l’environnement (conda install pandas
).
Si le fichier contient des données d’entrée à adapter suivant son application, on peut hard-coder ces données, c’est-à-dire, on les écrit directement dans le script.
Le contenu du script pourrait ressembler à cela:
####### INPUT DATA #########
INPUTFILE = "inputfile.txt"
############################
do_something(INPUTFILE)
On peut aussi passer des arguments à la commande d’exécution
du script avec, par exemple, la commande
python parsefile.py inputfile.txt
. Ici, inputfile.txt
est un argument
supplémentaire qui sera utilisé par le script parsefile.py
. On peut récupérer
ce genre d’argument directement depuis le code avec le module sys
et son
attribut argv
. Cet attribut est une list
, son
deuxième élément contiendra "inputfile.txt"
.
Le contenu de ce script pourrait ressembler à cela:
import sys
# Lecture de l'argument passé à la ligne de commande
inputfile = sys.argv[1]
do_something(inputfile)
Depuis un fichier batch¶
L’exemple ci-dessous montre comment adapter un fichier batch (extension en .bat) pour qu’il active un environnement conda, exécute un script Python et désactive conda.
Une fois adapté (chemin vers le dossier d’installation de Miniconda, nom de l’environnement à activer et chemin du script à exécuter), on peut enregistrer ce fichier batch et l’exécuter directement depuis la Command Prompt (il n’est pas nécessaire de l’exécuter depuis l”Anaconda Prompt).
@echo off
rem Chemin ver le dossier racine de Miniconda
set CONDAPATH=C:\path\to\Miniconda
rem Nom de l'environnement à activer (e.g. gis, datascience, etc.)
set ENVNAME=parsefile_env
call %CONDAPATH%\Scripts\activate.bat %CONDAPATH%\envs\%ENVNAME%
python parsefile.py
call conda deactivate
rem Désactive l'environnement base
call conda deactivate
Exercices¶
Récupération d’informations dans un fichier et analyse¶
L’exercice proposée consiste à analyser un fichier log. Ce genre de fichier contient souvent beaucoup de données qui sont difficiles à lire et analyser directement. Le script à coder va s’occuper d’extraire le contenu du fichier et de l’enregistrer au format CSV. L’objectif est ensuite d’analyser plus en profondeur les données, pour afficher des informations, créer une figure et enregistrer des données transformées.
Le fichier log contient des données sur des culverts. Chaque culvert est décrit dans un bloc comme le suivant:
La première ligne marque le début du bloc de définition d’un culvert. Le nom de la branch à laquelle appartient le culvert défini est défini sur la deuxième ligne, le nom du culvert sur la troisième ligne. La quatrième ligne contient une liste de 6 paramètres du culvert: type, diameter, length, manning, upstream chainage, upstream invert. La cinquième ligne marque la fin du bloc de définition.
Le fichier log peut être téléchargé ici.
Deux scripts sont à créer. Le premier s’occupera de:
- lire le fichier log
- récupérer toutes les données de chaque culvert
- afficher le nombre de culverts que contient le fichier log
- enregistrer ces données dans un fichier au format CSV
Le deuxième script s’occupera de:
- lire le fichier CSV enregistré par le script précédent
- supprimer les données liés au paramètres manning
- convertir le paramètre diameter de m en mm
- afficher le diamètre moyen de tous les culvert
- afficher le diamètre moyen des culverts dont le type est concrete
- créer et enregistrer un camembert de la répartition des culverts suivant leur type
- déterminer pour chaque culvert son paramètre downstream chainage en fonction des paramètres upstream chainage et length
- enregistrer les données (incluant les données transformées et les données ajoutées) dans un fichier au format CSV
Une solution à l’exercice est proposée ici.
Ajout de texte à un fichier PDF¶
Ce deuxième exercice consiste à ajouter du texte sur chaque page d’un fichier PDF. Les données à rajouter sont lues depuis un fichier CSV, il s’agit d’un numéro et d’une description. Le package à utiliser pour effectuer l’opération sur le fichier PDF est PyMuPDF.
Le script à créer devra donc:
- lire le fichier CSV pour en extraire les données,
- enregistrer un nouveau fichier PDF agrémenté du texte à rajouter.
Les données nécessaires à l’exercice proposé sont disponibles ici:
- le fichier PDF est composé de trois pages qui affichent des formes différentes (rectangle, triangle et un cercle),
- le fichier CSV contient la description de ces formes et un identifiant.
L’objectif pratique est d’ajouter en haut de chaque page l’identifiant et la description de la forme.
Une solution à l’exercice est proposée ici.