@@ -6,6 +6,8 @@ Ce dépôt contient: | |||
* Un répertoire par saison, contenant à chaque fois les sources des présentations (dans un sous-répertoire 'sessions/'), et le code écrit pendant les ateliers (dans un sous-répertoire 'sources/') | |||
* Enfin, un répertoire cours/ contenant les sources du cours en ligne. | |||
# License | |||
Le contenu de ce dépôt (cours et code source) est mis à disposition gratuitement selon les termes de la [Licence Creative Commons Attribution 3.0 France](https://creativecommons.org/licenses/by/3.0/fr/). |
@@ -0,0 +1,2 @@ | |||
explication des exercices | |||
insérer exercices pour chaque épisode de la saison 2 |
@@ -0,0 +1,6 @@ | |||
--- | |||
title: "{{ replace .Name "-" " " | title }}" | |||
date: {{ .Date }} | |||
draft: true | |||
--- | |||
@@ -0,0 +1,49 @@ | |||
baseURL: https://dmerej.info/books/python | |||
title: Programmation en Python | |||
theme: book | |||
disableKinds: ['taxonomy', 'taxonomyTerm'] | |||
# Book configuration | |||
disablePathToLower: true | |||
enableGitInfo: true | |||
# Code highlight | |||
pygmentsStyle: native | |||
pygmentsCodeFences: true | |||
params: | |||
# (Optional, default 6) Set how many table of contents levels to be showed on page. | |||
# Use false to hide ToC, note that 0 will default to 6 (https://gohugo.io/functions/default/) | |||
# You can also specify this parameter per page in front matter | |||
BookToC: 3 | |||
# (Optional, default none) Set the path to a logo for the book. If the logo is | |||
# /static/logo.png then the path would be logo.png | |||
# BookLogo: /logo.png | |||
# (Optional, default none) Set leaf bundle to render as side menu | |||
# When not specified file structure and weights will be used | |||
# BookMenuBundle: /menu | |||
# (Optional, default docs) Specify section of content to render as menu | |||
# You can also set value to '*' to render all sections to menu | |||
BookSection: docs | |||
# Set source repository location. | |||
# Used for 'Last Modified' and 'Edit this page' links. | |||
BookRepo: ~ | |||
# Enable "Edit this page" links for 'doc' page type. | |||
# Disabled by default. Uncomment to enable. Requires 'BookRepo' param. | |||
# Path must point to 'content' directory of repo. | |||
BookEditPath: ~ | |||
# Configure the date format used on the pages | |||
# - In git information | |||
# - In blog posts | |||
BookDateFormat: '2 Jan 2006' | |||
# (Optional, default true) Enables search function with flexsearch, | |||
# Index is built on fly, therefore it might slowdown your website. | |||
BookSearch: false |
@@ -0,0 +1,18 @@ | |||
--- | |||
title: Introduction | |||
type: docs | |||
--- | |||
# Présentation de ce livre | |||
Bienvenue! | |||
Ce livre contient les supports de cours destinés aux élève de l'École du Logiciel Libre, | |||
où je donne des cours sur Python depuis 2018. | |||
Il est destiné à toute personne qui voudrait découvrir la programmation. | |||
J'espère qu'il vous plaira! Sinon, vous pouvez vous rendre sur [ma page de contact](https://dmerej.info/blog/fr/pages/about/) et | |||
me faire part de vos remarques. | |||
@@ -0,0 +1,20 @@ | |||
+++ | |||
title = "Objet de ce livre" | |||
weight = 1 | |||
+++ | |||
# Objet de ce livre | |||
Apprendre la programmation en partant de rien, en utilisant Python et la ligne de commande | |||
# Pourquoi Python? | |||
* Excellent langage pour débuter | |||
* Mon langage préféré | |||
* Vraiment cross-platform (sauf pour le mobile) | |||
# Pourquoi la ligne de commande? | |||
Interface universelle | |||
La plus simple à utiliser (en Python en tout cas) |
@@ -0,0 +1,54 @@ | |||
+++ | |||
title = "Le langage Python" | |||
weight = 3 | |||
+++ | |||
# Présentation du langage Python | |||
## Utilisation de Python | |||
* Aussi appelé "langage de script", `glue language` | |||
* Bon partout, excellent nulle part | |||
* Exemples d'utilisation: | |||
* Sciences (physique, chimie, linguistique ...) | |||
* Animation (Pixar, Disney ...) | |||
* Sites web (journaux, youtube, ...) | |||
* Ligne de commande | |||
* ... | |||
## Petit détour: version d'un programme | |||
* Comme les versions d'un document | |||
* Si le nombre est plus grand, c'est plus récent | |||
* Souvent en plusieurs morceaux: `1.3, 1.4, 3.2.5`. etc | |||
* Plus l'écart est grand, plus le programme a changé. | |||
* `3.2.5 -> 3.2.6`: pas grand-chose | |||
* `1.5.1 -> 4.3`: beaucoup de changements | |||
* On omet souvent le reste des numéros quand c'est pas nécessaire | |||
## Historique | |||
* Créé par Guido van Rossum. Conçu à la base pour l'enseignement. | |||
* Le nom vient des Monty Python (si, si) | |||
* Python 1: Sortie en 1991 | |||
* Python 2: en 2000 | |||
* Python 3: en 2008 | |||
## Le grand schisme | |||
La plupart des langages continuent à être compatibles d'une version à l'autre. | |||
*Ce n'est pas le cas pour Python3*, et ça a causé beaucoup de confusion et de débats. | |||
Heureusement, 10 ans plus tard, la situation s'est arrangée, et Python2 cessera d'être maintenu le premier janvier 2020. | |||
## Python3 | |||
Ce cours fonctionne donc uniquement avec Python3. | |||
N'utilisez *pas* Python2, sinon certaines choses expliquées ici ne marcheront pas :/ | |||
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 1 - Introduction" | |||
weight = 1 | |||
+++ |
@@ -0,0 +1,25 @@ | |||
+++ | |||
title = "Ligne de commande" | |||
weight = 2 | |||
+++ | |||
# La ligne de commande | |||
## Pourquoi la ligne de commande? | |||
* Très puissant | |||
* Ancien, mais toujours d'actualité | |||
* Indispensable dans de nombreux cas | |||
* Écrire des programmes qui marche dans la ligne de commande est (relativement) simple | |||
* Possibilités infines, même si on ne fait que manipuler du texte | |||
## Les bases | |||
On tape un commande, on appuie sur entrée, l'ordinateur interprète ce qui a été tapé et affiche un message: | |||
* `cd chemin/vers/fichier` | |||
* `ls` (ou `dir` sous Windows) | |||
* `pwd` | |||
* Le premier mot est une *commande*, les autres mots sont des *arguments* | |||
@@ -0,0 +1,35 @@ | |||
+++ | |||
title = "L'interpréteur interactif" | |||
weight = 4 | |||
+++ | |||
# L'interpréteur interactif | |||
## Installation | |||
Il se lance depuis l'invite de commande du système d'exploitation: | |||
``` | |||
$ python3 | |||
Python 3.7.1 (default, Oct 22 2018, 10:41:28) | |||
[GCC 8.2.1 20180831] on linux | |||
Type "help", "credits" or "license" for more information. | |||
>>> | |||
``` | |||
## Deux invites de commandes | |||
Notez les trois chevrons: `>>>`. Cela vous permet de différencier l'invite | |||
de commandes du système d'exploitation de celle de Python. | |||
* Système d'exploitation -> Python: taper `python3` (sans arguments) | |||
* Python -> Système d'exploitation: taper `quit()` | |||
## Note | |||
À partir de maintenant, recopiez les entrées sur les slides dans votre propre | |||
interpréteur. | |||
Vous devez taper la même chose après l'invite de commande ('>>>') et vous devez voir les mêmes réponses. | |||
Si ce n'est pas le cas, relisez attentitivement ce que vous avez tapé, sinon [contactez-moi](https://dmerej.info/blog/fr/pages/about/). |
@@ -0,0 +1,62 @@ | |||
+++ | |||
title = "Maths simples" | |||
weight = 5 | |||
+++ | |||
# Maths simples | |||
## Opérations avec des entiers | |||
``` | |||
>>> 1 | |||
1 | |||
>>> 2 | |||
2 | |||
>>> 1 + 2 | |||
3 | |||
>>> 2 * 3 | |||
6 | |||
``` | |||
## Opérantions avec des flottants | |||
C'est le `.` qui fait le flottant | |||
``` | |||
>>> 0.5 | |||
0.5 | |||
>>> 0.5 + 0.2 | |||
0.7 | |||
>>> 10 / 3 | |||
3.3333333333333335 | |||
``` | |||
*Note: les flottants sont imprécis* | |||
## Division entières et modulo | |||
``` | |||
>>> 14 // 3 | |||
4 | |||
>>> 14 % 3 | |||
2 | |||
``` | |||
*Le `%` n'a rien à voir avec un pourcentage!* | |||
## Priorité des opérations | |||
``` | |||
>>> 1 + 2 * 3 | |||
7 | |||
>>> (1 + 2) * 3 | |||
9 | |||
``` | |||
*Les parenthèses permettent de grouper les expressions* | |||
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 2 - Premiers pas" | |||
weight = 2 | |||
+++ |
@@ -0,0 +1,49 @@ | |||
+++ | |||
title = "Variables" | |||
weight = 6 | |||
+++ | |||
# Variables | |||
```python | |||
>>> a = 2 | |||
>>> a | |||
2 | |||
>>> b = 3 | |||
>>> a + b | |||
5 | |||
``` | |||
* On peut nommer des valeurs | |||
* On peut afficher la valeur d'une variable entrant son nom dans le REPL | |||
```python | |||
>>> a = 2 | |||
>>> a | |||
2 | |||
>>> a = 3 | |||
>>> a | |||
3 | |||
``` | |||
* On peut changer la valeur d'une variable (d'où son nom) | |||
* Quand Python essaie d'exécuter du code, il commence par remplacer les | |||
variables par leurs valeurs | |||
## Nom des variables | |||
Préférez des noms longs et descriptifs | |||
Toujours en minuscules | |||
Séparez les "mots" par des tirets bas (underscore) | |||
```python | |||
>>> score = 42 | |||
>>> age_moyen = 22 | |||
``` | |||
@@ -0,0 +1,60 @@ | |||
+++ | |||
title = "Chaînes de caractères" | |||
weight = 7 | |||
+++ | |||
# Chaînes de caractères | |||
Aussi appelées "string". | |||
Avec des simple quotes (`'`) | |||
```python | |||
>>> 'Bonjour monde!' | |||
'Bonjour monde!' | |||
``` | |||
Marche aussi avec des double quotes (`"`) | |||
```python | |||
>>> "Bonjour, monde!" | |||
'Bonjour monde' | |||
``` | |||
## Double et simple quotes | |||
On peut mettre des simples quotes dans des double quotes et vice-versa. | |||
```python | |||
>>> "Il a dit: 'bonjour' ce matin." | |||
"Il a dit: 'bonjour' ce matin." | |||
>>> 'Il a dit: "bonjour" ce matin' | |||
'Il a dit: "bonjour" ce matin!' | |||
``` | |||
## Échappement | |||
Avec la barre oblique inversée "backslash" | |||
```python | |||
>>> 'Il a dit: "bonjour". C\'est sympa!' | |||
'Il a dit: "bonjour". C\'est sympa!' | |||
``` | |||
## Concaténation | |||
```python | |||
>>> name = "John" | |||
>>> message = "Bonjour " + name + " !" | |||
>>> message | |||
"Bonjour John !" | |||
``` | |||
@@ -0,0 +1,39 @@ | |||
+++ | |||
title = "Types" | |||
weight = 8 | |||
+++ | |||
# Types | |||
```python | |||
>>> a = 42 | |||
>>> message = "La réponse est: " + a | |||
TypeError: can only concatenate str (not "int") to str | |||
``` | |||
*Notre premier message d'erreur !* | |||
On ne mélange pas les torchons et les serviettes! | |||
## Conversions | |||
### Entier vers string | |||
```python | |||
>>> a = 42 | |||
>>> message = "La réponse est: " + str(a) | |||
>>> message | |||
'La réponse est 42' | |||
``` | |||
### String vers entier | |||
```python | |||
>>> answer = int("42") | |||
>>> answer | |||
42 | |||
``` | |||
Notez les parenthèses autour des valeurs. | |||
@@ -0,0 +1,60 @@ | |||
+++ | |||
title = "Booléens et conditions" | |||
weight = 9 | |||
+++ | |||
# Booléens et conditions | |||
## True et False | |||
En Python ce sont des mots-clés et les valeurs sont en majuscules! | |||
## Assignation | |||
On peut assigner des variables aux valeurs True et False | |||
``` | |||
>>> la_terre_est_plate = False | |||
... | |||
>>> python_c_est_genial = True | |||
``` | |||
## Comparaisons | |||
``` | |||
>>> a = 2 | |||
>>> b = 3 | |||
>>> a > b | |||
False | |||
``` | |||
``` | |||
>>> 2 + 2 == 4 | |||
True | |||
``` | |||
Note: `==` pour la comparaison, `=` pour l'assignation | |||
``` | |||
>>> a = 2 | |||
>>> b = 3 | |||
>>> a != b | |||
True | |||
>>> 2 + 2 >= 4 | |||
True | |||
``` | |||
``` | |||
>>> a = 2 | |||
>>> a < 2 | |||
False | |||
>>> 1 < a < 3 | |||
True | |||
``` | |||
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 3 - Variables et types" | |||
weight = 3 | |||
+++ |
@@ -0,0 +1,107 @@ | |||
+++ | |||
title = "Code source" | |||
weight = 10 | |||
+++ | |||
# Code source | |||
## Non persistance des variables | |||
```python | |||
$ python3 | |||
>>> a = 2 | |||
>>> quit() | |||
``` | |||
```python | |||
$ python3 | |||
>>> a | |||
Traceback (most recent call last): | |||
File "<stdin>", line 1, in <module> | |||
NameError: name 'a' is not defined | |||
``` | |||
## Du code dans un fichier | |||
Aussi appelé: "code source", ou "source". | |||
L'essence du logiciel libre :) | |||
## Installation d'un éditeur de texte simple | |||
* Linux; `gedit`, `kate`, ... | |||
* macOS: `CotEditor` | |||
* Windows: `Notepad++` | |||
J'insiste sur **simple**. Inutile d'installer un IDE pour le moment. | |||
## Configuration | |||
* Police de caractères à chasse fixe | |||
* Indentation de *4 espaces* | |||
* Remplacer tabulation par des espaces | |||
* Activer la coloration syntaxique | |||
## Notre premier fichier source | |||
Insérez le code suivant dans votre éditeur de texte | |||
```python | |||
# Affiche un message | |||
print("Bonjour, monde") | |||
``` | |||
Sauvegardez dans un fichier `bonjour.py` dans `Documents/e2l/python` par exemple | |||
# Démonstration avec `kate` | |||
// TODO: conseiller un éditeur par plateforme | |||
C'est l'éditeur que nous utiliserons pour nos ateliers. | |||
* Pour l'environement KDE, mais ça marche bien sous Gnome aussi | |||
* Coloration syntaxique | |||
* Auto-complétion | |||
## Lancer du code en ligne de commande | |||
```text | |||
cd Documents/e2l/python/ | |||
python3 bonjour.py | |||
Bonjour, monde | |||
``` | |||
* Les lignes commençant par dièse (`#`) ont été ignorées - ce sont des *commentaires*. | |||
* `print()` affiche la valeur, comme dans le REPL. | |||
## Note importante | |||
Vous avez juste besoin: | |||
* d'un éditeur de texte | |||
* de Python3 installé | |||
* d'une ligne de commande | |||
Pas la peine d'installer quoique ce soit de plus pour le moment | |||
// TODO: dupliqué? | |||
1. *Recopiez* le code affiché dans votre éditeur, à la suite du code | |||
déjà écrit | |||
2. Lancez le code depuis la ligne de commande | |||
3. Réparez les erreurs éventuelles | |||
4. Recommencez |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 4 - code source" | |||
weight = 4 | |||
+++ |
@@ -0,0 +1,115 @@ | |||
+++ | |||
title = "Flôt de contrôle" | |||
weight = 11 | |||
+++ | |||
# Flot de contrôle | |||
L'essence de la programmation! | |||
## if | |||
```python | |||
a = 3 | |||
b = 4 | |||
if a == b: | |||
print("a et b sont égaux") | |||
print("on continue") | |||
``` | |||
Notes: | |||
* deux points à la fin de la ligne | |||
* indentation après les deux points | |||
* si la condition n'est pas vraie, rien ne se passe | |||
Notez qu'on peut mettre uniquement une variable ou une valeur | |||
après le if. Ceci ne fonctionne pas: | |||
```python | |||
if a = 3: | |||
print("a égale 3") | |||
``` | |||
et fait une erreur de syntaxe | |||
## if / else | |||
```python | |||
a = 3 | |||
b = 4 | |||
if a == b: | |||
print("a et b sont égaux") | |||
else: | |||
print("a et b sont différent") | |||
``` | |||
## if / elif | |||
```python | |||
if age < 10: | |||
print("inférieur à dix") | |||
elif 10 <= age < 20: | |||
print("âge entre 10 et 20") | |||
elif 20 <= age < 40: | |||
print("âge entre 20 et 40") | |||
else: | |||
print("âge supérieur à 40") | |||
``` | |||
On peut mettre autont de `elif` qu'on veut! | |||
Le derier `else` s'éxécute en dernier | |||
## while | |||
Répéter tant qu'une condition est vraie | |||
```python | |||
i = 0 | |||
while i < 3: | |||
print(i) | |||
i = i + 1 | |||
``` | |||
``` | |||
0 | |||
1 | |||
2 | |||
``` | |||
## Notre première boucle infinie | |||
```python | |||
while True: | |||
print("spam!") | |||
``` | |||
CTRL-C pour interrompre | |||
## Combiner while et if | |||
On peut "sortir" de la boucle `while` avec `break` | |||
```python | |||
i = 0 | |||
while True: | |||
i = i + 1 | |||
print(i) | |||
if i > 3: | |||
break | |||
``` | |||
``` | |||
1 | |||
2 | |||
3 | |||
4 | |||
``` |
@@ -0,0 +1,38 @@ | |||
+++ | |||
title = "Exercice" | |||
weight = 12 | |||
+++ | |||
# Exercice | |||
// TODO: explication des exercises | |||
## Lire une entrée utilisateur | |||
* `input()` (encore des parenthèses ...) | |||
* interrompt le script | |||
* lit ce que l'utilisateur tape jusqu'à ce qu'il tape "entrée". | |||
* renvoie une string | |||
## Le jeu | |||
On fait deviner un nombre à l'utilisateur, en affichant 'trop grand', 'trop petit' | |||
jusqu'à ce qu'il trouve la valeur exacte. | |||
## Squelette | |||
// TODO: | |||
* explication du Squelette | |||
* pas de solution! | |||
```python | |||
# faites moi confiance, les deux lignes ci-dessous | |||
# permettent de tirer un nombre au hasard entre 0 et 100 | |||
import random | |||
nombre = random.randint(0, 100) | |||
print("devine le nombre auquel je pense") | |||
# votre code ici | |||
``` |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 5 - Flot de contrôle" | |||
weight = 5 | |||
+++ |
@@ -0,0 +1,75 @@ | |||
+++ | |||
title = "Fonctions" | |||
weight = 1 | |||
+++ | |||
# Fonctions | |||
## Fonction sans argument | |||
Définition: | |||
```python | |||
def dire_bonjour(): | |||
print("Bonjour") | |||
``` | |||
* avec `def` | |||
* avec un `:` à la fin et un _bloc indenté_ (appelé le "corps") | |||
Appel: | |||
``` | |||
>>> dire_bonjour() | |||
Bonjour | |||
``` | |||
* avec le nom de la fonction et des parenthèses | |||
## Le pouvoir des fonctions | |||
Ici on vient de créer une nouvelle fonctionnalité | |||
à Python. Avant qu'on définisse la fonction | |||
`dire_bonjour()`, il ne savait pas dire bonjour, | |||
il savait uniquement afficher des messages à | |||
l'écran. | |||
On dit qu'on a _créé une abstraction_. Et | |||
c'est une technique extrêmement utile en | |||
programmation. | |||
## Fonction avec un argument | |||
Définition: avec l'argument à l'intérieur des parenthèses | |||
```python | |||
def dire_bonjour(prénom): | |||
print("Bonjour " + prénom) | |||
``` | |||
Appel: en passant une variable ou une valeur dans les parenthèses | |||
```python | |||
>>> dire_bonjour("Germaine") | |||
Bonjour Germaine | |||
>>> prénom_de_charlotte = "Charlotte" | |||
>>> dire_bonjour(prénom_de_charlotte) | |||
Bonjour Charlotte | |||
``` | |||
## Exécution d'une fonction | |||
C'est exatement comme si on assignait les arguments de la fonction avant d'éxécuter le code | |||
dans le corps | |||
```python | |||
# Ceci: | |||
dire_bonjour("Dimitri") | |||
# Est équivalent à cela: | |||
prénom_de_dimitri = "Dimitri" | |||
print("Bonjour " + prénom_de_dimitri) | |||
# Lui-même équivalent à: | |||
print("Bonjour " + "Dimitri") | |||
``` |
@@ -0,0 +1,39 @@ | |||
+++ | |||
title = "Portée des variables" | |||
weight = 2 | |||
+++ | |||
# Portée des variables | |||
Les arguments d'une fonction n'existent que dans le corps de celle-ci | |||
```python | |||
def dire_bonjour(prénom): | |||
print("Bonjour " + prénom) | |||
dire_bonjour("Dimitri") # Ok | |||
print(prénom) # Erreur | |||
``` | |||
Les variables en dehors des fonctions sont disponibles partout: | |||
```python | |||
salutation = "Bonjour " | |||
def dire_bonjour(prénom): | |||
print(salutation + prénom) | |||
dire_bonjour("Dimitri") | |||
``` | |||
Une variable peut avoir en "cacher" une autre si elle a une portée différente | |||
```python | |||
def dire_bonjour(prénom): | |||
print("Bonjour " + prénom) # portée: uniquement dans | |||
# le corps dire_bonjour | |||
prénom = "Dimitri" # portée: dans tout le programme | |||
dire_bonjour(prénom) # Ok | |||
``` |
@@ -0,0 +1,43 @@ | |||
+++ | |||
title = "Fonctions à plusieurs arguments" | |||
weight = 2 | |||
+++ | |||
# Fonctions à plusieurs arguments | |||
On peut mettre autant d'arguments qu'on veut, séparés | |||
par des virgules: | |||
```python | |||
def afficher_addition(x, y): | |||
résultat = x + y | |||
print(résultat) | |||
``` | |||
```python | |||
>>> a = 4 | |||
>>> b = 5 | |||
>>> afficher_addition(a, b) | |||
9 | |||
``` | |||
## Arguments nommés | |||
En Python, on peut aussi utiliser le *nom* des arguments au lieu de | |||
leur position: | |||
```python | |||
def dire_bonjour(prénom): | |||
print("Bonjour " + prénom) | |||
``` | |||
```python | |||
>>> dire_bonjour(prénom="Gertrude") | |||
Bonjour Gertrude | |||
>>> afficher_addition(y=3, x=4) | |||
7 | |||
``` | |||
// TODO: soustraction | |||
@@ -0,0 +1 @@ | |||
@@ -0,0 +1,28 @@ | |||
+++ | |||
title = "Arguments par défaut" | |||
weight = 4 | |||
+++ | |||
# Arguments par défaut | |||
On peut aussi mettre des valeurs par défaut: | |||
Définition: | |||
```python | |||
def dire_bonjour(prénom, enthousiaste=False): | |||
message = "Bonjour " + prénom | |||
if enthousiaste: | |||
message += "!" | |||
print(message) | |||
``` | |||
Appel: | |||
```python | |||
>>> dire_bonjour("Thomas", enthousiaste=True) | |||
Bonjour Thomas! | |||
>>> dire_bonjour("Thomas", enthousiaste=False) | |||
Bonjour Thomas | |||
>>> dire_bonjour("Thomas") | |||
Bonjour Thomas | |||
``` | |||
@@ -0,0 +1,46 @@ | |||
+++ | |||
title = "Fonctions natives" | |||
weight = 5 | |||
+++ | |||
# Fonctions natives | |||
Fonctions qui sont toujours présentes dans l'interpréteur. On en a déjà vu quelques unes: | |||
* `print`, `input`: écrire et lire sur la ligne de commande | |||
* `str`, `int`: convertir des entiers en strings et vice-versa | |||
Il y en a tout un tas! | |||
La liste ici: https://docs.python.org/fr/3/library/functions.html#built-in-funcs | |||
## Retour sur print | |||
On peut passer autant d'arguments qu'on veut à `print` et: | |||
* Il les sépare par des espaces | |||
* Ajoute un retour à la ligne à la fin: | |||
```python | |||
>>> prénom = "Charlotte" | |||
print("Bonjour", pŕenom) | |||
Bonjour Charlotte | |||
``` | |||
On peut demander à `print` de changer son séparateur: | |||
```python | |||
>>> a = "chauve" | |||
>>> b = "souris" | |||
>>> print(a, b, sep="-") | |||
chauve-souris | |||
``` | |||
Ou de changer le caractère de fin: | |||
```python | |||
>>> print("Ceci tient", end="") | |||
>>> print("sur une seule ligne") | |||
Ceci tient sur une seule ligne | |||
``` | |||
@@ -0,0 +1,43 @@ | |||
+++ | |||
title = "return" | |||
weight = 6 | |||
+++ | |||
# Valeur de retour d'une fonction | |||
Définition avec le mot `return` | |||
```python | |||
def additionner(x, y): | |||
return x + y | |||
``` | |||
Récupérer la valeur de retour | |||
```python | |||
>>> a = 3 | |||
>>> b = 4 | |||
>>> c = additionner(a, b) # encore une assignation | |||
>>> c | |||
7 | |||
``` | |||
# Sortir d'une fonction avec return | |||
`return` interrompt également l'éxécution du | |||
corps de la fonction: | |||
```python | |||
def dire_bonjour(prénom, première_fois=False): | |||
print("Bonjour", prénom) | |||
if not première_fois: | |||
return | |||
print("Heureux de faire votre connaissance") | |||
``` | |||
```python | |||
>>> dire_bonjour("Dimitri", première_fois=True) | |||
Bonjour Dimitri | |||
Heureux de faire votre connaissance | |||
>>> dire_bonjour("Dimitri", première_fois=False) | |||
Bonjour Dimitri | |||
``` |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 6 - Fonctions" | |||
weight = 6 | |||
+++ |
@@ -0,0 +1,164 @@ | |||
+++ | |||
title = "Listes" | |||
weight = 1 | |||
+++ | |||
# Listes | |||
// TODO: split in pages | |||
## Définition | |||
Une liste est une _suite ordonée_ d'éléments. | |||
## Créer une liste | |||
Avec `[]`, et les élements séparés par des virgules: | |||
```python | |||
liste_vide = [] | |||
trois_entiers = [1, 2, 3] | |||
``` | |||
## Listes hétérogènes | |||
On peut mettre des types différents dans la même liste | |||
```python | |||
ma_liste = [True, 2, "trois"] | |||
``` | |||
On peut aussi mettre des listes dans des listes: | |||
```python | |||
liste_de_listes = [[1, 2], ["Germaine", "Gertrude"]] | |||
``` | |||
## Connaître la taille d'une liste | |||
Avec `len()` - encore une fonction native | |||
```python | |||
>>> liste_vide = [] | |||
>>> len(liste_vide) | |||
0 | |||
>>> trois_entiers = [1, 2, 3] | |||
>>> len(trois_entiers) | |||
3 | |||
``` | |||
## Concaténation de listes | |||
Avec `+` | |||
```python | |||
>>> prénoms = ["Alice", "Bob"] | |||
>>> prénoms += ["Charlie", "Eve"] | |||
>>> prénoms | |||
['Alice', 'Bob', "Charlie", 'Eve'] | |||
``` | |||
On ne peut concaténer des listes que avec d'autres listes: | |||
```python | |||
>>> scores = [1, 2, 3] | |||
>>> scores += 4 # TypeError | |||
>>> scores += [4] # OK | |||
``` | |||
## Test d'appartenance | |||
Avec `in`: | |||
```python | |||
>>> prénoms = ["Alice", "Bob"] | |||
>>> "Alice" in prénoms | |||
True | |||
``` | |||
```python | |||
>>> prénoms = ["Alice", "Bob"] | |||
>>> "Charlie" in prénoms | |||
False | |||
``` | |||
## Itérer sur les élements d'une liste | |||
Avec `for ... in` | |||
```python | |||
prénoms = ["Alice", "Bob", "Charlie"] | |||
for prénom in prénoms: | |||
# La variable 'prénom" est assignée à chaque | |||
# élément de la liste | |||
print("Bonjour", prénom) | |||
Bonjour Alice | |||
Bonjour Bob | |||
Bonjour Charlie | |||
``` | |||
## Indéxer une liste | |||
* Avec `[]` et un entier | |||
* Les index valides vont de 0 à `n-1` où `n` est la | |||
taille de la liste. | |||
```python | |||
>>> fruits = ["pomme", "orange", "poire"] | |||
>>> fruits[0] | |||
"pomme" | |||
>>> fruits[1] | |||
"orange" | |||
>>> list[2] | |||
"poire" | |||
>>> fruits[3] # IndexError | |||
``` | |||
## Modifier une liste | |||
Encore une assignation: | |||
```python | |||
>>> fruits = ["pomme", "orange", "poire"] | |||
>>> fruits[0] = "abricot" | |||
>>> fruits | |||
["abricot", "orange", "poire"] | |||
``` | |||
## Les strings sont aussi des listes (presque) | |||
On peut itérer sur les caractères d'une string: | |||
```python | |||
for c in "vache": | |||
print(c) | |||
v | |||
a | |||
c | |||
h | |||
e | |||
``` | |||
On peut tester si un caractère est présent: | |||
```python | |||
>>> "e" in "vache" | |||
True | |||
>>> "x" in "vache" | |||
False | |||
``` | |||
Mais on neut peut pas modifier une string | |||
```python | |||
>>> prénom = "Charlotte" | |||
>>> prénom[0] | |||
"C" | |||
>>> prénom[3] | |||
"r" | |||
>>> prénom[0] = "X" # TypeError | |||
``` |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 7 - Listes" | |||
weight = 7 | |||
+++ |
@@ -0,0 +1,98 @@ | |||
+++ | |||
title = "None" | |||
weight = 1 | |||
+++ | |||
# None | |||
## Définition | |||
`None` est une "valeur magique" natif en Python. Il est toujours présent, et il est unique. | |||
Un peu comme `True` et `False` qui sont deux valeurs qui servent à représenter tous les booléens. | |||
## Représenter l'absence | |||
L'interpréteur intéractif n'affiche rien quand la valeur est None | |||
```python | |||
>>> a = 42 | |||
>>> a | |||
42 | |||
>>> b = None | |||
>>> b | |||
``` | |||
## Retourner None | |||
En réalité, *toutes* les fonctions pythons retournent *quelque chose*, même quand | |||
elle ne contiennent pas le mot-clé `return`. | |||
```python | |||
def ne_renvoie_rien(): | |||
print("je ne fais qu'afficher quelque chose") | |||
``` | |||
```python | |||
>>> resultat = ne_renvoie_rien() | |||
"je ne fais qu'afficher quelque chose" | |||
>>> resultat | |||
``` | |||
## Opérations avec None | |||
La plupart des fonctions que nous avons vues échouent si on leur passe None | |||
en argument: | |||
```python | |||
>>> len(None) | |||
TypeError: object of type 'NoneType' has no len() | |||
>>> None < 3 | |||
TypeError: '<' not supported between instances of | |||
'NoneType' and 'int' | |||
>>> int(None) | |||
TypeError: int() argument must be a string, | |||
a bytes-like object or a number, | |||
not 'NoneType' | |||
>>> str(None) | |||
'None' | |||
``` | |||
## Example d'utilisation: | |||
```python | |||
def trouve_dans_liste(valeur, liste): | |||
for element in liste: | |||
if element == valeur: | |||
return element | |||
return None | |||
``` | |||
```python | |||
>>> trouve_dans_liste(2, [1, 2, 3]) | |||
2 | |||
>>> trouve_dans_liste(False, [True, False]) | |||
False | |||
>>> trouve_dans_liste(1, [3, 4]) | |||
``` | |||
```python | |||
def trouve_dans_liste(liste, valeur): | |||
for element in liste: | |||
if element == valeur: | |||
return element | |||
``` | |||
None est Falsy, et on peut vérifier si une variable vaut None avec `is None` | |||
```python | |||
# hypothèse: `ma_valeur` n'est pas None | |||
mon_element = trouve_dans_liste(ma_valeur, ma_liste) | |||
if mon_element is None: | |||
print("élément absent de la liste") | |||
if not mon_element: | |||
# Peut-être que l'élément n'était pas dans la liste, | |||
# ou peut-être y était-il, mais avec une valeur falsy | |||
... | |||
``` |
@@ -0,0 +1,34 @@ | |||
+++ | |||
title = "pass" | |||
weight = 2 | |||
+++ | |||
# pass | |||
En Python, à cause de l'organisation en blocs indentés, on ne | |||
peut pas vraiment avoir de blocs vides. Mais parfois, on | |||
a besoin d'un bloc qui ne fasse rien. | |||
Dans ce cas, on peut utiliser le mot-clé `pass`, par exemple | |||
après un if: | |||
```python | |||
une_condition = False | |||
if une_condition: | |||
pass | |||
else: | |||
print("une_condition n'est pas vraie") | |||
``` | |||
On peut aussi - et c'est l'usage le plus courant - utiliser `pass` pour | |||
définir une fonction qui ne fait rien: | |||
```python | |||
def ne_fait_rien(): | |||
pass | |||
``` | |||
```python | |||
>>> ne_fait_rien() | |||
<rien> | |||
``` |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 8 - None et pass" | |||
weight = 8 | |||
+++ |
@@ -0,0 +1,205 @@ | |||
+++ | |||
title = "Dictionnaires" | |||
weight = 1 | |||
+++ | |||
# Dictionnaires | |||
## Définition | |||
Un dictionaire est une _association_ entre des clés et des valeurs. | |||
* Les clés sont uniques | |||
* Les valeurs sont arbitraires | |||
## Création de dictionnaires | |||
```python | |||
# dictionaire vide | |||
>>> {} | |||
# une clé, une valeur | |||
>>> {"a": 42} | |||
# deux clés, deux valeurs | |||
>>> {"a": 42, "b": 53} | |||
# les clés sont uniques: | |||
>>> {"a": 42, "a": 53} | |||
{"a": 53} | |||
``` | |||
Note: tous les dictionnaires sont truthy, sauf les dictionnaires vides. | |||
## Accès aux valeurs | |||
Avec `[]`, comme pour les listes, mais avec une *clé* à la place d'un *index*. | |||
```python | |||
>>> scores = {"john": 10, "bob": 42} | |||
>>> scores["john"] | |||
10 | |||
>>> scores["bob"] | |||
42 | |||
>>> scores["charlie"] | |||
KeyError | |||
``` | |||
## Test d'appartenance | |||
Avec `in`, comme le listes: | |||
```python | |||
>>> scores = {"john": 10, "bob": 42} | |||
>>> "charlie" in scores | |||
False | |||
``` | |||
## Modifier la valeur d'une clé | |||
Comme pour les listes: on assigne la nouvelle variable: | |||
```python | |||
>>> scores = {"john": 10, "bob": 42} | |||
>>> scores["john"] = 20 | |||
>>> scores | |||
{"john": 20, "bob": 42} | |||
``` | |||
## Créer une nouvelle clé | |||
Même méchanisme que pour la modification des clés existantes | |||
```python | |||
>>> scores = {"john": 10, "bob": 42} | |||
>>> scores["charlie"] = 30 | |||
>>> scores | |||
{"john": 20, "bob": 42, "charlie": 30} | |||
``` | |||
*rappel*: ceci ne fonctionne pas avec les listes! | |||
```python | |||
>>> ma_liste = ["a", "b"] | |||
>>> ma_liste[1] = "c" # ok | |||
["a", "c"] | |||
>>> ma_liste[3] = "d" | |||
IndexError | |||
``` | |||
## Itérer sur les clés | |||
Avec `for ... in ...`, comme pour les listes | |||
```python | |||
scores = {"john": 10, "bob": 42} | |||
for nom in scores: | |||
# `nom` est assigné à "john" puis "bob" | |||
score_associé_au_nom = scores[nom] | |||
print(nom, score_associé_au_nom) | |||
``` | |||
## Détruire une clé | |||
Avec `del` - un nouveau mot-clé: | |||
```python | |||
>>> scores = {"john": 10, "bob": 42} | |||
>>> del scores["bob"] | |||
>>> scores | |||
{"john": 10} | |||
``` | |||
## Détruire un élément d'une liste | |||
```python | |||
>>> fruits = ["pomme", "banane", "poire"] | |||
>>> del fruits[1] | |||
>>> fruits | |||
["pomme", "poire"] | |||
``` | |||
## Détruire une variable | |||
```python | |||
>>> mon_entier = 42 | |||
>>> mon_entier += 3 | |||
>>> mon_entier | |||
45 | |||
>>> del mon_entier | |||
>>> mon_entier == 45 | |||
NameError: name 'mon_entier' is not defined | |||
``` | |||
## Détruire une fonction | |||
On peu aussi supprimer des fonctions: | |||
```python | |||
def ma_fonction(): | |||
print("bonjour") | |||
del ma_fonction | |||
>>> ma_fonction() | |||
NameError: name 'ma_fonction' is not defined | |||
``` | |||
## Des dictionnaires partout | |||
Les variables globales d'un programme Python sont dans un dictionnaire, | |||
accessible avec la fonction native `globals()`: | |||
```python | |||
$ python3 | |||
>>> globals() | |||
{ | |||
... | |||
'__doc__': None, | |||
'__name__': '__main__', | |||
... | |||
} | |||
``` | |||
On reparlera de `__doc__` et `__name__` un autre jour ... | |||
```python | |||
$ python3 | |||
>>> a = 42 | |||
>>> globals() | |||
{ | |||
... | |||
'__doc__': None, | |||
'__name__': '__main__', | |||
... | |||
'a': 42 | |||
} | |||
``` | |||
```python | |||
$ python3 | |||
>>> a = 42 | |||
>>> del globals()["a"] | |||
>>> a | |||
NameError: name 'a' is not defined | |||
``` | |||
On peut accéder aux variables locales d'une fonction avec `locals()` | |||
```python | |||
def ma_fonction(): | |||
a = 42 | |||
b = 3 | |||
c = a + b | |||
print(locals()) | |||
>>> ma_fonction() | |||
{'a': 42, 'b': 3, 'c': 45} | |||
``` | |||
En revanche, il n'est pas conseillé de modifier le dictionaire renvoyé par `locals()` ... | |||
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 9 - Dictionnaires" | |||
weight = 9 | |||
+++ |
@@ -0,0 +1,169 @@ | |||
+++ | |||
title = "Tuples" | |||
weight = 1 | |||
+++ | |||
# Tuples | |||
## Définition | |||
Un tuple est un ensemble *ordonné* et *immuable* d'éléments. Le nombre, l'ordre et la valeur des éléments sont fixes. | |||
## Création de tuples | |||
```python | |||
# Un tuple vide | |||
() | |||
# Un tuple à un élément | |||
(1,) # notez la virgule | |||
# Un tuple à deux éléments, aussi appelé couple | |||
(1, 2) | |||
``` | |||
Sauf pour le tuple vide, c'est la *virgule* qui fait le tuple | |||
Note: tous les tuples sont truthy, sauf les tuples vides. | |||
# Tuples hétérogènes | |||
Comme les listes, les tuples peuvent contenir des éléments de types différents: | |||
```python | |||
# Un entier et une string | |||
mon_tuple = (42, "bonjour") | |||
# Un entier et un autre tuple | |||
mon_tuple = (21, (True, "au revoir")) | |||
``` | |||
## Accès | |||
Avec `[]` et l'index de l'élément dans le tuple: | |||
```python | |||
mon_tuple = (42, "bonjour") | |||
mon_tuple[0] | |||
42 | |||
mon_tuple[1] | |||
"bonjour" | |||
``` | |||
## Modification | |||
Interdit! | |||
```python | |||
mon_tuple = (42, "bonjour") | |||
mon_tuple[0] = 44 | |||
TypeError: 'tuple' object does not support item assignment | |||
``` | |||
## Test d'appartenance | |||
Avec `in` | |||
```python | |||
>>> mon_tuple = (42, 14) | |||
>>> 42 in mon_tuple | |||
True | |||
>>> 14 in mon_tuple | |||
True | |||
>>> 13 in mon_tuple | |||
False | |||
``` | |||
## Déstructuration | |||
Créer plusieurs variables en une seule ligne: | |||
```python | |||
>>> couple = ("Batman", "Robin") | |||
>>> héros, side_kick = couple | |||
>>> héros | |||
'Batman' | |||
>>> side_kick | |||
'Robin' | |||
``` | |||
## Quelques erreurs classiques | |||
```python | |||
>>> héros, side_kick, ennemi = couple | |||
ValueError (3 != 2) | |||
>>> (héros,) = couple | |||
ValueError (1 != 2) | |||
# Gare à la virgule: | |||
>>> héros, = couple | |||
ValueError (1 != 2) | |||
``` | |||
## Pièges | |||
```python | |||
f(a, b, c) # appelle f() avec trois arguments | |||
f((a, b, c)) # appelle f() avec un seul argument | |||
# (qui est lui-même un tuple à 3 valeurs) | |||
f(()) # appelle f() avec un tuple vide | |||
(a) # juste la valeur de a entre parenthèses | |||
(a,) # un tuple à un élément, qui vaut la valeur de a | |||
``` | |||
## On peut aussi déstructurer des listes | |||
```python | |||
>>> fruits = ["pomme", "banane", "orange"] | |||
>>> premier, deuxième, troisième = fruits | |||
>>> premier | |||
"pomme" | |||
>>> deuxième | |||
"banane" | |||
>>> troisième | |||
"orange" | |||
``` | |||
On dit aussi: unpacking | |||
## Utilisations des tuples | |||
Pour simplifier des conditions: | |||
```python | |||
# Avant | |||
if ( | |||
ma_valeur == "nord" or | |||
ma_valeur == "sud" or | |||
ma_valeur == "ouest" or | |||
ma_valeur == "est"): | |||
print("direction", ma_valeur) | |||
``` | |||
```python | |||
# Après | |||
if ma_valeur in ("nord", "sud", "est", "ouest"): | |||
print("direction", ma_valeur) | |||
``` | |||
## Pour retourner plusieurs valeurs | |||
```python | |||
def tire_carte(): | |||
valeur = "10" | |||
couleur = "trèfle" | |||
return (valeur, couleur) | |||
v, c = tire_carte() | |||
print(v, "de", c) | |||
# 10 de trèfle | |||
``` | |||
Ce n'est pas une nouvelle syntaxe, juste de la manipulation de tuples! |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 10 - tuples" | |||
weight = 10 | |||
+++ |
@@ -0,0 +1,319 @@ | |||
+++ | |||
title = "Classes" | |||
weight = 1 | |||
+++ | |||
# Classes | |||
Ce qu’on a vu jusqu’ici: | |||
* Des types simples (entiers, booléens, ...) | |||
* Des structures de données (listes, dictionnaires, ...) | |||
* Des fonctions qui manipulent ces types ou ces types | |||
* Des fonctions qui s’appellent les unes les autres | |||
On appelle cet ensemble de concepts, cette façon d'écrire du code, un *paradigme* - | |||
et c'est un paradigme *procédural*. | |||
On va passer à un autre paradigme: l'*orienté objet*. | |||
## Orienté objet - une première définition | |||
Un "objet" informatique *représente* un véritable "objet" physique | |||
dans le vrai monde véritable. | |||
Ce n'est pas une très bonne définition: | |||
1. Ce n'est pas nécessaire | |||
2. Ce n'est même pas forcément souhaitable! | |||
Je le mentionne juste parce que c'est une idée reçue très répandue. | |||
## Orienté objet - 2ème définition | |||
Une meilleure définition, c'est de dire que la programmation | |||
orientée objet permet de mettre au même endroit: | |||
* des données | |||
* des fonctions qui opèrent sur ces données | |||
L'important c'est que les deux aillent ensemble! | |||
*Note: ce n'est pas **la** meilleure définition de l'orienté objet, mais on s'en contentera | |||
pour le moment ...* | |||
## Les classes | |||
On va parler *d'une* façon de faire de l'orienté objet: avec des classes. | |||
Mais notez bien qu'on peut faire de l'orienté objet *sans* classes! | |||
## Le plan de construction | |||
On dit souvent qu'en Python, "tout est objet". | |||
Pour bien comprendre cela, il faut d'abord parler des *classes* et des *instances de classes*. | |||
Une classe est un *plan de construction*, et est définie ainsi: | |||
```python | |||
class MaClasse: | |||
# du code ici | |||
``` | |||
Comme les fonctions, les classes contienent un *corps*, qui est le bloc *identé* en dessous | |||
du mot-clé `class`, de nom de la classe et du `:` en fin de ligne. | |||
Les classes sont utilisées pour construire des *instances*. | |||
## Créons des instances | |||
On peut faire un plan de construction vide avec le mot clé pass: | |||
```python | |||
class MaClasse: | |||
pass | |||
``` | |||
Dans ce cas, on crée une instance en mettant le nom de la classe suivi d'une paire de parenthèses - | |||
un peu comme pour appeler une fonction: | |||
```python | |||
>>> mon_instance = MaClasse() | |||
``` | |||
Ici, `mon_instance` est une *instance* de la classe `MaClasse`. | |||
## Attributs | |||
Les attributs sont des éléments **nommés** à *l'intérieur* d'une instance. | |||
On peut y accéder avec la syntaxe `<instance>.<attribut>`: | |||
```python | |||
y = a.x | |||
``` | |||
Ici, `y` est l'attribut `x` de l'instance `a`. | |||
Les attributs peuvent être des fonctions: | |||
```python | |||
func = a.x | |||
func(10) | |||
``` | |||
Ici, on crée une variable `func` qui prend la valeur de l'attribut `x` dans l'instance `a`, puis | |||
on l'appelle avec l'argument `10` à la ligne suivante. | |||
Le code suivant fait exactement la même chose, mais avec une ligne de moins: | |||
```python | |||
a.x(10) | |||
``` | |||
On peut *créer* des attributs dans *n'importe quel instance*, en utilisant l'*assignation*: | |||
```python | |||
>>> mon_instance = MaClasse() | |||
# Création de l'attribut `x` dans `mon_instance` | |||
>>> mon_instance.x = 42 | |||
# Accés à l'attribut `x` dans `mon_instance` | |||
>>> mon_instance.mon_attribut | |||
42 | |||
``` | |||
## Méthodes - définition | |||
On peut aussi mettre des *méthodes* dans des classes. | |||
On utilise `def`, comme pour les fonctions, mais les méthodes *doivent* avoir au | |||
moins un argument appelé `self`, et être à l'intérieur du bloc de la classe: | |||
```python | |||
class MaClasse: | |||
def ma_méthode(self): | |||
return 42 | |||
``` | |||
## Méthodes - appel | |||
Une méthode ne peut être appelée que depuis une *instance* de | |||
la classe: | |||
```python | |||
class MaClasse: | |||
def ma_méthode(self): | |||
return 42 | |||
>>> ma_méthode() | |||
Erreur | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.ma_méthode() | |||
42 | |||
``` | |||
Notez qu'on ne passe *pas* d'argument quand on apelle `ma_méthode` depuis l'instance. | |||
## Méthodes et attributs | |||
`self` *prend la valeur de l'instance courante* quand la méthode est appelée. | |||
On peut le voir en utilisant des attributs: | |||
```python | |||
class MaClasse: | |||
def affiche_attribut_x(self): | |||
# Accès à l'attribut `x` dans `self` | |||
print(self.x) | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.x = 42 | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
``` | |||
On peut aussi *créer* des attributs dans une méthode: | |||
```python | |||
class MaClasse: | |||
def crée_attribut_x(self): | |||
self.x = 42 | |||
def affiche_attribut_x(self): | |||
print(self.x) | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.affiche_attribut_x() | |||
# Erreur: `mon_instance` n'a pas d'attribut `x` | |||
>>> mon_instance.crée_attribut_x() | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
``` | |||
Les méthodes peuveunt aussi prendre plusieurs arguments, en plus de `self` - mais `self` doit | |||
toujours être le premier argument. | |||
Par example, pour créer un attribut avec une certaine valeur: | |||
```python | |||
class MaClasse | |||
def crée_attribut_x(self, valeur_de_x): | |||
self.x = valeur_de_x | |||
def affiche_attribut_x(self); | |||
print(self.x) | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.crée_attribut_x(42) | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
``` | |||
## Méthodes appelant d'autres méthodes | |||
Comme les méthodes sont *aussi* des attributs, les méthodes d'une instance peuvent s'appeler | |||
les unes les autres: | |||
```python | |||
class MaClasse: | |||
def méthode_1(self): | |||
print("démarrage de la méthode 1") | |||
print("la méthode 1 affiche bonjour") | |||
print("bonjour") | |||
print("fin de la méthode 1") | |||
def méthode_2(self): | |||
print("la méthode 2 appelle la méthode 1") | |||
self.méthode_1() | |||
print("fin de la méthode 2") | |||
``` | |||
```python | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.méthode_2() | |||
``` | |||
```text | |||
la méthode 2 appelle la méthode 1 | |||
démarrage de la méthode 1 | |||
la méthode 1 affiche bonjour | |||
bonjour | |||
fin de la méthode 1 | |||
fin de la méthode 2 | |||
``` | |||
## Une méthode spéciale | |||
Si vous définissez une méthode `__init__`, celle-ci est appelée *automatiquement* | |||
quand l'instance est construite. | |||
On dit que c'est une méthode "magique" parce qu'elle fait quelque chose _sans_ qu'on | |||
l'appelle explicitement. | |||
On utilise souvent `__init__` pour créer des attributs | |||
```python | |||
class MaClasse: | |||
def __init__(self): | |||
self.x = 1 | |||
self.y = 2 | |||
>>> mon_instance = MaClasse() | |||
# __init__ est appelée automatiquement! | |||
>>> mon_instance.x | |||
1 | |||
>>> mon_instance.y | |||
2 | |||
``` | |||
On prend souvent les *valeurs* des attributs à créer en arguments de la méthode `__init__ `. | |||
```python | |||
class MaClasse: | |||
def __init__(self, x, y): | |||
self.x = x | |||
self.y = y | |||
``` | |||
Dans ce cas, les arguments de la méthode `__init__` apparaissent à l'intérieur des parenthèses après le | |||
nom de la classe: | |||
``` | |||
>>> mon_instance = MaClasse(3, 4) | |||
>>> mon_instance.x | |||
3 | |||
>>> mon_instance.y | |||
4 | |||
``` | |||
*Pour cette raison, `__init__` est souvent appelé le _constructeur_ de la classe.* | |||
## Récapitulatif | |||
* Classe: plan de construction | |||
* Instance: valeur issue d'une classe | |||
* Attribut: variable dans une instance | |||
* Méthode: fonction dans une instance (qui prend `self` en premier argument) | |||
* `__init__`: méthode magique appelée automatiquement pendant l'instanciation | |||
## Classes et programmation orienté objet | |||
Ainsi, on peut ranger au même endroit des données et des fonctions opérant sur ces données. | |||
Les données sont les attributs, et les fonctions opérant sur ces attributs sont les méthodes. | |||
On peut ainsi séparer les *responsabilités* à l'intérieur d'un code en les répartissant | |||
entres plusieurs classes. |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 11 - Classes (1ère partie)" | |||
weight = 11 | |||
+++ |
@@ -0,0 +1,148 @@ | |||
+++ | |||
title = "Modules" | |||
weight = 1 | |||
+++ | |||
# Modules | |||
## Un fichier = un module | |||
Et oui, vous faites des modules sans le savoir depuis le début :) | |||
Un fichier `foo.py` correspond *toujours* module `foo` | |||
**Attention: Ce n'est pas tout à fait réciproque. Le module `foo` peut venir d'autre chose | |||
qu'un fichier foo.py.** | |||
# Importer un module | |||
Ou: accéder à du code provenant d'un *autre* fichier source. | |||
Imaginons un fichier `bonjour.py` contenant seulement une assignation | |||
d'une variable `a` à l'entier 42 : | |||
```python | |||
# Dans bonjour.py | |||
a = 42 | |||
``` | |||
On peut accéder à cette variable en important le module, par | |||
exemple depuis l'interpréteur, en utilisant le mot-clé `import` | |||
suivi du nom du module: | |||
```python | |||
$ python | |||
>>> import bonjour | |||
>>> bonjour.a | |||
42 | |||
``` | |||
Notez que pour que cela fonctionne: | |||
* Le nom du module est écrit directement, ce n'est *pas* une | |||
chaîne de caractères. | |||
* Il faut lancer la commande `python` sans argument | |||
* Il faut la lancer depuis le répertoire qui contient `bonjour.py`. | |||
On voit que l'assignation de la variable `a` dans `bonjour.py` est devenue | |||
un *attribut* du module `bonjour` lorsque `bonjour` a été importé | |||
\newpage | |||
Si maintenant on rajoute une fonction `dire_bonjour` dans `bonjour.py`: | |||
```python | |||
# toujours dans bonjour.py | |||
a = 42 | |||
def dire_bonjour(): | |||
print("Bonjour!") | |||
``` | |||
On peut appeler la fonction `dire_bonjour` depuis l'interpréteur en accédant | |||
à l'attribut `dire_bonjour` du module `bonjour`: | |||
```python | |||
>>> import bonjour | |||
>>> bonjour.dire_bonjour() | |||
Bonjour! | |||
``` | |||
## Différence avec la commande python | |||
Notez bien que lancer l'interpréteur et taper `import bonjour` dedans n'est pas | |||
la même chose que lancer `python bonjour.py`. | |||
Dans le deuxième cas, tout le code dans `bonjour.py` est exécuté, puis la commande python | |||
se termine. | |||
Dans le cas de l'interpréteur, on peut utiliser tous les attributs du module et appeler | |||
les fonctions autant de fois qu'on veut: | |||
```python | |||
>>> import bonjour | |||
>>> bonjour.dire_bonjour() | |||
Bonjour! | |||
>>> bonjour.dire_bonjour() | |||
Bonjour! | |||
``` | |||
On peut aussi modifier les valeurs des attributs: | |||
```python | |||
>>> import bonjour | |||
>>> bonjour.a | |||
42 | |||
>>> bonjour.a = 36 | |||
>>> bonjour.a | |||
36 | |||
``` | |||
## Les imports ne sont faits qu'une seule fois | |||
Il est important de noter que le code à l'intérieur d'un | |||
module n'est *pas* ré-éxécuté si le module a déjà été | |||
importé auparavant. | |||
On peut le voir en mettant du code dans `bonjour.py`, | |||
en plus des simples définitions de fonctions et assignations | |||
de variables | |||
```python | |||
# Dans bonjour.py | |||
print("Je suis le module bonjour et tu viens de m’importer") | |||
``` | |||
```python | |||
>>> import bonjour | |||
Je suis le module foo et tu viens de m’importer | |||
>>> import bonjour | |||
<rien> | |||
``` | |||
Il faudra donc redémarrer l'interpréteur à chaque fois que le code dans `bonjour.py` change. | |||
## La bibliothèque standard | |||
La bibliothèque standard est une collection de modules directement utilisables fournis à l'installation de Python. | |||
Exemple: `sys`, `random`, ... | |||
Toute la bibliothèque standard est documentée - et la traduction en Français est en cours: | |||
https://docs.python.org/fr/3/library/index.html | |||
Mettez ce lien dans vos favoris - il vous sera très utile. | |||
## Quelques exemples de modules de la bibliothèque standard | |||
### Easter eggs | |||
(Ou fonctionnalités cachées) | |||
* `import antigravity` | |||
* `import this` | |||
Je vous laisse découvrir ce que fait le premier. Quant au deuxième, il contient | |||
une liste de préceptes que la plupart des développeurs Python s'efforcent de | |||
respecter. On en reparlera ... |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 12 - Modules - 1ère partie" | |||
weight = 12 | |||
+++ |
@@ -0,0 +1,186 @@ | |||
+++ | |||
title = "Rappels" | |||
weight = 1 | |||
+++ | |||
# Rappels | |||
_Note: ceci est surtout un rappel du chapitre 11. N'hésitez pas à vous y | |||
reporter si les exemples de code ne vous paraissent pas clairs._ | |||
## Classes vides | |||
Définition: | |||
```python | |||
class MaClasse: | |||
pass | |||
``` | |||
Instanciation: | |||
```python | |||
>>> instance_1 = MaClasse() | |||
``` | |||
## Attributs | |||
Un attribut est une variable _à l'intérieur_ d'autre chose (par exemple une instance de classe). | |||
La syntaxe consiste en l'instance à gauche et l'attribut à droite après un point: | |||
```python | |||
>>> mon_instance = MaClasse() | |||
# création de l'attribut `x` dans mon_instance: | |||
>>> mon_instance.x = 42 | |||
# accès à l'attribut `x` dans mon_instance: | |||
>>> mon_instance.x | |||
42 | |||
``` | |||
## Méthodes | |||
Une méthode est une fonction définie à l'intérieur d'une classe: | |||
Définition: | |||
```python | |||
class MaClasse: | |||
def ma_méthode(self): | |||
return 42 | |||
``` | |||
Les méthodes sont des attributs des instances de classes: | |||
```python | |||
class MaClasse: | |||
def ma_méthode(self): | |||
return 42 | |||
>>> ma_méthode() | |||
Erreur | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.ma_méthode() | |||
42 | |||
``` | |||
## self | |||
`self` *prend la valeur de l'instance courante* quand la méthode est appelée. | |||
```python | |||
class MaClasse: | |||
def affiche_attribut_x(self): | |||
print(self.x) | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.x = 42 | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
``` | |||
On peut aussi *créer* des attributs dans une méthode: | |||
```python | |||
class MaClasse: | |||
def crée_attribut_x(self): | |||
self.x = 42 | |||
def affiche_attribut_x(self): | |||
print(self.x) | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.affiche_attribut_x() | |||
# Erreur: `mon_instance` n'a pas d'attribut `x` | |||
>>> mon_instance.crée_attribut_x() | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
``` | |||
## Méthodes avec arguments | |||
```python | |||
class MaClasse | |||
def crée_attribut_x(self, valeur_de_x): | |||
self.x = valeur_de_x | |||
def affiche_attribut_x(self); | |||
print(self.x) | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.crée_attribut_x(42) | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
``` | |||
## Méthodes appelant d'autres méthodes | |||
```python | |||
class MaClasse: | |||
def méthode_1(self): | |||
print("démarrage de la méthode 1") | |||
print("la méthode 1 affiche bonjour") | |||
print("bonjour") | |||
print("fin de la méthode 1") | |||
def méthode_2(self): | |||
print("la méthode 2 appelle la méthode 1") | |||
self.méthode_1() | |||
print("fin de la méthode 2") | |||
``` | |||
```python | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.méthode_2() | |||
``` | |||
```text | |||
la méthode 2 appelle la méthode 1 | |||
démarrage de la méthode 1 | |||
la méthode 1 affiche bonjour | |||
bonjour | |||
fin de la méthode 1 | |||
fin de la méthode 2 | |||
``` | |||
## Constructeur sans arguments | |||
Un constructeur en Python désigne la méthode nomée `__init__`, | |||
quand celle-ci existe. | |||
La méthode `__init__` est appelée automatiquement quand la | |||
classe est instanciée: | |||
```python | |||
class MaClasse: | |||
def __init__(self): | |||
self.x = 1 | |||
self.y = 2 | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.x | |||
1 | |||
>>> mon_instance.y | |||
2 | |||
``` | |||
## Constructeur avec arguments | |||
La méthode `__init__` peut avoir des arguments, | |||
dans ce cas, ceux ci doivent être fournis | |||
lors de l'instanciation: | |||
```python | |||
class MaClasse: | |||
def __init__(self, x, y): | |||
self.x = x | |||
self.y = y | |||
``` | |||
```python | |||
>>> mon_instance = MaClasse(3, 4) | |||
>>> mon_instance.x | |||
3 | |||
>>> mon_instance.y | |||
4 | |||
``` |
@@ -0,0 +1,91 @@ | |||
+++ | |||
title = "Couplage" | |||
weight = 2 | |||
+++ | |||
# Couplage | |||
## Définition | |||
Un couplage décrit une relation entre deux classes. | |||
## Exemple | |||
Ici on veut représenter des chats et des humains qui adoptent (on non) des chats. | |||
Tous les chats ont un nom, et tous les humains ont un prénom. | |||
On peut utiliser pour cela deux classes: `Chat` et `Humain`: | |||
```python | |||
class Chat: | |||
def __init__(self, nom): | |||
self.nom = nom | |||
>>> chat = Chat("Monsieur Moustaches") | |||
>>> chat.nom | |||
'Monsieur Moustaches' | |||
``` | |||
```python | |||
class Humain: | |||
def __init__(self, prénom): | |||
self.prénom = prénom | |||
>>> alice = Humain(prénom="Alice") | |||
>>> alice.prénom | |||
"Alice" | |||
``` | |||
Maintenant on veut que les humains puissent adopter des chats. | |||
Pour cela, on peut rajouter la méthode `adopte` dans la classe | |||
`Humain`. | |||
Cette méthode va prendre un argument - une instance de la | |||
classe `Chat`: | |||
```python | |||
class Humain: | |||
def __init__(self, prénom): | |||
self.prénom = prénom | |||
def adopte(self, chat): | |||
print(self.prénom, "adopte un chat") | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Humain("Alice") | |||
>>> alice.adopte(boule_de_poils) | |||
"Alice adopte un chat" | |||
``` | |||
On peut accéder au nom du chat depuis la méthode `adopte`, | |||
en utilisant la syntaxe `nom.attribut` vue précédemment: | |||
```python | |||
class Humain: | |||
def __init__(self, prénom): | |||
self.prénom = prénom | |||
def adopte(self, chat): | |||
print(self.prénom, "adopte", chat.nom) | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Humain("Alice") | |||
>>> alice.adopte(boule_de_poils) | |||
"Alice adopte Boule de Poils" | |||
``` | |||
## Couplage | |||
```python | |||
class Humain: | |||
... | |||
def adopte(self, chat): | |||
print(self.prénom, "adopte", chat.nom) | |||
``` | |||
Notez également que nous avons écrit `chat.nom`. ainsi, la méthode `adopte()` | |||
ne peut être appelée que part une instance qui a un attribut `nom` - sinon | |||
on aura une erreur. | |||
Donc si on modifie la classe `Chat` et qu'on renomme l'attribut `nom` en `surnom` par exemple, | |||
la méthode `adopte()` de la classe `Humain` cessera de fonctionner: on dit | |||
qu'on a un *couplage* entre les classes `Chat` et `Humain`. |
@@ -0,0 +1,117 @@ | |||
+++ | |||
title = "Composition" | |||
weight = 3 | |||
+++ | |||
# Composition | |||
## Définition | |||
Une classe à l'intérieur d'une autre classe. | |||
## Dépendances entre fonctions | |||
Exemple: on veut dessiner un sapin dans le terminal: | |||
```python | |||
def main(): | |||
largeur = demander_largeur() | |||
dessine_sapin(largeur) | |||
main() | |||
``` | |||
On voit que la fonction `dessine_sapin()` prend un argument `largeur`, qui est retourné | |||
par la fonction `demander_largeur()`. | |||
`dessine_sapin()` doit donc être appelée *après* `demander_largeur()`. On dit que `dessine_sapin()` | |||
_dépend_ de `demander_largeur()`. | |||
## Dépendances entre classes | |||
Un bon moyen d'introduire une dépendance entre deux classes est d'utiliser les constructeurs. | |||
Revoyons la classe Chat: | |||
```python | |||
class Chat: | |||
def __init__(self, nom): | |||
self.nom = nome | |||
``` | |||
Comme le constructeur de la classe Chat prend un nom en argument, il est impossible de construire | |||
des chats sans nom: | |||
```python | |||
>>> chat = Chat() | |||
TypeError: __init__() missing 1 required positional argument: 'nom' | |||
``` | |||
De la même façon, si on veut que tous les enfants aient un chat (pourquoi pas, après tout), on peut | |||
avoir une classe Enfant, dont le constructeur prend une instance de chat en plus du prénom: | |||
```python | |||
class Enfant: | |||
def __init__(self, prénom, chat): | |||
self.prénom = prénom | |||
self.chat = chat | |||
>>> alice = Enfant("Alice") | |||
TypeError: __init__() missing 1 required positional argument: 'chat' | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Enfant("Alice", boule_de_poils) | |||
# OK! | |||
``` | |||
## Utilisation de la composition | |||
Maintenant qu'on vit dans un monde où tous les enfants ont chacun un chat, on peut | |||
par exemple consoler tous les enfants en leur demandant de caresser leur chat, chat | |||
qui va ronronner et faire plaisir à son propriétaire. | |||
Voici comment on peut coder cela: d'abord, on rajoute les méthodes `caresse()` | |||
et `ronronne()` dans la classe Chat: | |||
```python | |||
class Chat: | |||
def __init__(self, nom): | |||
self.nom = nom | |||
def ronronne(self): | |||
print(self.nom, 'fait: "prrrrr"') | |||
def caresse(self): | |||
self.ronronne() | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> boule_de_poils.caresse() | |||
Boule de Poils fait "prrrrr" | |||
``` | |||
Ensuite, on peut rajouter la méthode `console()` dans la classe Enfant, | |||
qui va: | |||
* récupérer l'instance de la classe Chat dans `self` - comme n'importe quel attribut | |||
* puis appeler la méthode `caresse()` de cette instance | |||
```python | |||
class Enfant: | |||
def __init__(self, prénom, chat): | |||
self.chat = chat | |||
def console(self): | |||
self.chat.caresse() | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Enfant("Alice", boule_de_poils) | |||
# Alice est triste, on la console | |||
>>> alice.console() | |||
Boule de Poils fait "prrrrr" | |||
# Alice est consolée :) | |||
``` | |||
On dit parfois qu'on a *délégué* l'implémentation de la méthode `console()` de la classe Enfant | |||
à la méthode `caresse()` de la classe Chat. |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 13 - Classes (2ème partie)" | |||
weight = 13 | |||
+++ |
@@ -0,0 +1,42 @@ | |||
+++ | |||
title = "Introduction" | |||
weight = 1 | |||
+++ | |||
# Introduction | |||
## Importer un module | |||
Souvenez-vous, dans le chapitre 12 nous avons vu que le code suivant | |||
Ce code fonctionne s'il y a un ficher `foo.py` quelque part qui contient la fonction | |||
`bar` [^1]: | |||
```python | |||
import foo | |||
foo.bar() | |||
``` | |||
Ce fichier peut être présent soit dans le répertoire courant, soit dans la bibliothèque standard Python. | |||
## La variable PATH | |||
Vous connaissez peut-être le rôle de la variable d'environnement `PATH`. Celle-ci contient une liste de chemins, | |||
séparés par le caractère `:` et est utilisée par votre shell pour trouver le chemin complet des commandes que vous lancez. | |||
Par exemple: | |||
```bash | |||
PATH="/bin:/usr/bin:/usr/sbin" | |||
$ ifconfig | |||
# lance le binaire /usr/sbin/ifconfig | |||
$ ls | |||
# lance le binaire /bin/ls | |||
``` | |||
Le chemin est "résolu" par le shell en parcourant la liste de tout les | |||
chemins de la variable `PATH`, et en regardant si le chemin complet | |||
existe. La résolution s'arrête dès le premier chemin trouvé. | |||
Par exemple, si vous avez `PATH="/home/user/bin:/usr/bin"` et un fichier `ls` dans `/home/user/bin/ls`, c'est ce fichier-là | |||
(et non `/bin/ls`) qui sera utilisé quand vous taperez `ls`. |
@@ -0,0 +1,439 @@ | |||
+++ | |||
title = "sys.path" | |||
weight = 2 | |||
+++ | |||
# sys.path | |||
En Python, il existe une variable `path` prédéfinie dans le module `sys` qui fonctionne de manière similaire | |||
à la variable d'environnement `PATH`. | |||
Si j'essaye de l'afficher sur ma machine, voici ce que j'obtiens : | |||
```python | |||
import sys | |||
print(sys.path) | |||
``` | |||
``` | |||
[ | |||
"", | |||
"/usr/lib/python3.8", | |||
"/usr/lib/python3.8/lib-dynload", | |||
"/home/dmerej/.local/lib/python3.8/", | |||
"/usr/lib/python3.8/site-packages", | |||
] | |||
``` | |||
Le résultat dépend: | |||
* du système d'exploitation | |||
* de la façon dont Python a été installé | |||
* et de la présence ou non de certains réportoires. | |||
En fait, `sys.path` est construit dynamiquement par l'interpréteur Python au démarrage. | |||
Notez également que `sys.path` commence par une chaîne vide. En pratique, cela signifie que le répertoire courant a la priorité sur tout le reste. | |||
## Priorité du répertoire courant | |||
Prenons un exemple. Si vous ouvrez un explorateur de fichiers dans le deuxième | |||
élément de la liste de `sys.path` (`/usr/lib/python3.8/` sur ma machine), vous trouverez | |||
un grand nombre de fichiers Python. | |||
Notamment, vous devriez trouver un fichier `random.py` dans ce répertoire. | |||
En fait, vous trouverez la plupart des modules de la bibliothèque standard dans | |||
ce répertoire. | |||
Maintenant, imaginons que vous avez un deuxième fichier `random.py` dans votre répertoire courant. Finalement, imaginez | |||
que vous lancez un fichier `foo.py` contentant `import random` dans ce même réportoire. | |||
Et bien, c'est le fichier `random.py` de votre répertoire qui sera utilisé, et non celui de la bibliothèque standard! | |||
## Permissions des répertoires de sys.path | |||
Un autre aspect notable de `sys.path` est qu'il ne contient que deux répertoires dans lesquels l'utilisateur courant peut potentiellement écrire : le chemin courant et le chemin dans `~/.local/lib`. Tous les autres (`/usr/lib/python3.8/`, etc.) sont des chemins "système" et ne peuvent être modifiés que par un compte administrateur (avec `root` ou `sudo`, donc). | |||
La situation est semblable sur macOS et Windows [^2]. | |||
## Bibliothèques tierces | |||
Prenons un exemple : | |||
```python | |||
# dans foo.py | |||
import tabulate | |||
scores = [ | |||
["John", 345], | |||
["Mary-Jane", 2], | |||
["Bob", 543], | |||
] | |||
table = tabulate.tabulate(scores) | |||
print(table) | |||
``` | |||
``` | |||
$ python3 foo.py | |||
--------- --- | |||
John 345 | |||
Mary-Jane 2 | |||
Bob 543 | |||
--------- --- | |||
``` | |||
Ici, le module `tabulate` n'est ni dans la bibliothèque standard, ni écrit par l'auteur du script `foo.py`. On dit que c'est une bibliothèque tierce. | |||
On peut trouver [le code source de tabulate](https://bitbucket.org/astanin/python-tabulate/src/master/) facilement. La question qui se pose alors est: comment faire en sorte que `sys.path` contienne le module `tabulate`? | |||
Eh bien, plusieurs solutions s'offrent à vous. | |||
# Le gestionnaire de paquets | |||
Si vous utilisez une distribution Linux, peut-être pourrez-vous utiliser votre gestionnaire de paquets : | |||
```bash | |||
$ sudo apt install python3-tabulate | |||
``` | |||
Comme vous lancez votre gestionnaire de paquets avec `sudo`, celui-ci sera capable d'écrire dans les chemins système de `sys.path`. | |||
# À la main | |||
Une autre méthode consiste à partir des sources - par exemple, si le paquet de votre distribution n'est pas assez récent, ou si vous avez besoin de modifier le code de la bibliothèque en question. | |||
Voici une marche à suivre possible : | |||
1. Récupérer les sources de la version qui vous intéresse dans la [section téléchargement de bitbucket](https://bitbucket.org/astanin/python-tabulate/downloads/?tab=tags). | |||
1. Extraire l'archive, par exemple dans `src/tabulate` | |||
1. Se rendre dans `src/tabulate` et lancer `python3 setup.py install --user` | |||
# Anatomie du fichier setup.py | |||
La plupart des bibliothèques Python contiennent un `setup.py` à | |||
la racine de leurs sources. Il sert à plein de choses, la commande `install` | |||
n'étant qu'une parmi d'autres. | |||
Le fichier `setup.py` contient en général simplement un `import` de `setuptools`, et un appel à la fonction `setup()`, avec de nombreux arguments : | |||
```python | |||
# tabulate/setup.py | |||
from setuptools import setup | |||
setup( | |||
name='tabulate', | |||
version='0.8.1', | |||
description='Pretty-print tabular data', | |||
py_modules=["tabulate"], | |||
scripts=["bin/tabulate"], | |||
... | |||
) | |||
``` | |||
# Résultat de l'invocation de setup.py | |||
Par défaut, `setup.py` essaiera d'écrire dans un des chemins système de | |||
`sys.path` [^3], d'où l'utilisation de l'option `--user`. | |||
Voici à quoi ressemble la sortie de la commande : | |||
```bash | |||
$ cd src/tabulate | |||
$ python3 setup.py install --user | |||
running install | |||
... | |||
Copying tabulate-0.8.4-py3.7.egg to /home/dmerej/.local/lib/python3.7/site-packages | |||
... | |||
Installing tabulate script to /home/dmerej/.local/bin | |||
``` | |||
Notez que module a été copié dans `~/.local/lib/python3.7/site-packages/` et le script dans `~/.local/bin`. Cela signifie que *tous* les scripts Python lancés par l'utilisateur courant auront accès au module `tabulate`. | |||
Notez également qu'un script a été installé dans `~/.local/bin` - Une bibliothèque Python peut contenir aussi bien des modules que des scripts. | |||
Un point important est que vous n'avez en général pas besoin de lancer le script directement. Vous pouvez utiliser `python3 -m tabulate`. Procéder de cette façon est intéressant puisque vous n'avez pas à vous soucier de rajouter le chemin d'installation des scripts dans la variable d'environnement PATH. | |||
# Dépendances | |||
Prenons une autre bibliothèque : `cli-ui`. | |||
Elle permet d'afficher du texte en couleur dans un terminal | |||
```python | |||
import cli_ui | |||
cli_ui.info("Ceci est en", cli_ui.red, "rouge") | |||
``` | |||
Elle permet également d'afficher des tableaux en couleur : | |||
```python | |||
headers=["name", "score"] | |||
data = [ | |||
[(bold, "John"), (green, 10.0)], | |||
[(bold, "Jane"), (green, 5.0)], | |||
] | |||
cli_ui.info_table(data, headers=headers) | |||
``` | |||
Pour ce faire, elle repose sur la bibliothèque `tabulate` vue précédemment. On dit que `cli-ui` *dépend* de `tabulate`. | |||
# Déclaration des dépendances | |||
La déclaration de la dépendance de `cli-ui` vers `tabulate` s'effectue également dans le fichier `setup.py`: | |||
```python | |||
setup( | |||
name="cli-ui", | |||
version="0.9.1", | |||
install_requires=[ | |||
"tabulate", | |||
... | |||
], | |||
... | |||
) | |||
``` | |||
# pypi.org | |||
On comprend dès lors qu'il doit nécessairement exister un *annuaire* permettant de relier les noms de dépendances à leur code source. | |||
Cet annuaire, c'est le site [pypi.org](https://pypi.org/). Vous y trouverez les pages correspondant à [tabulate](https://pypi.org/project/tabulate/) et [cli-ui](https://pypi.org/project/python-cli-ui/). | |||
# pip | |||
`pip` est un outil qui vient par défaut avec Python3[^4]. Vous pouvez également l'installer grâce au script [get-pip.py](https://bootstrap.pypa.io/get-pip.py), en lançant `python3 get-pip.py --user`. | |||
Il est conseillé de *toujours* lancer `pip` avec `python3 -m pip`. De cette façon, vous êtes certains d'utiliser le module `pip` correspondant à votre binaire `python3`, et vous ne dépendez pas de ce qu'il y a dans votre `PATH`. | |||
`pip` est capable d'interroger le site `pypi.org` pour retrouver les dépendances, et également de lancer les différents scripts `setup.py`. | |||
Comme de nombreux outils, il s'utilise à l'aide de *commandes*. Voici comment installer `cli-ui` à l'aide de la commande 'install' de `pip`: | |||
```bash | |||
$ python3 -m pip install cli-ui --user | |||
Collecting cli-ui | |||
... | |||
Requirement already satisfied: unidecode in /usr/lib/python3.7/site-packages (from cli-ui) (1.0.23) | |||
Requirement already satisfied: colorama in /usr/lib/python3.7/site-packages (from cli-ui) (0.4.1) | |||
Requirement already satisfied: tabulate in /mnt/data/dmerej/src/python-tabulate (from cli-ui) (0.8.4) | |||
Installing collected packages: cli-ui | |||
Successfully installed cli-ui-0.9.1 | |||
``` | |||
On constate ici quelques limitations de `pip`: | |||
* Il faut penser à utiliser `--user` (de la même façon que lorsqu'on lance `setup.py` à la main) | |||
* Si le paquet est déjà installé dans le système, pip ne saura pas le mettre à jour - il faudra passer par le gestionnaire de paquet de | |||
la distribution | |||
En revanche, `pip` contient de nombreuses fonctionnalités intéressantes: | |||
* Il est capable de désinstaller des bibliothèques (à condition toutefois qu'elles ne soient pas dans un répertoire système) | |||
* Il est aussi capable d'afficher la liste complète des bibliothèques Python accessibles par l'utilisateur courant avec `freeze`. | |||
Voici un extrait de la commande `python3 -m pip freeze` au moment de la rédaction de cet article sur ma machine: | |||
``` | |||
$ python3 -m pip freeze | |||
apipkg==1.5 | |||
cli-ui==0.9.1 | |||
gaupol==1.5 | |||
tabulate==0.8.4 | |||
``` | |||
On y retrouve les bibliothèques `cli-ui` et `tabulate`, bien sûr, mais aussi la bibliothèque `gaupol`, qui correspond au [programme d'édition de sous-titres](https://otsaloma.io/gaupol/) que j'ai installé à l'aide du gestionnaire de paquets de ma distribution. Précisons que les modules de la bibliothèque standard et ceux utilisés directement par pip sont omis de la liste. | |||
On constate également que chaque bibliothèque possède un *numéro de version*. | |||
# Numéros de version | |||
Les numéros de version remplissent plusieurs rôles, mais l'un des principaux est de spécifier des changements incompatibles. | |||
Par exemple, pour `cli-ui`, la façon d'appeler la fonction `ask_choice` a changé entre les versions 0.7 et 0.8, comme le montre cet extrait du [changelog](https://tankerhq.github.io/python-cli-ui/changelog.html#v0-8-0): | |||
> the list of choices used by ask_choice is now a named keyword argument: | |||
```python | |||
# Old (<= 0.7) | |||
ask_choice("select a fruit", ["apple", "banana"]) | |||
# New (>= 0.8) | |||
ask_choice("select a fruit", choices=["apple", "banana"]) | |||
``` | |||
Ceci s'appelle un *changement d'API*. | |||
# Réagir aux changements d'API | |||
Plusieurs possibilités: | |||
* On peut bien sûr adapter le code pour utiliser la nouvelle API, mais cela n'est pas toujours possible ni souhaitable. | |||
* Une autre solution est de spécifier des *contraintes* sur le numéro de version dans la déclaration des dépendances. Par exemple : | |||
```python | |||
setup( | |||
install_requires=[ | |||
"cli-ui < 0.8", | |||
... | |||
] | |||
) | |||
``` | |||
# Aparté : pourquoi éviter sudo pip | |||
Souvenez-vous que les fichiers systèmes sont contrôlés par votre gestionnaire de paquets. | |||
Les mainteneurs de votre distribution font en sorte qu'ils fonctionnent bien les uns | |||
avec les autres. Par exemple, le paquet `python3-cli-ui` ne sera mis à jour que lorsque tous les paquets qui en dépendent seront prêts à utiliser la nouvelle API. | |||
En revanche, si vous lancez `sudo pip` (où `pip` avec un compte root), vous allez écrire dans ces mêmes répertoire et vous risquez de "casser" certains programmes de votre système. | |||
Mais il y a un autre problème encore pire. | |||
# Conflit de dépendances | |||
Supposons deux projets A et B dans votre répertoire personnel. Ils dépendent tous les deux de `cli-ui`, mais l'un des deux utilise `cli-ui 0.7` et l'autre `cli-ui 0.9`. Que faire ? | |||
# Environnements virtuels | |||
La solution est d'utiliser un environnement virtuel (*virtualenv* en abrégé). C'est un répertoire *isolé* du reste du système. | |||
Il se crée par exemple avec la commande `python3 -m venv foo-venv`. où `foo-venv` est un répertoire quelconque. | |||
## Aparté : python3 -m venv sur Debian | |||
La commande `python3 -m venv` fonctionne en général partout, dès l'installation de Python3 (*out of the box*, en Anglais), *sauf* sur Debian et ses dérivées [^5]. | |||
Si vous utilisez Debian, la commande pourrait ne pas fonctionner. En fonction des messages d'erreur que vous obtenez, il est possible de résoudre le problème en : | |||
* installant le paquet `python3-venv`, | |||
* ou en utilisant d'abord `pip` pour installer `virtualenv`, avec `python3 -m pip install virtualenv --user` puis en lançant `python3 -m virtualenv foo-venv`. | |||
## Comportement de python dans le virtualenv | |||
Ce répertoire contient de nombreux fichiers et dossiers, et notamment un binaire dans `foo-venv/bin/python3`. | |||
Voyons comment il se comporte en le comparant au binaire `/usr/bin/python3` habituel : | |||
``` | |||
$ /usr/bin/python3 -c 'import sys; print(sys.path)' | |||
['', | |||
... | |||
'/usr/lib/python3.7', | |||
'/usr/lib/python3.7.zip', | |||
'/usr/lib/python3.7/lib-dynload', | |||
'/home/dmerej/.local/lib/python3.7/site-packages', | |||
'/usr/lib/python3.7/site-packages' | |||
] | |||
$ /home/dmerej/foo-venv/bin/python -c 'import sys; print(sys.path)' | |||
['', | |||
'/usr/lib/python3.7', | |||
'/usr/lib/python3.7.zip', | |||
'/usr/lib/python3.7/lib-dynload', | |||
'/home/dmerej/foo-venv/lib/python3.7/site-packages, | |||
] | |||
``` | |||
À noter: | |||
* Le répertoire "global" dans `~/.local/lib` a disparu | |||
* Seuls quelques répertoires systèmes sont présents (ils correspondent plus ou moins à l'emplacement des modules de la bibliothèque standard) | |||
* Un répertoire *au sein* du virtualenv a été rajouté | |||
Ainsi, l'isolation du virtualenv est reflété dans la différence de la valeur de `sys.path`. | |||
Il faut aussi préciser que le virtualenv n'est pas complètement isolé du reste du système. En particulier, il dépend encore du binaire Python utilisé pour le créer. | |||
Par exemple, si vous utilisez `/usr/local/bin/python3.7 -m venv foo-37`, le virtualenv dans `foo-37` utilisera Python 3.7 et fonctionnera tant que le binaire `/usr/local/bin/python3.7` existe. | |||
Cela signifie également qu'il est possible qu'en mettant à jour le paquet `python3` sur votre distribution, vous rendiez inutilisables les virtualenvs créés avec l'ancienne version du paquet. | |||
## Comportement de pip dans le virtualenv | |||
D'après ce qui précède, le virtualenv ne devrait contenir aucun module en dehors de la bibliothèque standard et de `pip` lui-même. | |||
On peut s'en assurer en lançant `python3 -m pip freeze` depuis le virtualenv et en vérifiant que rien ne s'affiche. | |||
``` | |||
$ python3 -m pip freeze | |||
# de nombreuses bibliothèques en dehors du virtualenv | |||
apipkg==1.5 | |||
cli-ui==0.9.1 | |||
gaupol==1.5 | |||
tabulate==0.8.4 | |||
$ /home/dmerej/foo-venv/bin/python3 -m pip freeze | |||
# rien :) | |||
``` | |||
On peut alors utiliser le module `pip` *du virtualenv* pour installer des bibliothèques dans celui-ci : | |||
``` | |||
$ /home/dmerej/foo-venv/bin/python3 -m pip install cli-ui | |||
Collecting cli-ui | |||
Using cached https://pythonhosted.org/..cli_ui-0.9.1-py3-none-any.whl | |||
Collecting colorama (from cli-ui) | |||
Using cached https://pythonhosted.org/..colorama-0.4.1-py2.py3-none-any.whl | |||
Collecting unidecode (from cli-ui) | |||
Using cached https://pythonhosted.org/..Unidecode-1.0.23-py2.py3-none-any.whl | |||
Collecting tabulate (from cli-ui) | |||
Installing collected packages: colorama, unidecode, tabulate, cli-ui | |||
Successfully installed cli-ui-0.9.1 colorama-0.4.1 tabulate-0.8.3 | |||
unidecode-1.0.23 | |||
``` | |||
Cette fois, aucune bibliothèque n'est marquée comme déjà installée, et on récupère donc `cli-ui` et toutes ses dépendances. | |||
On a enfin notre solution pour résoudre notre conflit de dépendances : | |||
on peut simplement créer un virtualenv par projet. Ceci nous permettra | |||
d'avoir effectivement deux versions différentes de `cli-ui`, isolées les | |||
unes des autres. | |||
# Activer un virtualenv | |||
Devoir préciser le chemin du virtualenv en entier pour chaque commande peut devenir fastidieux ; heureusement, il est possible *d'activer* un virtualenv, en lançant une des commandes suivantes : | |||
* `source foo-venv/bin/activate` - si vous utilisez un shell POSIX | |||
* `source foo-venv/bin/activate.fish` - si vous utilisez Fish | |||
* `foo-venv\bin\activate.bat` - sous Windows | |||
Une fois le virtualenv activé, taper `python`, `python3` ou `pip` utilisera les binaires correspondants dans le virtualenv automatiquement, | |||
et ce, tant que la session du shell sera ouverte. | |||
Le script d'activation ne fait en réalité pas grand-chose à part modifier la variable `PATH` et rajouter le nom du virtualenv au début de l'invite de commandes : | |||
``` | |||
# Avant | |||
user@host:~/src $ source foo-env/bin/activate | |||
# Après | |||
(foo-env) user@host:~/src $ | |||
``` | |||
Pour sortir du virtualenv, entrez la commande `deactivate`. | |||
# Conclusion | |||
Le système de gestions des dépendances de Python peut paraître compliqué et bizarre, surtout venant d'autres langages. | |||
Mon conseil est de toujours suivre ces deux règles : | |||
* Un virtualenv par projet et par version de Python | |||
* Toujours utiliser `pip` *depuis* un virtualenv | |||
Certes, cela peut paraître fastidieux, mais c'est une méthode qui vous évitera probablement de vous arracher les cheveux (croyez-en mon expérience). | |||
Dans un futur article, nous approfondirons la question, en évoquant d'autres sujets comme `PYTHONPATH`, le fichier `requirements.txt` ou des outils comme `poetry` ou `pipenv`. À suivre. | |||
[^1]: C'est une condition suffisante, mais pas nécessaire - on y reviendra. | |||
[^2]: Presque. Il peut arriver que l'utilisateur courant ait les droits d'écriture dans *tous* les segments de `sys.path`, en fonction de l'installation de Python. Cela dit, c'est plutôt l'exception que la règle. | |||
[^3]: Cela peut vous paraître étrange à première vue. Il y a de nombreuses raisons historiques à ce comportement, et il n'est pas sûr qu'il puisse être changé un jour. | |||
[^4]: Presque. Parfois il faut installer un paquet supplémentaire, notamment sur les distributions basées sur Debian | |||
[^5]: Je n'ai pas réussi à trouver une explication satisfaisante à ce choix des mainteneurs Debian. Si vous avez des informations à ce sujet, je suis preneur. _Mise à jour: Il se trouve que cette décision s'inscrit au sein de la "debian policy", c'est à dire une liste de règles que doivent respecter tous les programmes maintenus par Debian._ |
@@ -0,0 +1,106 @@ | |||
+++ | |||
title = "Bibliothèques tierces" | |||
weight = 3 | |||
+++ | |||
# Bibliothèques tierces | |||
Prenons un exemple : | |||
```python | |||
# dans foo.py | |||
import tabulate | |||
scores = [ | |||
["John", 345], | |||
["Mary-Jane", 2], | |||
["Bob", 543], | |||
] | |||
table = tabulate.tabulate(scores) | |||
print(table) | |||
``` | |||
``` | |||
$ python3 foo.py | |||
--------- --- | |||
John 345 | |||
Mary-Jane 2 | |||
Bob 543 | |||
--------- --- | |||
``` | |||
Ici, le module `tabulate` n'est ni dans la bibliothèque standard, ni écrit par l'auteur du script `foo.py`. On dit que c'est une bibliothèque tierce. | |||
On peut trouver [le code source de tabulate](https://bitbucket.org/astanin/python-tabulate/src/master/) facilement. La question qui se pose alors est: comment faire en sorte que `sys.path` contienne le module `tabulate`? | |||
Eh bien, plusieurs solutions s'offrent à vous. | |||
## Le gestionnaire de paquets | |||
Si vous utilisez une distribution Linux, peut-être pourrez-vous utiliser votre gestionnaire de paquets : | |||
```bash | |||
$ sudo apt install python3-tabulate | |||
``` | |||
Comme vous lancez votre gestionnaire de paquets avec `sudo`, celui-ci sera capable d'écrire dans les chemins système de `sys.path`. | |||
## À la main | |||
Une autre méthode consiste à partir des sources - par exemple, si le paquet de votre distribution n'est pas assez récent, ou si vous avez besoin de modifier le code de la bibliothèque en question. | |||
Voici une marche à suivre possible : | |||
1. Récupérer les sources de la version qui vous intéresse dans la [section téléchargement de bitbucket](https://bitbucket.org/astanin/python-tabulate/downloads/?tab=tags). | |||
1. Extraire l'archive, par exemple dans `src/tabulate` | |||
1. Se rendre dans `src/tabulate` et lancer `python3 setup.py install --user` | |||
## Anatomie du fichier setup.py | |||
La plupart des bibliothèques Python contiennent un `setup.py` à | |||
la racine de leurs sources. Il sert à plein de choses, la commande `install` | |||
n'étant qu'une parmi d'autres. | |||
Le fichier `setup.py` contient en général simplement un `import` de `setuptools`, et un appel à la fonction `setup()`, avec de nombreux arguments : | |||
```python | |||
# tabulate/setup.py | |||
from setuptools import setup | |||
setup( | |||
name='tabulate', | |||
version='0.8.1', | |||
description='Pretty-print tabular data', | |||
py_modules=["tabulate"], | |||
scripts=["bin/tabulate"], | |||
... | |||
) | |||
``` | |||
## Résultat de l'invocation de setup.py | |||
Par défaut, `setup.py` essaiera d'écrire dans un des chemins système de | |||
`sys.path` [^3], d'où l'utilisation de l'option `--user`. | |||
Voici à quoi ressemble la sortie de la commande : | |||
```bash | |||
$ cd src/tabulate | |||
$ python3 setup.py install --user | |||
running install | |||
... | |||
Copying tabulate-0.8.4-py3.7.egg to /home/dmerej/.local/lib/python3.7/site-packages | |||
... | |||
Installing tabulate script to /home/dmerej/.local/bin | |||
``` | |||
Notez que module a été copié dans `~/.local/lib/python3.7/site-packages/` et le script dans `~/.local/bin`. Cela signifie que *tous* les scripts Python lancés par l'utilisateur courant auront accès au module `tabulate`. | |||
Notez également qu'un script a été installé dans `~/.local/bin` - Une bibliothèque Python peut contenir aussi bien des modules que des scripts. | |||
Un point important est que vous n'avez en général pas besoin de lancer le script directement. Vous pouvez utiliser `python3 -m tabulate`. Procéder de cette façon est intéressant puisque vous n'avez pas à vous soucier de rajouter le chemin d'installation des scripts dans la variable d'environnement PATH. | |||
@@ -0,0 +1,147 @@ | |||
+++ | |||
title = "Dépendances" | |||
weight = 4 | |||
+++ | |||
# Dépendances | |||
## Un autre exemple | |||
Prenons une autre bibliothèque : `cli-ui`. | |||
Elle permet d'afficher du texte en couleur dans un terminal | |||
```python | |||
import cli_ui | |||
cli_ui.info("Ceci est en", cli_ui.red, "rouge") | |||
``` | |||
Elle permet également d'afficher des tableaux en couleur : | |||
```python | |||
headers=["prénom", "score"] | |||
data = [ | |||
[(bold, "John"), (green, 10.0)], | |||
[(bold, "Jane"), (green, 5.0)], | |||
] | |||
cli_ui.info_table(data, headers=headers) | |||
``` | |||
Pour ce faire, elle repose sur la bibliothèque `tabulate` vue précédemment. On dit que `cli-ui` *dépend* de `tabulate`. | |||
## Déclaration des dépendances | |||
La déclaration de la dépendance de `cli-ui` vers `tabulate` s'effectue également dans le fichier `setup.py`: | |||
```python | |||
setup( | |||
name="cli-ui", | |||
version="0.9.1", | |||
install_requires=[ | |||
"tabulate", | |||
... | |||
], | |||
... | |||
) | |||
``` | |||
## pypi.org | |||
On comprend dès lors qu'il doit nécessairement exister un *annuaire* permettant de relier les noms de dépendances à leur code source. | |||
Cet annuaire, c'est le site [pypi.org](https://pypi.org/). Vous y trouverez les pages correspondant à [tabulate](https://pypi.org/project/tabulate/) et [cli-ui](https://pypi.org/project/python-cli-ui/). | |||
# pip | |||
`pip` est un outil qui vient par défaut avec Python3[^4]. Vous pouvez également l'installer grâce au script [get-pip.py](https://bootstrap.pypa.io/get-pip.py), en lançant `python3 get-pip.py --user`. | |||
Il est conseillé de *toujours* lancer `pip` avec `python3 -m pip`. De cette façon, vous êtes certains d'utiliser le module `pip` correspondant à votre binaire `python3`, et vous ne dépendez pas de ce qu'il y a dans votre `PATH`. | |||
`pip` est capable d'interroger le site `pypi.org` pour retrouver les dépendances, et également de lancer les différents scripts `setup.py`. | |||
Comme de nombreux outils, il s'utilise à l'aide de *commandes*. Voici comment installer `cli-ui` à l'aide de la commande 'install' de `pip`: | |||
```bash | |||
$ python3 -m pip install cli-ui --user | |||
Collecting cli-ui | |||
... | |||
Requirement already satisfied: unidecode in /usr/lib/python3.7/site-packages (from cli-ui) (1.0.23) | |||
Requirement already satisfied: colorama in /usr/lib/python3.7/site-packages (from cli-ui) (0.4.1) | |||
Requirement already satisfied: tabulate in /mnt/data/dmerej/src/python-tabulate (from cli-ui) (0.8.4) | |||
Installing collected packages: cli-ui | |||
Successfully installed cli-ui-0.9.1 | |||
``` | |||
On constate ici quelques limitations de `pip`: | |||
* Il faut penser à utiliser `--user` (de la même façon que lorsqu'on lance `setup.py` à la main) | |||
* Si le paquet est déjà installé dans le système, pip ne saura pas le mettre à jour - il faudra passer par le gestionnaire de paquet de | |||
la distribution | |||
En revanche, `pip` contient de nombreuses fonctionnalités intéressantes: | |||
* Il est capable de désinstaller des bibliothèques (à condition toutefois qu'elles ne soient pas dans un répertoire système) | |||
* Il est aussi capable d'afficher la liste complète des bibliothèques Python accessibles par l'utilisateur courant avec `freeze`. | |||
Voici un extrait de la commande `python3 -m pip freeze` au moment de la rédaction de cet article sur ma machine: | |||
``` | |||
$ python3 -m pip freeze | |||
apipkg==1.5 | |||
cli-ui==0.9.1 | |||
gaupol==1.5 | |||
tabulate==0.8.4 | |||
``` | |||
On y retrouve les bibliothèques `cli-ui` et `tabulate`, bien sûr, mais aussi la bibliothèque `gaupol`, qui correspond au [programme d'édition de sous-titres](https://otsaloma.io/gaupol/) que j'ai installé à l'aide du gestionnaire de paquets de ma distribution. Précisons que les modules de la bibliothèque standard et ceux utilisés directement par pip sont omis de la liste. | |||
On constate également que chaque bibliothèque possède un *numéro de version*. | |||
## Numéros de version | |||
Les numéros de version remplissent plusieurs rôles, mais l'un des principaux est de spécifier des changements incompatibles. | |||
Par exemple, pour `cli-ui`, la façon d'appeler la fonction `ask_choice` a changé entre les versions 0.7 et 0.8, comme le montre cet extrait du [changelog](https://tankerhq.github.io/python-cli-ui/changelog.html#v0-8-0): | |||
> the list of choices used by ask_choice is now a named keyword argument: | |||
```python | |||
# Old (<= 0.7) | |||
ask_choice("select a fruit", ["apple", "banana"]) | |||
# New (>= 0.8) | |||
ask_choice("select a fruit", choices=["apple", "banana"]) | |||
``` | |||
Ceci s'appelle un *changement d'API*. | |||
## Réagir aux changements d'API | |||
Plusieurs possibilités: | |||
* On peut bien sûr adapter le code pour utiliser la nouvelle API, mais cela n'est pas toujours possible ni souhaitable. | |||
* Une autre solution est de spécifier des *contraintes* sur le numéro de version dans la déclaration des dépendances. Par exemple : | |||
```python | |||
setup( | |||
install_requires=[ | |||
"cli-ui < 0.8", | |||
... | |||
] | |||
) | |||
``` | |||
# Aparté : pourquoi éviter sudo pip | |||
Souvenez-vous que les fichiers systèmes sont contrôlés par votre gestionnaire de paquets. | |||
Les mainteneurs de votre distribution font en sorte qu'ils fonctionnent bien les uns | |||
avec les autres. Par exemple, le paquet `python3-cli-ui` ne sera mis à jour que lorsque tous les paquets qui en dépendent seront prêts à utiliser la nouvelle API. | |||
En revanche, si vous lancez `sudo pip` (où `pip` avec un compte root), vous allez écrire dans ces mêmes répertoire et vous risquez de "casser" certains programmes de votre système. | |||
Mais il y a un autre problème encore pire. | |||
# Conflit de dépendances | |||
Supposons deux projets A et B dans votre répertoire personnel. Ils dépendent tous les deux de `cli-ui`, mais l'un des deux utilise `cli-ui 0.7` et l'autre `cli-ui 0.9`. Que faire ? |
@@ -0,0 +1,137 @@ | |||
+++ | |||
title = "Environnements virtuels" | |||
weight = 5 | |||
+++ | |||
# Environnements virtuels | |||
La solution à la question de la fin du chapitre précédent est d'utiliser un *environnement virtuel* | |||
(*virtualenv* en abrégé). | |||
C'est un répertoire *isolé* du reste du système. | |||
Il se crée par exemple avec la commande `python3 -m venv foo-venv`. où `foo-venv` est un répertoire quelconque. | |||
## Aparté : python3 -m venv sur Debian | |||
La commande `python3 -m venv` fonctionne en général partout, dès l'installation de Python3 (*out of the box*, en Anglais), *sauf* sur Debian et ses dérivées [^5]. | |||
Si vous utilisez Debian, la commande pourrait ne pas fonctionner. En fonction des messages d'erreur que vous obtenez, il est possible de résoudre le problème en : | |||
* installant le paquet `python3-venv`, | |||
* ou en utilisant d'abord `pip` pour installer `virtualenv`, avec `python3 -m pip install virtualenv --user` puis en lançant `python3 -m virtualenv foo-venv`. | |||
## Comportement de python dans le virtualenv | |||
Ce répertoire contient de nombreux fichiers et dossiers, et notamment un binaire dans `foo-venv/bin/python3`. | |||
Voyons comment il se comporte en le comparant au binaire `/usr/bin/python3` habituel : | |||
``` | |||
$ /usr/bin/python3 -c 'import sys; print(sys.path)' | |||
['', | |||
... | |||
'/usr/lib/python3.7', | |||
'/usr/lib/python3.7.zip', | |||
'/usr/lib/python3.7/lib-dynload', | |||
'/home/dmerej/.local/lib/python3.7/site-packages', | |||
'/usr/lib/python3.7/site-packages' | |||
] | |||
$ /home/dmerej/foo-venv/bin/python -c 'import sys; print(sys.path)' | |||
['', | |||
'/usr/lib/python3.7', | |||
'/usr/lib/python3.7.zip', | |||
'/usr/lib/python3.7/lib-dynload', | |||
'/home/dmerej/foo-venv/lib/python3.7/site-packages, | |||
] | |||
``` | |||
À noter: | |||
* Le répertoire "global" dans `~/.local/lib` a disparu | |||
* Seuls quelques répertoires systèmes sont présents (ils correspondent plus ou moins à l'emplacement des modules de la bibliothèque standard) | |||
* Un répertoire *au sein* du virtualenv a été rajouté | |||
Ainsi, l'isolation du virtualenv est reflété dans la différence de la valeur de `sys.path`. | |||
Il faut aussi préciser que le virtualenv n'est pas complètement isolé du reste du système. En particulier, il dépend encore du binaire Python utilisé pour le créer. | |||
Par exemple, si vous utilisez `/usr/local/bin/python3.7 -m venv foo-37`, le virtualenv dans `foo-37` utilisera Python 3.7 et fonctionnera tant que le binaire `/usr/local/bin/python3.7` existe. | |||
Cela signifie également qu'il est possible qu'en mettant à jour le paquet `python3` sur votre distribution, vous rendiez inutilisables les virtualenvs créés avec l'ancienne version du paquet. | |||
## Comportement de pip dans le virtualenv | |||
D'après ce qui précède, le virtualenv ne devrait contenir aucun module en dehors de la bibliothèque standard et de `pip` lui-même. | |||
On peut s'en assurer en lançant `python3 -m pip freeze` depuis le virtualenv et en vérifiant que rien ne s'affiche. | |||
``` | |||
$ python3 -m pip freeze | |||
# de nombreuses bibliothèques en dehors du virtualenv | |||
apipkg==1.5 | |||
cli-ui==0.9.1 | |||
gaupol==1.5 | |||
tabulate==0.8.4 | |||
$ /home/dmerej/foo-venv/bin/python3 -m pip freeze | |||
# rien :) | |||
``` | |||
On peut alors utiliser le module `pip` *du virtualenv* pour installer des bibliothèques dans celui-ci : | |||
``` | |||
$ /home/dmerej/foo-venv/bin/python3 -m pip install cli-ui | |||
Collecting cli-ui | |||
Using cached https://pythonhosted.org/..cli_ui-0.9.1-py3-none-any.whl | |||
Collecting colorama (from cli-ui) | |||
Using cached https://pythonhosted.org/..colorama-0.4.1-py2.py3-none-any.whl | |||
Collecting unidecode (from cli-ui) | |||
Using cached https://pythonhosted.org/..Unidecode-1.0.23-py2.py3-none-any.whl | |||
Collecting tabulate (from cli-ui) | |||
Installing collected packages: colorama, unidecode, tabulate, cli-ui | |||
Successfully installed cli-ui-0.9.1 colorama-0.4.1 tabulate-0.8.3 | |||
unidecode-1.0.23 | |||
``` | |||
Cette fois, aucune bibliothèque n'est marquée comme déjà installée, et on récupère donc `cli-ui` et toutes ses dépendances. | |||
On a enfin notre solution pour résoudre notre conflit de dépendances : | |||
on peut simplement créer un virtualenv par projet. Ceci nous permettra | |||
d'avoir effectivement deux versions différentes de `cli-ui`, isolées les | |||
unes des autres. | |||
## Activer un virtualenv | |||
Devoir préciser le chemin du virtualenv en entier pour chaque commande peut devenir fastidieux ; heureusement, il est possible *d'activer* un virtualenv, en lançant une des commandes suivantes : | |||
* `source foo-venv/bin/activate` - si vous utilisez un shell POSIX | |||
* `source foo-venv/bin/activate.fish` - si vous utilisez Fish | |||
* `foo-venv\bin\activate.bat` - sous Windows | |||
Une fois le virtualenv activé, taper `python`, `python3` ou `pip` utilisera les binaires correspondants dans le virtualenv automatiquement, | |||
et ce, tant que la session du shell sera ouverte. | |||
Le script d'activation ne fait en réalité pas grand-chose à part modifier la variable `PATH` et rajouter le nom du virtualenv au début de l'invite de commandes : | |||
``` | |||
# Avant | |||
user@host:~/src $ source foo-env/bin/activate | |||
# Après | |||
(foo-env) user@host:~/src $ | |||
``` | |||
Pour sortir du virtualenv, entrez la commande `deactivate`. | |||
## Conclusion | |||
Le système de gestions des dépendances de Python peut paraître compliqué et bizarre, surtout venant d'autres langages. | |||
Mon conseil est de toujours suivre ces deux règles : | |||
* Un virtualenv par projet et par version de Python | |||
* Toujours utiliser `pip` *depuis* un virtualenv | |||
Certes, cela peut paraître fastidieux, mais c'est une méthode qui vous évitera probablement de vous arracher les cheveux (croyez-en mon expérience). |
@@ -0,0 +1,4 @@ | |||
+++ | |||
title = "Chapitre 14 - Bibliothèques" | |||
weight = 14 | |||
+++ |
@@ -0,0 +1,37 @@ | |||
import argparse | |||
import subprocess | |||
import sys | |||
def build(): | |||
process = subprocess.run(["hugo"]) | |||
if process.returncode != 0: | |||
sys.exit("build failed") | |||
def deploy(*, dry_run): | |||
cmd = [ | |||
"rsync", | |||
"--itemize-changes", | |||
"--recursive", | |||
"--delete", | |||
"public/", | |||
"dedi3:/srv/nginx/html/books/python/", | |||
] | |||
if dry_run: | |||
cmd += ["--dry-run"] | |||
process = subprocess.run(cmd) | |||
if process.returncode != 0: | |||
sys.exit("deployment failed") | |||
def main(): | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument("-n", "--dry-run", action="store_true") | |||
args = parser.parse_args() | |||
build() | |||
deploy(dry_run=args.dry_run) | |||
if __name__ == "__main__": | |||
main() |