Explorar el Código

Proofread, convert to sphinx

master
Dimitri Merejkowsky hace 4 años
padre
commit
c60cdf06ad
Se han modificado 51 ficheros con 2331 adiciones y 2716 borrados
  1. +3
    -1
      cours/build.py
  2. +6
    -8
      cours/source/01-introduction/01-introduction.rst
  3. +12
    -13
      cours/source/01-introduction/02-le-langage-python.rst
  4. +9
    -4
      cours/source/01-introduction/index.rst
  5. +6
    -8
      cours/source/02-premiers-pas/01-bases-ligne-de-commandes.rst
  6. +19
    -19
      cours/source/02-premiers-pas/02-interpréteur.rst
  7. +41
    -41
      cours/source/02-premiers-pas/03-maths-simples.rst
  8. +9
    -4
      cours/source/02-premiers-pas/index.rst
  9. +24
    -28
      cours/source/03-variables-et-types/01-variables.rst
  10. +31
    -34
      cours/source/03-variables-et-types/02-chaînes-de-caractères.rst
  11. +22
    -25
      cours/source/03-variables-et-types/03-types.rst
  12. +21
    -24
      cours/source/03-variables-et-types/04-booléens.rst
  13. +10
    -4
      cours/source/03-variables-et-types/index.rst
  14. +0
    -107
      cours/source/04-code-source/01-code-source.rst
  15. +98
    -4
      cours/source/04-code-source/index.rst
  16. +24
    -24
      cours/source/05-flot-de-controle/01-flot-de-contrôle.rst
  17. +16
    -17
      cours/source/05-flot-de-controle/02-exercice.rst
  18. +8
    -4
      cours/source/05-flot-de-controle/index.rst
  19. +33
    -43
      cours/source/06-fonctions/01-functions.rst
  20. +18
    -28
      cours/source/06-fonctions/02-portée-des-variables.rst
  21. +20
    -34
      cours/source/06-fonctions/03-plusieurs-arguments.rst
  22. +0
    -1
      cours/source/06-fonctions/04
  23. +15
    -24
      cours/source/06-fonctions/04-par-défaut.rst
  24. +20
    -30
      cours/source/06-fonctions/05-fonctions-natives.rst
  25. +34
    -43
      cours/source/06-fonctions/06-return.rst
  26. +12
    -4
      cours/source/06-fonctions/index.rst
  27. +0
    -164
      cours/source/07-listes/01-listes.rst
  28. +150
    -4
      cours/source/07-listes/index.rst
  29. +71
    -89
      cours/source/08-none-et-pass/01-none.rst
  30. +11
    -24
      cours/source/08-none-et-pass/02-pass.rst
  31. +8
    -4
      cours/source/08-none-et-pass/index.rst
  32. +0
    -205
      cours/source/09-dictionnaires/01-dictionnaires.rst
  33. +176
    -4
      cours/source/09-dictionnaires/index.rst
  34. +0
    -169
      cours/source/10-tuples/01-tuples.rst
  35. +151
    -4
      cours/source/10-tuples/index.rst
  36. +0
    -319
      cours/source/11-classes-01/01-classes.rst
  37. +296
    -4
      cours/source/11-classes-01/index.rst
  38. +0
    -148
      cours/source/12-modules-01/01-modules.rst
  39. +135
    -4
      cours/source/12-modules-01/index.rst
  40. +128
    -137
      cours/source/13-classes-02/01-rappels.rst
  41. +65
    -74
      cours/source/13-classes-02/02-couplage.rst
  42. +64
    -75
      cours/source/13-classes-02/03-composition.rst
  43. +9
    -4
      cours/source/13-classes-02/index.rst
  44. +20
    -23
      cours/source/14-bibliothèques-01/01-rappels.rst
  45. +32
    -414
      cours/source/14-bibliothèques-01/02-sys.path.rst
  46. +243
    -69
      cours/source/14-bibliothèques-01/03-bibliotheques-tierces.rst
  47. +113
    -94
      cours/source/14-bibliothèques-01/04-dépendances.rst
  48. +120
    -89
      cours/source/14-bibliothèques-01/05-virtualenv.rst
  49. +12
    -4
      cours/source/14-bibliothèques-01/index.rst
  50. +1
    -0
      cours/source/conf.py
  51. +15
    -15
      cours/source/index.rst

+ 3
- 1
cours/build.py Ver fichero

@@ -6,9 +6,11 @@ def main():
dev = "--dev" in sys.argv
if dev:
program = "sphinx-autobuild"
opts = []
else:
program = "sphinx-build"
cmd = [program, "-d", "build", "-b", "html", "source", "build/html"]
opts = ["-W"]
cmd = [program, *opts, "-d", "build", "-b", "html", "source", "build/html"]
subprocess.run(cmd, check=True)




+ 6
- 8
cours/source/01-introduction/01-introduction.rst Ver fichero

@@ -1,19 +1,17 @@
+++
title = "Objet de ce livre"
weight = 1
+++

# Objet de ce livre
Objet de ce livre
=================

Apprendre la programmation en partant de rien, en utilisant Python et la ligne de commande

# Pourquoi Python?
Pourquoi Python?
----------------

* Excellent langage pour débuter
* Mon langage préféré
* Vraiment cross-platform (sauf pour le mobile)

# Pourquoi la ligne de commande?
Pourquoi la ligne de commande?
------------------------------

Interface universelle



+ 12
- 13
cours/source/01-introduction/02-le-langage-python.rst Ver fichero

@@ -1,11 +1,8 @@
+++
title = "Le langage Python"
weight = 3
+++
Présentation du langage Python
==============================

# Présentation du langage Python

## Utilisation de Python
Utilisation de Python
----------------------

* Aussi appelé "langage de script", `glue language`

@@ -19,7 +16,8 @@ weight = 3
* Ligne de commande
* ...

## Petit détour: version d'un programme
Petit détour: version d'un programme
------------------------------------

* Comme les versions d'un document
* Si le nombre est plus grand, c'est plus récent
@@ -29,7 +27,8 @@ weight = 3
* `1.5.1 -> 4.3`: beaucoup de changements
* On omet souvent le reste des numéros quand c'est pas nécessaire

## Historique
Historique
----------

* Créé par Guido van Rossum. Conçu à la base pour l'enseignement.
* Le nom vient des Monty Python (si, si)
@@ -37,7 +36,8 @@ weight = 3
* Python 2: en 2000
* Python 3: en 2008

## Le grand schisme
Le grand schisme
----------------

La plupart des langages continuent à être compatibles d'une version à l'autre.

@@ -45,10 +45,9 @@ La plupart des langages continuent à être compatibles d'une version à l'autre

Heureusement, 10 ans plus tard, la situation s'est arrangée, et Python2 cessera d'être maintenu le premier janvier 2020.

## Python3
Python3
-------

Ce cours fonctionne donc uniquement avec Python3.

N'utilisez *pas* Python2, sinon certaines choses expliquées ici ne marcheront pas :/



+ 9
- 4
cours/source/01-introduction/index.rst Ver fichero

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


.. toctree::
:maxdepth: 1

01-introduction
02-le-langage-python

+ 6
- 8
cours/source/02-premiers-pas/01-bases-ligne-de-commandes.rst Ver fichero

@@ -1,11 +1,8 @@
+++
title = "Ligne de commande"
weight = 2
+++
La ligne de commande
===================

# La ligne de commande

## Pourquoi la ligne de commande?
Pourquoi la ligne de commande?
------------------------------

* Très puissant
* Ancien, mais toujours d'actualité
@@ -13,7 +10,8 @@ weight = 2
* É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
Les bases
----------

On tape un commande, on appuie sur entrée, l'ordinateur interprète ce qui a été tapé et affiche un message:



+ 19
- 19
cours/source/02-premiers-pas/02-interpréteur.rst Ver fichero

@@ -1,30 +1,30 @@
+++
title = "L'interpréteur interactif"
weight = 4
+++
# L'interpréteur interactif
L'interpréteur interactif
=========================

## Installation
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.
>>>
```
.. code-block:: console

## Deux invites de commandes
$ 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.
>>>

Notez les trois chevrons: `>>>`. Cela vous permet de différencier l'invite
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()`
* Système d'exploitation -> Python: taper ``python3`` (sans arguments)
* Python -> Système d'exploitation: taper ``quit()``

## Note
Note
-----


À partir de maintenant, recopiez les entrées sur les slides dans votre propre
@@ -32,4 +32,4 @@ 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/).
Si ce n'est pas le cas, relisez attentitivement ce que vous avez tapé, sinon `contactez-moi <https://dmerej.info/blog/fr/pages/about/>`_.

+ 41
- 41
cours/source/02-premiers-pas/03-maths-simples.rst Ver fichero

@@ -1,62 +1,62 @@
+++
title = "Maths simples"
weight = 5
+++
Maths simples
=============

# Maths simples
Opérations avec des entiers
---------------------------

## Opérations avec des entiers
.. code-block:: python

```
>>> 1
1
>>> 2
2
>>> 1 + 2
3
>>> 2 * 3
6
```
>>> 1
1
>>> 2
2
>>> 1 + 2
3
>>> 2 * 3
6


## Opérantions avec des flottants
Opérations avec des flottants
-----------------------------

C'est le `.` qui fait le flottant
C'est le ``.`` qui fait le flottant

```
>>> 0.5
0.5
>>> 0.5 + 0.2
0.7
>>> 10 / 3
3.3333333333333335
```

.. code-block:: python

>>> 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
Division entières et modulo
---------------------------

.. code-block:: python

```
>>> 14 // 3
4
>>> 14 % 3
2
```
>>> 14 // 3
4
>>> 14 % 3
2

*Le `%` n'a rien à voir avec un pourcentage!*


## Priorité des opérations
Priorité des opérations
------------------------


```
>>> 1 + 2 * 3
7
>>> (1 + 2) * 3
9
```
.. code-block:: python

*Les parenthèses permettent de grouper les expressions*
>>> 1 + 2 * 3
7
>>> (1 + 2) * 3
9

*Les parenthèses permettent de grouper les expressions*

+ 9
- 4
cours/source/02-premiers-pas/index.rst Ver fichero

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

.. toctree::
:maxdepth: 1

01-bases-ligne-de-commandes
02-interpréteur.rst
03-maths-simples.rst

+ 24
- 28
cours/source/03-variables-et-types/01-variables.rst Ver fichero

@@ -1,31 +1,27 @@
+++
title = "Variables"
weight = 6
+++

# Variables

```python
>>> a = 2
>>> a
2
>>> b = 3
>>> a + b
5
```
Variables
=========

.. code-block:: 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
```
.. code-block:: python
>>> a = 2
>>> a
2
>>> a = 3
>>> a
3

* On peut changer la valeur d'une variable (d'où son nom)

@@ -34,7 +30,8 @@ weight = 6
variables par leurs valeurs


## Nom des variables
Nom des variables
-----------------

Préférez des noms longs et descriptifs

@@ -42,8 +39,7 @@ Toujours en minuscules

Séparez les "mots" par des tirets bas (underscore)

```python
>>> score = 42
>>> age_moyen = 22
```
.. code-block:: python

score = 42
age_moyen = 22

+ 31
- 34
cours/source/03-variables-et-types/02-chaînes-de-caractères.rst Ver fichero

@@ -1,60 +1,57 @@
+++
title = "Chaînes de caractères"
weight = 7
+++

# Chaînes de caractères
Chaînes de caractères
======================

Aussi appelées "string".


Avec des simple quotes (`'`)
Avec des simple quotes (``'``)

.. code-block:: python

```python
>>> 'Bonjour monde!'
'Bonjour monde!'
```
>>> 'Bonjour monde!'
'Bonjour monde!'

Marche aussi avec des double quotes (`"`)

```python
>>> "Bonjour, monde!"
'Bonjour monde'
```
.. code-block:: python

## Double et simple quotes
>>> "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."
.. code-block:: 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!'
```
>>> 'Il a dit: "bonjour" ce matin'
'Il a dit: "bonjour" ce matin!'


## Échappement
Échappement
-----------


Avec la barre oblique inversée "backslash"


```python
>>> 'Il a dit: "bonjour". C\'est sympa!'
'Il a dit: "bonjour". C\'est sympa!'
```
.. code-block:: python

>>> 'Il a dit: "bonjour". C\'est sympa!'
'Il a dit: "bonjour". C\'est sympa!'

## Concaténation

Concaténation
-------------

```python
>>> name = "John"
>>> message = "Bonjour " + name + " !"
>>> message
"Bonjour John !"
```

.. code-block:: python

>>> name = "John"
>>> message = "Bonjour " + name + " !"
>>> message
"Bonjour John !"

+ 22
- 25
cours/source/03-variables-et-types/03-types.rst Ver fichero

@@ -1,39 +1,36 @@
+++
title = "Types"
weight = 8
+++
Types
=====

# Types
.. code-block:: python

```python
>>> a = 42
>>> message = "La réponse est: " + a
TypeError: can only concatenate str (not "int") to str
```
>>> 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
Conversions
-----------

### Entier vers string
Entier vers string
++++++++++++++++++

```python
>>> a = 42
>>> message = "La réponse est: " + str(a)
>>> message
'La réponse est 42'
```
.. code-block:: python

>>> a = 42
>>> message = "La réponse est: " + str(a)
>>> message
'La réponse est 42'

### String vers entier
String vers entier
++++++++++++++++++

```python
>>> answer = int("42")
>>> answer
42
```
.. code-block:: python

Notez les parenthèses autour des valeurs.
>>> answer = int("42")
>>> answer
42

Notez les parenthèses autour des valeurs.

+ 21
- 24
cours/source/03-variables-et-types/04-booléens.rst Ver fichero

@@ -1,43 +1,40 @@
+++
title = "Booléens et conditions"
weight = 9
+++
Booléens et conditions
======================

# Booléens et conditions

## True et False
True et False
--------------


En Python ce sont des mots-clés et les valeurs sont en majuscules!


## Assignation
Assignation
-----------

On peut assigner des variables aux valeurs True et False


```
>>> la_terre_est_plate = False
...
>>> python_c_est_genial = True
```
.. code-block:: python

la_terre_est_plate = False
python_c_est_genial = True


## Comparaisons

```
>>> a = 2
>>> b = 3
>>> a > b
False
```
.. code-block:: python

```
>>> 2 + 2 == 4
True
```
>>> a = 2
>>> b = 3
>>> a > b
False

.. code-block:: python

>>> 2 + 2 == 4
True

Note: `==` pour la comparaison, `=` pour l'assignation
Note: ``==`` pour la comparaison, ``=`` pour l'assignation


```


+ 10
- 4
cours/source/03-variables-et-types/index.rst Ver fichero

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

.. toctree::
:maxdepth: 1

01-variables
02-chaînes-de-caractères
03-types
04-booléens

+ 0
- 107
cours/source/04-code-source/01-code-source.rst Ver fichero

@@ -1,107 +0,0 @@
+++
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

+ 98
- 4
cours/source/04-code-source/index.rst Ver fichero

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

Non persistance des variables
------------------------------

.. code-block:: console

$ python3
>>> a = 2
>>> quit()
```

.. code-block:: console

$ 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

.. code-block:: python

# Affiche un message
print("Bonjour, monde")


Sauvegardez dans un fichier `bonjour.py` dans `Documents/e2l/python` par exemple


Lancer du code en ligne de commande
-----------------------------------


.. code-block:: console

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


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


+ 24
- 24
cours/source/05-flot-de-controle/01-flot-de-contrôle.rst Ver fichero

@@ -1,22 +1,19 @@
+++
title = "Flôt de contrôle"
weight = 11
+++

# Flot de contrôle
Flot de contrôle
================

L'essence de la programmation!


## if
if
--

```python
a = 3
b = 4
if a == b:
print("a et b sont égaux")
print("on continue")
```
.. code-block:: python
a = 3
b = 4
if a == b:
print("a et b sont égaux")
print("on continue")


Notes:
@@ -26,17 +23,16 @@ Notes:
* 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:
après le if. Ceci ne fonctionne pas::

```python
if a = 3:
print("a égale 3")
```
if a = 3:
print("a égale 3")

et fait une erreur de syntaxe


## if / else
if / else
---------

```python
a = 3
@@ -48,7 +44,8 @@ else:
```


## if / elif
if / elif
--------

```python
if age < 10:
@@ -65,7 +62,8 @@ On peut mettre autont de `elif` qu'on veut!
Le derier `else` s'éxécute en dernier


## while
while
-----

Répéter tant qu'une condition est vraie

@@ -83,7 +81,8 @@ while i < 3:
```


## Notre première boucle infinie
Notre première boucle infinie
-----------------------------

```python
while True:
@@ -93,7 +92,8 @@ while True:
CTRL-C pour interrompre


## Combiner while et if
Combiner while et if
--------------------

On peut "sortir" de la boucle `while` avec `break`



+ 16
- 17
cours/source/05-flot-de-controle/02-exercice.rst Ver fichero

@@ -1,13 +1,10 @@
+++
title = "Exercice"
weight = 12
+++

# Exercice
Exercice
========

// TODO: explication des exercises

## Lire une entrée utilisateur
Lire une entrée utilisateur
----------------------------

* `input()` (encore des parenthèses ...)

@@ -15,24 +12,26 @@ weight = 12
* lit ce que l'utilisateur tape jusqu'à ce qu'il tape "entrée".
* renvoie une string

## Le jeu
Le jeu
------

On fait deviner un nombre à l'utilisateur, en affichant 'trop grand', 'trop petit'
jusqu'à ce qu'il trouve la valeur exacte.

## Squelette
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)
.. code-block:: 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")
print("devine le nombre auquel je pense")

# votre code ici
```
# votre code ici

+ 8
- 4
cours/source/05-flot-de-controle/index.rst Ver fichero

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

.. toctree::
:maxdepth: 1

01-flot-de-contrôle
02-exercice

+ 33
- 43
cours/source/06-fonctions/01-functions.rst Ver fichero

@@ -1,30 +1,26 @@
+++
title = "Fonctions"
weight = 1
+++
Fonctions
=========

# Fonctions
Fonction sans argument
---------------------

## Fonction sans argument
Définition::

def dire_bonjour():
print("Bonjour")

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
```
Appel::
>>> dire_bonjour()
Bonjour

* avec le nom de la fonction et des parenthèses

## Le pouvoir des fonctions
Le pouvoir des fonctions
------------------------

Ici on vient de créer une nouvelle fonctionnalité
à Python. Avant qu'on définisse la fonction
@@ -37,39 +33,33 @@ c'est une technique extrêmement utile en
programmation.


## Fonction avec un argument

Définition: avec l'argument à l'intérieur des parenthèses
Fonction avec un argument

```python
def dire_bonjour(prénom):
print("Bonjour " + prénom)
```
Définition: avec l'argument à l'intérieur des parenthèses::

Appel: en passant une variable ou une valeur dans les parenthèses
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
>>> dire_bonjour("Germaine")
Bonjour Germaine

>>> prénom_de_charlotte = "Charlotte"
>>> dire_bonjour(prénom_de_charlotte)
Bonjour Charlotte
```
>>> prénom_de_charlotte = "Charlotte"
>>> dire_bonjour(prénom_de_charlotte)
Bonjour Charlotte

## Exécution d'une fonction
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
dans le corps::

```python
# Ceci:
dire_bonjour("Dimitri")
# Ceci:
dire_bonjour("Dimitri")

# Est équivalent à cela:
prénom_de_dimitri = "Dimitri"
print("Bonjour " + prénom_de_dimitri)
# Est équivalent à cela:
prénom_de_dimitri = "Dimitri"
print("Bonjour " + prénom_de_dimitri)

# Lui-même équivalent à:
print("Bonjour " + "Dimitri")
```
# Lui-même équivalent à:
print("Bonjour " + "Dimitri")

+ 18
- 28
cours/source/06-fonctions/02-portée-des-variables.rst Ver fichero

@@ -1,39 +1,29 @@
+++
title = "Portée des variables"
weight = 2
+++
Portée des variables
====================

# Portée des variables
Les arguments d'une fonction n'existent que dans le corps de celle-ci::

Les arguments d'une fonction n'existent que dans le corps de celle-ci
def dire_bonjour(prénom):
print("Bonjour " + prénom)

```python
def dire_bonjour(prénom):
print("Bonjour " + prénom)
dire_bonjour("Dimitri") # Ok
print(prénom) # Erreur

dire_bonjour("Dimitri") # Ok
print(prénom) # Erreur
```

Les variables en dehors des fonctions sont disponibles partout::

Les variables en dehors des fonctions sont disponibles partout:
salutation = "Bonjour "

```python
salutation = "Bonjour "
def dire_bonjour(prénom):
print(salutation + prénom)

def dire_bonjour(prénom):
print(salutation + prénom)
dire_bonjour("Dimitri")

dire_bonjour("Dimitri")
```
Une variable peut avoir en "cacher" une autre si elle a une portée différente::

Une variable peut avoir en "cacher" une autre si elle a une portée différente
def dire_bonjour(prénom):
print("Bonjour " + prénom) # portée: uniquement dans
# le corps dire_bonjour

```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
```
prénom = "Dimitri" # portée: dans tout le programme
dire_bonjour(prénom) # Ok

+ 20
- 34
cours/source/06-fonctions/03-plusieurs-arguments.rst Ver fichero

@@ -1,43 +1,29 @@
+++
title = "Fonctions à plusieurs arguments"
weight = 2
+++

# Fonctions à plusieurs arguments
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
par des virgules::

En Python, on peut aussi utiliser le *nom* des arguments au lieu de
leur position:
def soustraction(x, y):
résultat = x - y
return résultat

```python
def dire_bonjour(prénom):
print("Bonjour " + prénom)
```
résultat = soustraction(5, 4)
print(résultat)
# affiche: 1

```python
>>> dire_bonjour(prénom="Gertrude")
Bonjour Gertrude
Arguments nommés
----------------

>>> afficher_addition(y=3, x=4)
7
```
En Python, on peut aussi utiliser le *nom* des arguments au lieu de
leur position::

def dire_bonjour(prénom):
print("Bonjour " + prénom)

// TODO: soustraction
dire_bonjour(prénom="Gertrude")
# Affiche: Bonjour Gertrude

résultat = soustraction(y=4, x=5)
print(résultat)
# affiche: 1

+ 0
- 1
cours/source/06-fonctions/04 Ver fichero

@@ -1 +0,0 @@


+ 15
- 24
cours/source/06-fonctions/04-par-défaut.rst Ver fichero

@@ -1,28 +1,19 @@
+++
title = "Arguments par défaut"
weight = 4
+++
Arguments par défaut
====================

# Arguments par défaut
On peut aussi mettre des valeurs par défaut::

On peut aussi mettre des valeurs par défaut:
def dire_bonjour(prénom, enthousiaste=False):
message = "Bonjour " + prénom
if enthousiaste:
message += "!"
print(message)

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
```
Appel::

>>> dire_bonjour("Thomas", enthousiaste=True)
Bonjour Thomas!
>>> dire_bonjour("Thomas", enthousiaste=False)
Bonjour Thomas
>>> dire_bonjour("Thomas")
Bonjour Thomas

+ 20
- 30
cours/source/06-fonctions/05-fonctions-natives.rst Ver fichero

@@ -1,46 +1,36 @@
+++
title = "Fonctions natives"
weight = 5
+++

# Fonctions natives
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
* ``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
Retour sur print
----------------

On peut passer autant d'arguments qu'on veut à `print` et:
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
```
* Ajoute un retour à la ligne à la fin::

On peut demander à `print` de changer son séparateur:
>>> prénom = "Charlotte"
print("Bonjour", pŕenom)
Bonjour Charlotte

```python
>>> a = "chauve"
>>> b = "souris"
>>> print(a, b, sep="-")
chauve-souris
```
On peut demander à `print` de changer son séparateur::

Ou de changer le caractère de fin:
```python
>>> print("Ceci tient", end="")
>>> print("sur une seule ligne")
Ceci tient sur une seule ligne
```
>>> a = "chauve"
>>> b = "souris"
>>> print(a, b, sep="-")
chauve-souris

Ou de changer le caractère de fin::

>>> print("Ceci tient", end="")
>>> print("sur une seule ligne")
Ceci tient sur une seule ligne

+ 34
- 43
cours/source/06-fonctions/06-return.rst Ver fichero

@@ -1,43 +1,34 @@
+++
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
```
Valeur de retour d'une fonction
=================================

Définition avec le mot ``return``::

def additionner(x, y):
return x + y

Récupérer la valeur de retour::

a = 3
b = 4
c = additionner(a, b) # encore une assignation
print(c)
# Affche: 7

Sortir d'une fonction avec return
---------------------------------

``return`` interrompt également l'éxécution du
corps de la fonction::

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

>>> dire_bonjour("Dimitri", première_fois=True)
Bonjour Dimitri
Heureux de faire votre connaissance

>>> dire_bonjour("Dimitri", première_fois=False)
Bonjour Dimitri

+ 12
- 4
cours/source/06-fonctions/index.rst Ver fichero

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

.. toctree::
:maxdepth: 1

01-functions.rst
02-portée-des-variables.rst
03-plusieurs-arguments.rst
04-par-défaut.rst
05-fonctions-natives.rst
06-return.rst

+ 0
- 164
cours/source/07-listes/01-listes.rst Ver fichero

@@ -1,164 +0,0 @@
+++
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
```

+ 150
- 4
cours/source/07-listes/index.rst Ver fichero

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

// TODO: split in pages

Définition
----------

Une liste est une _suite ordonée_ d'éléments.

Créer une liste
---------------

Avec des crochets: ``[``, ``]``, et les élements séparés par des virgules::

liste_vide = []
trois_entiers = [1, 2, 3]


Listes hétérogènes
------------------

On peut mettre des types différents dans la même liste::

ma_liste = [True, 2, "trois"]

On peut aussi mettre des listes dans des listes::

liste_de_listes = [[1, 2], ["Germaine", "Gertrude"]]

Connaître la taille d'une liste
-------------------------------

Avec ``len()`` - encore une fonction native::

>>> liste_vide = []
>>> len(liste_vide)
0
>>> trois_entiers = [1, 2, 3]
>>> len(trois_entiers)
3

Concaténation de listes
-----------------------

Avec ``+``::

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

>>> scores = [1, 2, 3]
>>> scores += 4 # TypeError
>>> scores += [4] # OK

Test d'appartenance
-------------------

Avec ``in``::

>>> prénoms = ["Alice", "Bob"]
>>> "Alice" in prénoms
True

>>> prénoms = ["Alice", "Bob"]
>>> "Charlie" in prénoms
False

Itérer sur les élements d'une liste
------------------------------------

Avec ``for ... in``::

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


+ 71
- 89
cours/source/08-none-et-pass/01-none.rst Ver fichero

@@ -1,98 +1,80 @@
+++
title = "None"
weight = 1
+++
None
====

# None
Définition
-----------

## Définition
``None`` est une "valeur magique" natif en python. il est toujours présent, et il est unique.

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

Un peu comme `True` et `False` qui sont deux valeurs qui servent à représenter tous les booléens.
Représenter l'absence
----------------------

## Représenter l'absence
L'interpréteur intéractif n'affiche rien quand la valeur est None::

L'interpréteur intéractif n'affiche rien quand la valeur est None
>>> a = 42
>>> a
42
>>> b = None
>>> b

```python
>>> a = 42
>>> a
42
>>> b = None
>>> b
```

## Retourner None
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
...
```
elle ne contiennent pas le mot-clé ``return``.::

def ne_renvoie_rien():
print("je ne fais qu'afficher quelque chose")

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

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

.. code-block:: python

def trouve_dans_liste(valeur, liste):
for element in liste:
if element == valeur:
return element
return None

>>> trouve_dans_liste(2, [1, 2, 3])
2
>>> trouve_dans_liste(False, [True, False])
False
>>> trouve_dans_liste(1, [3, 4])


None est Falsy, et on peut vérifier si une variable vaut ``None`` avec ``is None``::

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

+ 11
- 24
cours/source/08-none-et-pass/02-pass.rst Ver fichero

@@ -1,34 +1,21 @@
+++
title = "pass"
weight = 2
+++

# pass
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:
après un if::

```python
une_condition = False
if une_condition:
pass
else:
print("une_condition n'est pas vraie")
```
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
```
définir une fonction qui ne fait rien::

```python
>>> ne_fait_rien()
<rien>
```
def ne_fait_rien():
pass

+ 8
- 4
cours/source/08-none-et-pass/index.rst Ver fichero

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

.. toctree::
:maxdepth: 1

01-none
02-pass

+ 0
- 205
cours/source/09-dictionnaires/01-dictionnaires.rst Ver fichero

@@ -1,205 +0,0 @@
+++
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()` ...


+ 176
- 4
cours/source/09-dictionnaires/index.rst Ver fichero

@@ -1,4 +1,176 @@
+++
title = "Chapitre 9 - Dictionnaires"
weight = 9
+++
Chapitre 9 - 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
-------------------------

Avec des accolades: ``{``, ``}`` ::

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

>>> scores = {"john": 10, "bob": 42}
>>> scores["john"]
10
>>> scores["bob"]
42
>>> scores["charlie"]
KeyError

Test d'appartenance
---------------------

Avec ``in``, comme le listes::

>>> scores = {"john": 10, "bob": 42}
>>> "charlie" in scores
False

Modifier la valeur d'une clé
-----------------------------

Comme pour les listes, avec une assignation::

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

>>> scores = {"john": 10, "bob": 42}
>>> scores["charlie"] = 30
>>> scores
{"john": 20, "bob": 42, "charlie": 30}

*rappel*: ceci ne fonctionne pas avec les listes!::
>>> 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::

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)

del
---

Détruire une clé
+++++++++++++++++

Avec ``del`` - un nouveau mot-clé::

>>> scores = {"john": 10, "bob": 42}
>>> del scores["bob"]
>>> scores
{"john": 10}

Détruire un élément d'une liste
++++++++++++++++++++++++++++++++

Aussi avec ``del``::

>>> fruits = ["pomme", "banane", "poire"]
>>> del fruits[1]
>>> fruits
["pomme", "poire"]

Détruire une variable
+++++++++++++++++++++

Encore et toujours ``del``::

>>> mon_entier = 42
>>> mon_entier += 3
>>> mon_entier
45
>>> del mon_entier
>>> mon_entier == 45
NameError: name 'mon_entier' is not defined

Des dictionnaires partout
---------------------------

Les variables globales d'un programme Python sont dans un dictionnaire,
accessible avec la fonction native `globals()`::

$ python3
>>> globals()
{
...
'__doc__': None,
'__name__': '__main__',
...
}

On reparlera de `__doc__` et `__name__` un autre jour ...::


$ python3
>>> a = 42
>>> globals()
{
...
'__doc__': None,
'__name__': '__main__',
...
'a': 42
}
```

.. code-block::

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()``::

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
- 169
cours/source/10-tuples/01-tuples.rst Ver fichero

@@ -1,169 +0,0 @@
+++
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!

+ 151
- 4
cours/source/10-tuples/index.rst Ver fichero

@@ -1,4 +1,151 @@
+++
title = "Chapitre 10 - tuples"
weight = 10
+++
Chapitre 10 - 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
------------------

Avec des parenthèses::

tuple_vide = ()
tuple_à_un_élement = (1,) # notez la virgule
tupble_à_deux_éléments = (1, 2) # on dit aussi: "couple"
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::

# 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::

mon_tuple = (42, "bonjour")
mon_tuple[0]
42
mon_tuple[1]
"bonjour"

Modification
------------

Interdit::

mon_tuple = (42, "bonjour")
mon_tuple[0] = 44
TypeError: 'tuple' object does not support item assignment


Test d'appartenance
-------------------

Avec ``in``:

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

>>> couple = ("Batman", "Robin")
>>> héros, side_kick = couple
>>> héros
'Batman'
>>> side_kick
'Robin'


Quelques erreurs classiques
---------------------------

.. code-block:: 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
------

.. code-block::

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

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

# Avant:
if (
ma_valeur == "nord" or
ma_valeur == "sud" or
ma_valeur == "ouest" or
ma_valeur == "est"):
print("direction", ma_valeur)

# Après:
if ma_valeur in ("nord", "sud", "est", "ouest"):
print("direction", ma_valeur)

Pour retourner plusieurs valeurs::

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
- 319
cours/source/11-classes-01/01-classes.rst Ver fichero

@@ -1,319 +0,0 @@
+++
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.

+ 296
- 4
cours/source/11-classes-01/index.rst Ver fichero

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

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

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

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

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

y = a.x

Ici, ``y`` est l'attribut ``x`` de l'instance ``a``.

Les attributs peuvent être des fonctions::

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

a.x(10)

On peut *créer* des attributs dans *n'importe quel instance*, en utilisant l'*assignation*::

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

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

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

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

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


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

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


>>> mon_instance = MaClasse()
>>> mon_instance.méthode_2()

.. code-block::

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 nomée ``__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::


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__``::

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

.. note::

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
- 148
cours/source/12-modules-01/01-modules.rst Ver fichero

@@ -1,148 +0,0 @@
+++
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 ...

+ 135
- 4
cours/source/12-modules-01/index.rst Ver fichero

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

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

# 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
>>> 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é


Si maintenant on rajoute une fonction ``dire_bonjour`` dans ``bonjour.py``::

# 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``::

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

>>> import bonjour
>>> bonjour.dire_bonjour()
Bonjour!
>>> bonjour.dire_bonjour()
Bonjour!

On peut aussi modifier les valeurs des attributs::

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

# Dans bonjour.py
print("Je suis le module bonjour et tu viens de m’importer")

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

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


+ 128
- 137
cours/source/13-classes-02/01-rappels.rst Ver fichero

@@ -1,186 +1,177 @@
+++
title = "Rappels"
weight = 1
+++
Rappels
======

# Rappels
// TODO: drop?

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

Instanciation:
```python
>>> instance_1 = MaClasse()
```
Définition::

class MaClasse:
pass

## Attributs
Instanciation::

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:
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
```
>>> 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
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:
Définition::

```python
class MaClasse:
def ma_méthode(self):
class MaClasse:
def ma_méthode(self):
return 42
>>> ma_méthode()
Erreur
>>> mon_instance = MaClasse()
>>> mon_instance.ma_méthode()
42

Les méthodes sont des attributs des instances de classes::

class MaClasse:
def ma_méthode(self):
return 42
>>> ma_méthode()
Erreur
>>> mon_instance = MaClasse()
>>> mon_instance.ma_méthode()
42
```


## self
self
----

`self` *prend la valeur de l'instance courante* quand la méthode est appelée.
`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)
class MaClasse:
def affiche_attribut_x(self):
print(self.x)

>>> mon_instance = MaClasse()
>>> mon_instance.x = 42
>>> mon_instance.affiche_attribut_x()
42
```
>>> mon_instance = MaClasse()
>>> mon_instance.x = 42
>>> mon_instance.affiche_attribut_x()
42

On peut aussi *créer* des attributs dans une méthode:
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)
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 = 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
```
>>> mon_instance.crée_attribut_x()
>>> mon_instance.affiche_attribut_x()
42

## Méthodes avec arguments
Méthodes avec arguments
------------------------

```python
class MaClasse
def crée_attribut_x(self, valeur_de_x):
self.x = valeur_de_x
.. code-block::

def affiche_attribut_x(self);
print(self.x)
class MaClasse
def crée_attribut_x(self, valeur_de_x):
self.x = valeur_de_x

>>> mon_instance = MaClasse()
>>> mon_instance.crée_attribut_x(42)
>>> mon_instance.affiche_attribut_x()
42
```
def affiche_attribut_x(self);
print(self.x)

## Méthodes appelant d'autres méthodes
>>> mon_instance = MaClasse()
>>> mon_instance.crée_attribut_x(42)
>>> mon_instance.affiche_attribut_x()
42

```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")
Méthodes appelant d'autres méthodes
------------------------------------

.. code-block::

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


```python
>>> mon_instance = MaClasse()
>>> mon_instance.méthode_2()
```
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")

```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
>>> mon_instance = MaClasse()
>>> mon_instance.méthode_2()

.. code-block::

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

Un constructeur en Python désigne la méthode nomée `__init__`,
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:
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
class MaClasse:
def __init__(self):
self.x = 1
self.y = 2

>>> mon_instance = MaClasse()
>>> mon_instance.x
1
>>> mon_instance.y
2
```
>>> mon_instance = MaClasse()
>>> mon_instance.x
1
>>> mon_instance.y
2

## Constructeur avec arguments
Constructeur avec arguments
----------------------------

La méthode `__init__` peut avoir des arguments,
La méthode ``__init__`` peut avoir des arguments,
dans ce cas, ceux ci doivent être fournis
lors de l'instanciation:
lors de l'instanciation::

```python
class MaClasse:
def __init__(self, x, y):
self.x = x
self.y = y
```
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
```
>>> mon_instance = MaClasse(3, 4)
>>> mon_instance.x
3

>>> mon_instance.y
4

+ 65
- 74
cours/source/13-classes-02/02-couplage.rst Ver fichero

@@ -1,91 +1,82 @@
+++
title = "Couplage"
weight = 2
+++
Couplage
========

# Couplage

## Définition
Définition
----------

Un couplage décrit une relation entre deux classes.

## Exemple
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`:
On peut utiliser pour cela deux classes: `Chat` et `Humain`::

```python
class Chat:
def __init__(self, nom):
self.nom = nom
class Chat:
def __init__(self, nom):
self.nom = nom

>>> chat = Chat("Monsieur Moustaches")
>>> chat.nom
'Monsieur Moustaches'
```
>>> 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"
```
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`.
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
classe ``Chat``::

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

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

.. code-block::

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

+ 64
- 75
cours/source/13-classes-02/03-composition.rst Ver fichero

@@ -1,117 +1,106 @@
+++
title = "Composition"
weight = 3
+++
Composition
============

# Composition

## Définition
Définition
-----------

Une classe à l'intérieur d'une autre classe.

## Dépendances entre fonctions
Dépendances entre fonctions
-----------------------------

Exemple: on veut dessiner un sapin dans le terminal:
Exemple: on veut dessiner un sapin dans le terminal::

```python
def main():
largeur = demander_largeur()
dessine_sapin(largeur)
def main():
largeur = demander_largeur()
dessine_sapin(largeur)

main()
```
main()


On voit que la fonction `dessine_sapin()` prend un argument `largeur`, qui est retourné
par la fonction `demander_largeur()`.
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()`.
``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
Dépendances entre classes
-------------------------

Un bon moyen d'introduire une dépendance entre deux classes est d'utiliser les constructeurs.

Revoyons la classe Chat:
Revoyons la classe Chat::

```python
class Chat:
def __init__(self, nom):
self.nom = nome
```
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:
des chats sans nom::

```python
>>> chat = Chat()
TypeError: __init__() missing 1 required positional argument: 'nom'
```
>>> 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:
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
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'
>>> 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!
```
>>> boule_de_poils = Chat("Boule de Poils")
>>> alice = Enfant("Alice", boule_de_poils)
# OK!

## Utilisation de la composition
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:
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
class Chat:
def __init__(self, nom):
self.nom = nom

def ronronne(self):
print(self.nom, 'fait: "prrrrr"')
def ronronne(self):
print(self.nom, 'fait: "prrrrr"')

def caresse(self):
self.ronronne()
def caresse(self):
self.ronronne()


>>> boule_de_poils = Chat("Boule de Poils")
>>> boule_de_poils.caresse()
Boule de Poils fait "prrrrr"
```
>>> 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,
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
* 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
class Enfant:
def __init__(self, prénom, chat):
self.chat = chat

def console(self):
self.chat.caresse()
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 :)
```
>>> 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.
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.

+ 9
- 4
cours/source/13-classes-02/index.rst Ver fichero

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

.. toctree::
:maxdepth: 1

01-rappels
02-couplage
03-composition

+ 20
- 23
cours/source/14-bibliothèques-01/01-rappels.rst Ver fichero

@@ -1,42 +1,39 @@
+++
title = "Introduction"
weight = 1
+++
Introduction
============

# Introduction

## Importer un module
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]:
``bar``:::

```python
import foo
foo.bar()
```
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
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.
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
```
.. code-block:: console
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`.
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``.

+ 32
- 414
cours/source/14-bibliothèques-01/02-sys.path.rst Ver fichero

@@ -1,439 +1,57 @@
+++
title = "sys.path"
weight = 2
+++
sys.path
========

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

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:

Si j'essaye de l'afficher sur ma machine, voici ce que j'obtiens :
import sys
print(sys.path)

```python
import sys
print(sys.path)
```
.. code-block:: text

```
[
"",
"/usr/lib/python3.8",
"/usr/lib/python3.8/lib-dynload",
"/home/dmerej/.local/lib/python3.8/",
"/usr/lib/python3.8/site-packages",
]
```
[
"",
"/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.
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.
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
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
é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.
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.
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!
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
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).
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._
La situation est semblable sur macOS et Windows.

+ 243
- 69
cours/source/14-bibliothèques-01/03-bibliotheques-tierces.rst Ver fichero

@@ -1,106 +1,280 @@
+++
title = "Bibliothèques tierces"
weight = 3
+++
Bibliothèques tierces
=====================

# Bibliothèques tierces
Prenons un exemple::

Prenons un exemple :
# dans foo.py
import tabulate

```python
# dans foo.py
import tabulate
scores = [
["John", 345],
["Mary-Jane", 2],
["Bob", 543],
]
table = tabulate.tabulate(scores)
print(table)

scores = [
["John", 345],
["Mary-Jane", 2],
["Bob", 543],
]
table = tabulate.tabulate(scores)
print(table)
```
.. code-block:: console

```
$ python3 foo.py
--------- ---
John 345
Mary-Jane 2
Bob 543
--------- ---
```
$ 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.
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`?
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
Le gestionnaire de paquets
---------------------------

Si vous utilisez une distribution Linux, peut-être pourrez-vous utiliser votre gestionnaire de paquets&nbsp;:
Si vous utilisez une distribution Linux, peut-être pourrez-vous utiliser votre gestionnaire de paquets:

```bash
$ sudo apt install python3-tabulate
```
.. code-block:: console

Comme vous lancez votre gestionnaire de paquets avec `sudo`, celui-ci sera capable d'écrire dans les chemins système de `sys.path`.
$ sudo apt install python3-tabulate

## À la main
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`
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
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`
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 :
Le fichier ``setup.py`` contient en général simplement un ``import`` de
``setuptools``, et un appel à la fonction ``setup()``, avec de nombreux
arguments::

# 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``, d'où l'utilisation de l'option ``--user``.

Voici à quoi ressemble la sortie de la commande:

.. code-block:: console

$ 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::

import cli_ui

cli_ui.info("Ceci est en", cli_ui.red, "rouge")

Elle permet également d'afficher des tableaux en couleur::

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

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

.. code-block:: console

$ 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:

.. code-block:: console

$ 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:*

.. code-block::

# Old (<= 0.7)
ask_choice("select a fruit", ["apple", "banana"])
# New (>= 0.8)
ask_choice("select a fruit", choices=["apple", "banana"])

```python
# tabulate/setup.py
from setuptools import setup
Ceci s'appelle un *changement d'API*.

setup(
name='tabulate',
version='0.8.1',
description='Pretty-print tabular data',
py_modules=["tabulate"],
scripts=["bin/tabulate"],
...
)
```
Réagir aux changements d'API
-----------------------------

Plusieurs possibilités:

## Résultat de l'invocation de setup.py
* 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::

setup(
install_requires=[
"cli-ui < 0.8",
...
]
)

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`.
Aparté : pourquoi éviter sudo pip
---------------------------------

Voici à quoi ressemble la sortie de la commande :
Souvenez-vous que les fichiers systèmes sont contrôlés par votre gestionnaire de paquets.

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

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`.
Mais il y a un autre problème encore pire.

Notez également qu'un script a été installé dans `~/.local/bin` - Une bibliothèque Python peut contenir aussi bien des modules que des scripts.
Conflit de dépendances
----------------------

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


+ 113
- 94
cours/source/14-bibliothèques-01/04-dépendances.rst Ver fichero

@@ -1,147 +1,166 @@
+++
title = "Dépendances"
weight = 4
+++
Dépendances
===========

# Dépendances
Prenons une autre bibliothèque : ``cli-ui``.

## Un autre exemple
Elle permet d'afficher du texte en couleur dans un terminal::

Prenons une autre bibliothèque : `cli-ui`.
import cli_ui

Elle permet d'afficher du texte en couleur dans un terminal
cli_ui.info("Ceci est en", cli_ui.red, "rouge")

```python
import cli_ui
Elle permet également d'afficher des tableaux en couleur::

cli_ui.info("Ceci est en", cli_ui.red, "rouge")
```
headers=["name", "score"]
data = [
[(bold, "John"), (green, 10.0)],
[(bold, "Jane"), (green, 5.0)],
]
cli_ui.info_table(data, headers=headers)

Elle permet également d'afficher des tableaux en couleur :
Pour ce faire, elle repose sur la bibliothèque ``tabulate`` vue
précédemment. On dit que ``cli-ui`` *dépend* de ``tabulate``.

```python
headers=["prénom", "score"]
data = [
[(bold, "John"), (green, 10.0)],
[(bold, "Jane"), (green, 5.0)],
]
cli_ui.info_table(data, headers=headers)
```
Déclaration des dépendances
----------------------------

Pour ce faire, elle repose sur la bibliothèque `tabulate` vue précédemment. On dit que `cli-ui` *dépend* de `tabulate`.
La déclaration de la dépendance de ``cli-ui`` vers ``tabulate`` s'effectue également dans le fichier ``setup.py``::

## Déclaration des dépendances
setup(
name="cli-ui",
version="0.9.1",
install_requires=[
"tabulate",
...
],
...
)

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

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

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

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

Comme de nombreux outils, il s'utilise à l'aide de *commandes*. Voici comment installer `cli-ui` à l'aide de la commande 'install' de `pip`:
.. code-block:: console

```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
```
$ 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`:
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
* 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`.
* 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:
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
```
.. code-block:: console

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

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)>`_:

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

> the list of choices used by ask_choice is now a named keyword argument:
.. code-block::

```python
# Old (<= 0.7)
ask_choice("select a fruit", ["apple", "banana"])
# New (>= 0.8)
ask_choice("select a fruit", choices=["apple", "banana"])
```
# 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
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 :
* 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",
...
]
)
```
setup(
install_requires=[
"cli-ui < 0.8",
...
]
)

# Aparté : pourquoi éviter sudo pip
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.
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.
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
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 ?
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 ?

+ 120
- 89
cours/source/14-bibliothèques-01/05-virtualenv.rst Ver fichero

@@ -1,137 +1,168 @@
+++
title = "Environnements virtuels"
weight = 5
+++
Environnements virtuels
========================

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

La solution à la question de la fin du chapitre précédent est d'utiliser un *environnement virtuel*
(*virtualenv* en abrégé).
Il se crée par exemple avec la commande ``python3 -m venv foo-venv``. où
``foo-venv`` est un répertoire quelconque.

C'est un répertoire *isolé* du reste du système.
Aparté : python3 -m venv sur Debian
------------------------------------

Il se crée par exemple avec la commande `python3 -m venv foo-venv`. où `foo-venv` est un répertoire quelconque.
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.

## Aparté : python3 -m venv sur Debian
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 :

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

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 :
Comportement de python dans le virtualenv
-----------------------------------------

* 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`.
Ce répertoire contient de nombreux fichiers et dossiers, et notamment un
binaire dans ``foo-venv/bin/python3``.

## Comportement de python dans le virtualenv
Voyons comment il se comporte en le comparant au binaire ``/usr/bin/python3``
habituel:

Ce répertoire contient de nombreux fichiers et dossiers, et notamment un binaire dans `foo-venv/bin/python3`.
.. code-block:: console

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'
]

```
$ /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'
]
.. code-block:: console

$ /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,
]
```
$ /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)
* 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`.
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.
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.
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.
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
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.
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.
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
.. code-block:: console

$ /home/dmerej/foo-venv/bin/python3 -m pip freeze
# rien :)
```
$ 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

On peut alors utiliser le module `pip` *du virtualenv* pour installer des bibliothèques dans celui-ci :
.. code-block:: console

```
$ /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
```
$ /home/dmerej/foo-venv/bin/python3 -m pip freeze
# rien :)

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 peut alors utiliser le module ``pip`` *du virtualenv* pour installer des
bibliothèques dans celui-ci :

.. code-block:: console

$ /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
d'avoir effectivement deux versions différentes de ``cli-ui``, isolées les
unes des autres.

## Activer un virtualenv
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 :
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
* ``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,
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 :
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:

.. code-block:: console

```
# Avant
user@host:~/src $ source foo-env/bin/activate
# Après
(foo-env) user@host:~/src $
```
# Avant
user@host:~/src $ source foo-env/bin/activate
# Après
(foo-env) user@host:~/src $

Pour sortir du virtualenv, entrez la commande `deactivate`.
Pour sortir du virtualenv, entrez la commande ``deactivate``.

## Conclusion
Conclusion
----------

Le système de gestions des dépendances de Python peut paraître compliqué et bizarre, surtout venant d'autres langages.
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 :
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
* 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).
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).

+ 12
- 4
cours/source/14-bibliothèques-01/index.rst Ver fichero

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

.. toctree::
:maxdepth: 1

01-rappels
02-sys.path
03-bibliotheques-tierces
04-dépendances
05-virtualenv


+ 1
- 0
cours/source/conf.py Ver fichero

@@ -12,6 +12,7 @@ language = "fr"
templates_path = ["_templates"]
exclude_patterns = []

html_show_sourcelink = False
html_theme_path = [sphinx_nameko_theme.get_html_theme_path()]
html_theme = "nameko"
html_static_path = ["_static"]

+ 15
- 15
cours/source/index.rst Ver fichero

@@ -2,20 +2,20 @@ Programmation en Python
=======================

.. toctree::
:maxdepth: 2
:maxdepth: 1
:caption: Table des matières:

01-introduction/index
02-premiers-pas/index
03-variables-et-types/index
04-code-source/index
05-flot-de-controle/index
06-fonctions/index
07-listes/index
08-none-et-pass/index
09-dictionnaires/index
10-tuples/index
11-classes-01/index
12-modules-01/index
13-classes-02/index
14-bibliothèques-01/index
01-introduction/index
02-premiers-pas/index
03-variables-et-types/index
04-code-source/index
05-flot-de-controle/index
06-fonctions/index
07-listes/index
08-none-et-pass/index
09-dictionnaires/index
10-tuples/index
11-classes-01/index
12-modules-01/index
13-classes-02/index
14-bibliothèques-01/index