@@ -6,9 +6,11 @@ def main(): | |||||
dev = "--dev" in sys.argv | dev = "--dev" in sys.argv | ||||
if dev: | if dev: | ||||
program = "sphinx-autobuild" | program = "sphinx-autobuild" | ||||
opts = [] | |||||
else: | else: | ||||
program = "sphinx-build" | 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) | subprocess.run(cmd, check=True) | ||||
@@ -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 | Apprendre la programmation en partant de rien, en utilisant Python et la ligne de commande | ||||
# Pourquoi Python? | |||||
Pourquoi Python? | |||||
---------------- | |||||
* Excellent langage pour débuter | * Excellent langage pour débuter | ||||
* Mon langage préféré | * Mon langage préféré | ||||
* Vraiment cross-platform (sauf pour le mobile) | * Vraiment cross-platform (sauf pour le mobile) | ||||
# Pourquoi la ligne de commande? | |||||
Pourquoi la ligne de commande? | |||||
------------------------------ | |||||
Interface universelle | Interface universelle | ||||
@@ -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` | * Aussi appelé "langage de script", `glue language` | ||||
@@ -19,7 +16,8 @@ weight = 3 | |||||
* Ligne de commande | * Ligne de commande | ||||
* ... | * ... | ||||
## Petit détour: version d'un programme | |||||
Petit détour: version d'un programme | |||||
------------------------------------ | |||||
* Comme les versions d'un document | * Comme les versions d'un document | ||||
* Si le nombre est plus grand, c'est plus récent | * 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 | * `1.5.1 -> 4.3`: beaucoup de changements | ||||
* On omet souvent le reste des numéros quand c'est pas nécessaire | * 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. | * Créé par Guido van Rossum. Conçu à la base pour l'enseignement. | ||||
* Le nom vient des Monty Python (si, si) | * Le nom vient des Monty Python (si, si) | ||||
@@ -37,7 +36,8 @@ weight = 3 | |||||
* Python 2: en 2000 | * Python 2: en 2000 | ||||
* Python 3: en 2008 | * Python 3: en 2008 | ||||
## Le grand schisme | |||||
Le grand schisme | |||||
---------------- | |||||
La plupart des langages continuent à être compatibles d'une version à l'autre. | 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. | 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. | Ce cours fonctionne donc uniquement avec Python3. | ||||
N'utilisez *pas* Python2, sinon certaines choses expliquées ici ne marcheront pas :/ | N'utilisez *pas* Python2, sinon certaines choses expliquées ici ne marcheront pas :/ | ||||
@@ -1,4 +1,9 @@ | |||||
+++ | |||||
title = "Chapitre 1 - Introduction" | |||||
weight = 1 | |||||
+++ | |||||
Chapitre 1 - Introduction | |||||
========================= | |||||
.. toctree:: | |||||
:maxdepth: 1 | |||||
01-introduction | |||||
02-le-langage-python |
@@ -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 | * Très puissant | ||||
* Ancien, mais toujours d'actualité | * Ancien, mais toujours d'actualité | ||||
@@ -13,7 +10,8 @@ weight = 2 | |||||
* Écrire des programmes qui marche dans la ligne de commande est (relativement) simple | * É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 | * 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: | On tape un commande, on appuie sur entrée, l'ordinateur interprète ce qui a été tapé et affiche un message: | ||||
@@ -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: | 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. | 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 | À 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. | 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/>`_. |
@@ -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* | *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!* | *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* |
@@ -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 |
@@ -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 nommer des valeurs | ||||
* On peut afficher la valeur d'une variable entrant son nom dans le REPL | * 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) | * On peut changer la valeur d'une variable (d'où son nom) | ||||
@@ -34,7 +30,8 @@ weight = 6 | |||||
variables par leurs valeurs | variables par leurs valeurs | ||||
## Nom des variables | |||||
Nom des variables | |||||
----------------- | |||||
Préférez des noms longs et descriptifs | Préférez des noms longs et descriptifs | ||||
@@ -42,8 +39,7 @@ Toujours en minuscules | |||||
Séparez les "mots" par des tirets bas (underscore) | Séparez les "mots" par des tirets bas (underscore) | ||||
```python | |||||
>>> score = 42 | |||||
>>> age_moyen = 22 | |||||
``` | |||||
.. code-block:: python | |||||
score = 42 | |||||
age_moyen = 22 |
@@ -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". | 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 (`"`) | 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. | 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" | 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 !" |
@@ -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 !* | *Notre premier message d'erreur !* | ||||
On ne mélange pas les torchons et les serviettes! | 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. |
@@ -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! | 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 | 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 | ## 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 | |||||
``` | ``` | ||||
@@ -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 |
@@ -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 |
@@ -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 | |||||
@@ -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! | 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: | Notes: | ||||
@@ -26,17 +23,16 @@ Notes: | |||||
* si la condition n'est pas vraie, rien ne se passe | * si la condition n'est pas vraie, rien ne se passe | ||||
Notez qu'on peut mettre uniquement une variable ou une valeur | 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 | et fait une erreur de syntaxe | ||||
## if / else | |||||
if / else | |||||
--------- | |||||
```python | ```python | ||||
a = 3 | a = 3 | ||||
@@ -48,7 +44,8 @@ else: | |||||
``` | ``` | ||||
## if / elif | |||||
if / elif | |||||
-------- | |||||
```python | ```python | ||||
if age < 10: | if age < 10: | ||||
@@ -65,7 +62,8 @@ On peut mettre autont de `elif` qu'on veut! | |||||
Le derier `else` s'éxécute en dernier | Le derier `else` s'éxécute en dernier | ||||
## while | |||||
while | |||||
----- | |||||
Répéter tant qu'une condition est vraie | 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 | ```python | ||||
while True: | while True: | ||||
@@ -93,7 +92,8 @@ while True: | |||||
CTRL-C pour interrompre | CTRL-C pour interrompre | ||||
## Combiner while et if | |||||
Combiner while et if | |||||
-------------------- | |||||
On peut "sortir" de la boucle `while` avec `break` | On peut "sortir" de la boucle `while` avec `break` | ||||
@@ -1,13 +1,10 @@ | |||||
+++ | |||||
title = "Exercice" | |||||
weight = 12 | |||||
+++ | |||||
# Exercice | |||||
Exercice | |||||
======== | |||||
// TODO: explication des exercises | // TODO: explication des exercises | ||||
## Lire une entrée utilisateur | |||||
Lire une entrée utilisateur | |||||
---------------------------- | |||||
* `input()` (encore des parenthèses ...) | * `input()` (encore des parenthèses ...) | ||||
@@ -15,24 +12,26 @@ weight = 12 | |||||
* lit ce que l'utilisateur tape jusqu'à ce qu'il tape "entrée". | * lit ce que l'utilisateur tape jusqu'à ce qu'il tape "entrée". | ||||
* renvoie une string | * renvoie une string | ||||
## Le jeu | |||||
Le jeu | |||||
------ | |||||
On fait deviner un nombre à l'utilisateur, en affichant 'trop grand', 'trop petit' | On fait deviner un nombre à l'utilisateur, en affichant 'trop grand', 'trop petit' | ||||
jusqu'à ce qu'il trouve la valeur exacte. | jusqu'à ce qu'il trouve la valeur exacte. | ||||
## Squelette | |||||
Squelette | |||||
-------- | |||||
// TODO: | // TODO: | ||||
* explication du Squelette | * explication du Squelette | ||||
* pas de solution! | * 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 |
@@ -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 |
@@ -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 `def` | ||||
* avec un `:` à la fin et un _bloc indenté_ (appelé le "corps") | * 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 | * 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é | Ici on vient de créer une nouvelle fonctionnalité | ||||
à Python. Avant qu'on définisse la fonction | à Python. Avant qu'on définisse la fonction | ||||
@@ -37,39 +33,33 @@ c'est une technique extrêmement utile en | |||||
programmation. | 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 | 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") |
@@ -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 |
@@ -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 | 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 |
@@ -1 +0,0 @@ | |||||
@@ -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 |
@@ -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: | 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! | Il y en a tout un tas! | ||||
La liste ici: https://docs.python.org/fr/3/library/functions.html#built-in-funcs | 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 | * 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 |
@@ -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 |
@@ -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 |
@@ -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 | |||||
``` |
@@ -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 | |||||
``` | |||||
@@ -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 | 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 | |||||
... |
@@ -1,34 +1,21 @@ | |||||
+++ | |||||
title = "pass" | |||||
weight = 2 | |||||
+++ | |||||
# pass | |||||
pass | |||||
==== | |||||
En Python, à cause de l'organisation en blocs indentés, on ne | En Python, à cause de l'organisation en blocs indentés, on ne | ||||
peut pas vraiment avoir de blocs vides. Mais parfois, on | peut pas vraiment avoir de blocs vides. Mais parfois, on | ||||
a besoin d'un bloc qui ne fasse rien. | a besoin d'un bloc qui ne fasse rien. | ||||
Dans ce cas, on peut utiliser le mot-clé `pass`, par exemple | 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 | 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 |
@@ -1,4 +1,8 @@ | |||||
+++ | |||||
title = "Chapitre 8 - None et pass" | |||||
weight = 8 | |||||
+++ | |||||
Chapitre 8 - None et pass | |||||
========================= | |||||
.. toctree:: | |||||
:maxdepth: 1 | |||||
01-none | |||||
02-pass |
@@ -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()` ... | |||||
@@ -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()`` ... |
@@ -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! |
@@ -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! |
@@ -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. |
@@ -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. | |||||
@@ -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 ... |
@@ -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 ... | |||||
@@ -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). | 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: | 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 | 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. | 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 | 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 |
@@ -1,91 +1,82 @@ | |||||
+++ | |||||
title = "Couplage" | |||||
weight = 2 | |||||
+++ | |||||
Couplage | |||||
======== | |||||
# Couplage | |||||
## Définition | |||||
Définition | |||||
---------- | |||||
Un couplage décrit une relation entre deux classes. | 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. | 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. | 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. | 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 | 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. | 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``. |
@@ -1,117 +1,106 @@ | |||||
+++ | |||||
title = "Composition" | |||||
weight = 3 | |||||
+++ | |||||
Composition | |||||
============ | |||||
# Composition | |||||
## Définition | |||||
Définition | |||||
----------- | |||||
Une classe à l'intérieur d'une autre classe. | 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. | 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 | 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 | 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 | 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 | par exemple consoler tous les enfants en leur demandant de caresser leur chat, chat | ||||
qui va ronronner et faire plaisir à son propriétaire. | 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: | 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. |
@@ -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 |
@@ -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 | 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 | 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. | 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: | 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 | 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 | 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é. | 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``. |
@@ -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: | Le résultat dépend: | ||||
* du système d'exploitation | * du système d'exploitation | ||||
* de la façon dont Python a été installé | * de la façon dont Python a été installé | ||||
* et de la présence ou non de certains réportoires. | * 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 | 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. | 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 | En fait, vous trouverez la plupart des modules de la bibliothèque standard dans | ||||
ce répertoire. | 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 : | |||||
```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. |
@@ -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. | 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 : | |||||
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. | 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 : | 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. | 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 ? | |||||
@@ -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. | 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 | la distribution | ||||
En revanche, `pip` contient de nombreuses fonctionnalités intéressantes: | 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*. | 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*. | Ceci s'appelle un *changement d'API*. | ||||
## Réagir aux changements d'API | |||||
Réagir aux changements d'API | |||||
----------------------------- | |||||
Plusieurs possibilités: | 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. | 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 | 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. | 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 ? |
@@ -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: | À 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é | * 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 a enfin notre solution pour résoudre notre conflit de dépendances : | ||||
on peut simplement créer un virtualenv par projet. Ceci nous permettra | 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. | 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. | 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 | * 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). |
@@ -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 | |||||
@@ -12,6 +12,7 @@ language = "fr" | |||||
templates_path = ["_templates"] | templates_path = ["_templates"] | ||||
exclude_patterns = [] | exclude_patterns = [] | ||||
html_show_sourcelink = False | |||||
html_theme_path = [sphinx_nameko_theme.get_html_theme_path()] | html_theme_path = [sphinx_nameko_theme.get_html_theme_path()] | ||||
html_theme = "nameko" | html_theme = "nameko" | ||||
html_static_path = ["_static"] | html_static_path = ["_static"] |
@@ -2,20 +2,20 @@ Programmation en Python | |||||
======================= | ======================= | ||||
.. toctree:: | .. toctree:: | ||||
:maxdepth: 2 | |||||
:maxdepth: 1 | |||||
:caption: Table des matières: | :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 |