Browse Source

Ajout des sources du cours en ligne

master
Dimitri Merejkowsky 4 years ago
parent
commit
da6b00adaa
54 changed files with 3511 additions and 0 deletions
  1. +2
    -0
      README.md
  2. +2
    -0
      cours/TODO
  3. +6
    -0
      cours/archetypes/default.md
  4. +49
    -0
      cours/config.yaml
  5. +18
    -0
      cours/content/_index.md
  6. +20
    -0
      cours/content/docs/01-introduction/01-introduction.md
  7. +54
    -0
      cours/content/docs/01-introduction/02-le-langage-python.md
  8. +4
    -0
      cours/content/docs/01-introduction/_index.md
  9. +25
    -0
      cours/content/docs/02-premiers-pas/01-bases-ligne-de-commandes.md
  10. +35
    -0
      cours/content/docs/02-premiers-pas/02-interpréteur.md
  11. +62
    -0
      cours/content/docs/02-premiers-pas/03-maths-simples.md
  12. +4
    -0
      cours/content/docs/02-premiers-pas/_index.md
  13. +49
    -0
      cours/content/docs/03-variables-et-types/01-variables.md
  14. +60
    -0
      cours/content/docs/03-variables-et-types/02-chaînes-de-caractères.md
  15. +39
    -0
      cours/content/docs/03-variables-et-types/03-types.md
  16. +60
    -0
      cours/content/docs/03-variables-et-types/04-booléens.md
  17. +4
    -0
      cours/content/docs/03-variables-et-types/_index.md
  18. +107
    -0
      cours/content/docs/04-code-source/01-code-source.md
  19. +4
    -0
      cours/content/docs/04-code-source/_index.md
  20. +115
    -0
      cours/content/docs/05-flot-de-controle/01-flot-de-contrôle.md
  21. +38
    -0
      cours/content/docs/05-flot-de-controle/02-exercice.md
  22. +4
    -0
      cours/content/docs/05-flot-de-controle/_index.md
  23. +75
    -0
      cours/content/docs/06-fonctions/01-functions.md
  24. +39
    -0
      cours/content/docs/06-fonctions/02-portée-des-variables.md
  25. +43
    -0
      cours/content/docs/06-fonctions/03-plusieurs-arguments.md
  26. +1
    -0
      cours/content/docs/06-fonctions/04
  27. +28
    -0
      cours/content/docs/06-fonctions/04-par-défaut.md
  28. +46
    -0
      cours/content/docs/06-fonctions/05-fonctions-natives.md
  29. +43
    -0
      cours/content/docs/06-fonctions/06-return.md
  30. +4
    -0
      cours/content/docs/06-fonctions/_index.md
  31. +164
    -0
      cours/content/docs/07-listes/01-listes.md
  32. +4
    -0
      cours/content/docs/07-listes/_index.md
  33. +98
    -0
      cours/content/docs/08-none-et-pass/01-none.md
  34. +34
    -0
      cours/content/docs/08-none-et-pass/02-pass.md
  35. +4
    -0
      cours/content/docs/08-none-et-pass/_index.md
  36. +205
    -0
      cours/content/docs/09-dictionnaires/01-dictionnaires.md
  37. +4
    -0
      cours/content/docs/09-dictionnaires/_index.md
  38. +169
    -0
      cours/content/docs/10-tuples/01-tuples.md
  39. +4
    -0
      cours/content/docs/10-tuples/_index.md
  40. +319
    -0
      cours/content/docs/11-classes-01/01-classes.md
  41. +4
    -0
      cours/content/docs/11-classes-01/_index.md
  42. +148
    -0
      cours/content/docs/12-modules-01/01-modules.md
  43. +4
    -0
      cours/content/docs/12-modules-01/_index.md
  44. +186
    -0
      cours/content/docs/13-classes-02/01-rappels.md
  45. +91
    -0
      cours/content/docs/13-classes-02/02-couplage.md
  46. +117
    -0
      cours/content/docs/13-classes-02/03-composition.md
  47. +4
    -0
      cours/content/docs/13-classes-02/_index.md
  48. +42
    -0
      cours/content/docs/14-bibliothèques-01/01-rappels.md
  49. +439
    -0
      cours/content/docs/14-bibliothèques-01/02-sys.path.md
  50. +106
    -0
      cours/content/docs/14-bibliothèques-01/03-bibliotheques-tierces.md
  51. +147
    -0
      cours/content/docs/14-bibliothèques-01/04-dépendances.md
  52. +137
    -0
      cours/content/docs/14-bibliothèques-01/05-virtualenv.md
  53. +4
    -0
      cours/content/docs/14-bibliothèques-01/_index.md
  54. +37
    -0
      cours/deploy.py

+ 2
- 0
README.md View File

@@ -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/).

+ 2
- 0
cours/TODO View File

@@ -0,0 +1,2 @@
explication des exercices
insérer exercices pour chaque épisode de la saison 2

+ 6
- 0
cours/archetypes/default.md View File

@@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---


+ 49
- 0
cours/config.yaml View File

@@ -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

+ 18
- 0
cours/content/_index.md View File

@@ -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.


+ 20
- 0
cours/content/docs/01-introduction/01-introduction.md View File

@@ -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)

+ 54
- 0
cours/content/docs/01-introduction/02-le-langage-python.md View File

@@ -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 :/



+ 4
- 0
cours/content/docs/01-introduction/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 1 - Introduction"
weight = 1
+++

+ 25
- 0
cours/content/docs/02-premiers-pas/01-bases-ligne-de-commandes.md View File

@@ -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*


+ 35
- 0
cours/content/docs/02-premiers-pas/02-interpréteur.md View File

@@ -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/).

+ 62
- 0
cours/content/docs/02-premiers-pas/03-maths-simples.md View File

@@ -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*


+ 4
- 0
cours/content/docs/02-premiers-pas/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 2 - Premiers pas"
weight = 2
+++

+ 49
- 0
cours/content/docs/03-variables-et-types/01-variables.md View File

@@ -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
```


+ 60
- 0
cours/content/docs/03-variables-et-types/02-chaînes-de-caractères.md View File

@@ -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 !"
```



+ 39
- 0
cours/content/docs/03-variables-et-types/03-types.md View File

@@ -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.


+ 60
- 0
cours/content/docs/03-variables-et-types/04-booléens.md View File

@@ -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
```


+ 4
- 0
cours/content/docs/03-variables-et-types/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 3 - Variables et types"
weight = 3
+++

+ 107
- 0
cours/content/docs/04-code-source/01-code-source.md View File

@@ -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

+ 4
- 0
cours/content/docs/04-code-source/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 4 - code source"
weight = 4
+++

+ 115
- 0
cours/content/docs/05-flot-de-controle/01-flot-de-contrôle.md View File

@@ -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
```

+ 38
- 0
cours/content/docs/05-flot-de-controle/02-exercice.md View File

@@ -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
```

+ 4
- 0
cours/content/docs/05-flot-de-controle/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 5 - Flot de contrôle"
weight = 5
+++

+ 75
- 0
cours/content/docs/06-fonctions/01-functions.md View File

@@ -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")
```

+ 39
- 0
cours/content/docs/06-fonctions/02-portée-des-variables.md View File

@@ -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
```

+ 43
- 0
cours/content/docs/06-fonctions/03-plusieurs-arguments.md View File

@@ -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


+ 1
- 0
cours/content/docs/06-fonctions/04 View File

@@ -0,0 +1 @@


+ 28
- 0
cours/content/docs/06-fonctions/04-par-défaut.md View File

@@ -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
```


+ 46
- 0
cours/content/docs/06-fonctions/05-fonctions-natives.md View File

@@ -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
```



+ 43
- 0
cours/content/docs/06-fonctions/06-return.md View File

@@ -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
```

+ 4
- 0
cours/content/docs/06-fonctions/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 6 - Fonctions"
weight = 6
+++

+ 164
- 0
cours/content/docs/07-listes/01-listes.md View File

@@ -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
```

+ 4
- 0
cours/content/docs/07-listes/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 7 - Listes"
weight = 7
+++

+ 98
- 0
cours/content/docs/08-none-et-pass/01-none.md View File

@@ -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
...
```

+ 34
- 0
cours/content/docs/08-none-et-pass/02-pass.md View File

@@ -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>
```

+ 4
- 0
cours/content/docs/08-none-et-pass/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 8 - None et pass"
weight = 8
+++

+ 205
- 0
cours/content/docs/09-dictionnaires/01-dictionnaires.md View File

@@ -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()` ...


+ 4
- 0
cours/content/docs/09-dictionnaires/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 9 - Dictionnaires"
weight = 9
+++

+ 169
- 0
cours/content/docs/10-tuples/01-tuples.md View File

@@ -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!

+ 4
- 0
cours/content/docs/10-tuples/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 10 - tuples"
weight = 10
+++

+ 319
- 0
cours/content/docs/11-classes-01/01-classes.md View File

@@ -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.

+ 4
- 0
cours/content/docs/11-classes-01/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 11 - Classes (1ère partie)"
weight = 11
+++

+ 148
- 0
cours/content/docs/12-modules-01/01-modules.md View File

@@ -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 ...

+ 4
- 0
cours/content/docs/12-modules-01/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 12 - Modules - 1ère partie"
weight = 12
+++

+ 186
- 0
cours/content/docs/13-classes-02/01-rappels.md View File

@@ -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
```

+ 91
- 0
cours/content/docs/13-classes-02/02-couplage.md View File

@@ -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`.

+ 117
- 0
cours/content/docs/13-classes-02/03-composition.md View File

@@ -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.

+ 4
- 0
cours/content/docs/13-classes-02/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 13 - Classes (2ème partie)"
weight = 13
+++

+ 42
- 0
cours/content/docs/14-bibliothèques-01/01-rappels.md View File

@@ -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`.

+ 439
- 0
cours/content/docs/14-bibliothèques-01/02-sys.path.md View File

@@ -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&nbsp;:

```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._

+ 106
- 0
cours/content/docs/14-bibliothèques-01/03-bibliotheques-tierces.md View File

@@ -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&nbsp;:

```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.


+ 147
- 0
cours/content/docs/14-bibliothèques-01/04-dépendances.md View File

@@ -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 ?

+ 137
- 0
cours/content/docs/14-bibliothèques-01/05-virtualenv.md View File

@@ -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).

+ 4
- 0
cours/content/docs/14-bibliothèques-01/_index.md View File

@@ -0,0 +1,4 @@
+++
title = "Chapitre 14 - Bibliothèques"
weight = 14
+++

+ 37
- 0
cours/deploy.py View File

@@ -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()