@@ -6,9 +6,11 @@ def main(): | |||
dev = "--dev" in sys.argv | |||
if dev: | |||
program = "sphinx-autobuild" | |||
opts = [] | |||
else: | |||
program = "sphinx-build" | |||
cmd = [program, "-d", "build", "-b", "html", "source", "build/html"] | |||
opts = ["-W"] | |||
cmd = [program, *opts, "-d", "build", "-b", "html", "source", "build/html"] | |||
subprocess.run(cmd, check=True) | |||
@@ -1,19 +1,17 @@ | |||
+++ | |||
title = "Objet de ce livre" | |||
weight = 1 | |||
+++ | |||
# Objet de ce livre | |||
Objet de ce livre | |||
================= | |||
Apprendre la programmation en partant de rien, en utilisant Python et la ligne de commande | |||
# Pourquoi Python? | |||
Pourquoi Python? | |||
---------------- | |||
* Excellent langage pour débuter | |||
* Mon langage préféré | |||
* Vraiment cross-platform (sauf pour le mobile) | |||
# Pourquoi la ligne de commande? | |||
Pourquoi la ligne de commande? | |||
------------------------------ | |||
Interface universelle | |||
@@ -1,11 +1,8 @@ | |||
+++ | |||
title = "Le langage Python" | |||
weight = 3 | |||
+++ | |||
Présentation du langage Python | |||
============================== | |||
# Présentation du langage Python | |||
## Utilisation de Python | |||
Utilisation de Python | |||
---------------------- | |||
* Aussi appelé "langage de script", `glue language` | |||
@@ -19,7 +16,8 @@ weight = 3 | |||
* Ligne de commande | |||
* ... | |||
## Petit détour: version d'un programme | |||
Petit détour: version d'un programme | |||
------------------------------------ | |||
* Comme les versions d'un document | |||
* Si le nombre est plus grand, c'est plus récent | |||
@@ -29,7 +27,8 @@ weight = 3 | |||
* `1.5.1 -> 4.3`: beaucoup de changements | |||
* On omet souvent le reste des numéros quand c'est pas nécessaire | |||
## Historique | |||
Historique | |||
---------- | |||
* Créé par Guido van Rossum. Conçu à la base pour l'enseignement. | |||
* Le nom vient des Monty Python (si, si) | |||
@@ -37,7 +36,8 @@ weight = 3 | |||
* Python 2: en 2000 | |||
* Python 3: en 2008 | |||
## Le grand schisme | |||
Le grand schisme | |||
---------------- | |||
La plupart des langages continuent à être compatibles d'une version à l'autre. | |||
@@ -45,10 +45,9 @@ La plupart des langages continuent à être compatibles d'une version à l'autre | |||
Heureusement, 10 ans plus tard, la situation s'est arrangée, et Python2 cessera d'être maintenu le premier janvier 2020. | |||
## Python3 | |||
Python3 | |||
------- | |||
Ce cours fonctionne donc uniquement avec Python3. | |||
N'utilisez *pas* Python2, sinon certaines choses expliquées ici ne marcheront pas :/ | |||
@@ -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 | |||
* Ancien, mais toujours d'actualité | |||
@@ -13,7 +10,8 @@ weight = 2 | |||
* Écrire des programmes qui marche dans la ligne de commande est (relativement) simple | |||
* Possibilités infines, même si on ne fait que manipuler du texte | |||
## Les bases | |||
Les bases | |||
---------- | |||
On tape un commande, on appuie sur entrée, l'ordinateur interprète ce qui a été tapé et affiche un message: | |||
@@ -1,30 +1,30 @@ | |||
+++ | |||
title = "L'interpréteur interactif" | |||
weight = 4 | |||
+++ | |||
# L'interpréteur interactif | |||
L'interpréteur interactif | |||
========================= | |||
## Installation | |||
Installation | |||
------------ | |||
Il se lance depuis l'invite de commande du système d'exploitation: | |||
``` | |||
$ python3 | |||
Python 3.7.1 (default, Oct 22 2018, 10:41:28) | |||
[GCC 8.2.1 20180831] on linux | |||
Type "help", "credits" or "license" for more information. | |||
>>> | |||
``` | |||
.. code-block:: console | |||
## Deux invites de commandes | |||
$ python3 | |||
Python 3.7.1 (default, Oct 22 2018, 10:41:28) | |||
[GCC 8.2.1 20180831] on linux | |||
Type "help", "credits" or "license" for more information. | |||
>>> | |||
Notez les trois chevrons: `>>>`. Cela vous permet de différencier l'invite | |||
Deux invites de commandes | |||
------------------------- | |||
Notez les trois chevrons: ``>>>``. Cela vous permet de différencier l'invite | |||
de commandes du système d'exploitation de celle de Python. | |||
* Système d'exploitation -> Python: taper `python3` (sans arguments) | |||
* Python -> Système d'exploitation: taper `quit()` | |||
* Système d'exploitation -> Python: taper ``python3`` (sans arguments) | |||
* Python -> Système d'exploitation: taper ``quit()`` | |||
## Note | |||
Note | |||
----- | |||
À partir de maintenant, recopiez les entrées sur les slides dans votre propre | |||
@@ -32,4 +32,4 @@ interpréteur. | |||
Vous devez taper la même chose après l'invite de commande ('>>>') et vous devez voir les mêmes réponses. | |||
Si ce n'est pas le cas, relisez attentitivement ce que vous avez tapé, sinon [contactez-moi](https://dmerej.info/blog/fr/pages/about/). | |||
Si ce n'est pas le cas, relisez attentitivement ce que vous avez tapé, sinon `contactez-moi <https://dmerej.info/blog/fr/pages/about/>`_. |
@@ -1,62 +1,62 @@ | |||
+++ | |||
title = "Maths simples" | |||
weight = 5 | |||
+++ | |||
Maths simples | |||
============= | |||
# Maths simples | |||
Opérations avec des entiers | |||
--------------------------- | |||
## Opérations avec des entiers | |||
.. code-block:: python | |||
``` | |||
>>> 1 | |||
1 | |||
>>> 2 | |||
2 | |||
>>> 1 + 2 | |||
3 | |||
>>> 2 * 3 | |||
6 | |||
``` | |||
>>> 1 | |||
1 | |||
>>> 2 | |||
2 | |||
>>> 1 + 2 | |||
3 | |||
>>> 2 * 3 | |||
6 | |||
## Opérantions avec des flottants | |||
Opérations avec des flottants | |||
----------------------------- | |||
C'est le `.` qui fait le flottant | |||
C'est le ``.`` qui fait le flottant | |||
``` | |||
>>> 0.5 | |||
0.5 | |||
>>> 0.5 + 0.2 | |||
0.7 | |||
>>> 10 / 3 | |||
3.3333333333333335 | |||
``` | |||
.. code-block:: python | |||
>>> 0.5 | |||
0.5 | |||
>>> 0.5 + 0.2 | |||
0.7 | |||
>>> 10 / 3 | |||
3.3333333333333335 | |||
*Note: les flottants sont imprécis* | |||
## Division entières et modulo | |||
Division entières et modulo | |||
--------------------------- | |||
.. code-block:: python | |||
``` | |||
>>> 14 // 3 | |||
4 | |||
>>> 14 % 3 | |||
2 | |||
``` | |||
>>> 14 // 3 | |||
4 | |||
>>> 14 % 3 | |||
2 | |||
*Le `%` n'a rien à voir avec un pourcentage!* | |||
## Priorité des opérations | |||
Priorité des opérations | |||
------------------------ | |||
``` | |||
>>> 1 + 2 * 3 | |||
7 | |||
>>> (1 + 2) * 3 | |||
9 | |||
``` | |||
.. code-block:: python | |||
*Les parenthèses permettent de grouper les expressions* | |||
>>> 1 + 2 * 3 | |||
7 | |||
>>> (1 + 2) * 3 | |||
9 | |||
*Les parenthèses permettent de grouper les expressions* |
@@ -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 afficher la valeur d'une variable entrant son nom dans le REPL | |||
```python | |||
>>> a = 2 | |||
>>> a | |||
2 | |||
>>> a = 3 | |||
>>> a | |||
3 | |||
``` | |||
.. code-block:: python | |||
>>> a = 2 | |||
>>> a | |||
2 | |||
>>> a = 3 | |||
>>> a | |||
3 | |||
* On peut changer la valeur d'une variable (d'où son nom) | |||
@@ -34,7 +30,8 @@ weight = 6 | |||
variables par leurs valeurs | |||
## Nom des variables | |||
Nom des variables | |||
----------------- | |||
Préférez des noms longs et descriptifs | |||
@@ -42,8 +39,7 @@ Toujours en minuscules | |||
Séparez les "mots" par des tirets bas (underscore) | |||
```python | |||
>>> score = 42 | |||
>>> age_moyen = 22 | |||
``` | |||
.. code-block:: python | |||
score = 42 | |||
age_moyen = 22 |
@@ -1,60 +1,57 @@ | |||
+++ | |||
title = "Chaînes de caractères" | |||
weight = 7 | |||
+++ | |||
# Chaînes de caractères | |||
Chaînes de caractères | |||
====================== | |||
Aussi appelées "string". | |||
Avec des simple quotes (`'`) | |||
Avec des simple quotes (``'``) | |||
.. code-block:: python | |||
```python | |||
>>> 'Bonjour monde!' | |||
'Bonjour monde!' | |||
``` | |||
>>> 'Bonjour monde!' | |||
'Bonjour monde!' | |||
Marche aussi avec des double quotes (`"`) | |||
```python | |||
>>> "Bonjour, monde!" | |||
'Bonjour monde' | |||
``` | |||
.. code-block:: python | |||
## Double et simple quotes | |||
>>> "Bonjour, monde!" | |||
'Bonjour monde' | |||
Double et simple quotes | |||
----------------------- | |||
On peut mettre des simples quotes dans des double quotes et vice-versa. | |||
```python | |||
>>> "Il a dit: 'bonjour' ce matin." | |||
"Il a dit: 'bonjour' ce matin." | |||
.. code-block:: python | |||
>>> "Il a dit: 'bonjour' ce matin." | |||
"Il a dit: 'bonjour' ce matin." | |||
>>> 'Il a dit: "bonjour" ce matin' | |||
'Il a dit: "bonjour" ce matin!' | |||
``` | |||
>>> 'Il a dit: "bonjour" ce matin' | |||
'Il a dit: "bonjour" ce matin!' | |||
## Échappement | |||
Échappement | |||
----------- | |||
Avec la barre oblique inversée "backslash" | |||
```python | |||
>>> 'Il a dit: "bonjour". C\'est sympa!' | |||
'Il a dit: "bonjour". C\'est sympa!' | |||
``` | |||
.. code-block:: python | |||
>>> 'Il a dit: "bonjour". C\'est sympa!' | |||
'Il a dit: "bonjour". C\'est sympa!' | |||
## Concaténation | |||
Concaténation | |||
------------- | |||
```python | |||
>>> name = "John" | |||
>>> message = "Bonjour " + name + " !" | |||
>>> message | |||
"Bonjour John !" | |||
``` | |||
.. code-block:: python | |||
>>> name = "John" | |||
>>> message = "Bonjour " + name + " !" | |||
>>> message | |||
"Bonjour John !" |
@@ -1,39 +1,36 @@ | |||
+++ | |||
title = "Types" | |||
weight = 8 | |||
+++ | |||
Types | |||
===== | |||
# Types | |||
.. code-block:: python | |||
```python | |||
>>> a = 42 | |||
>>> message = "La réponse est: " + a | |||
TypeError: can only concatenate str (not "int") to str | |||
``` | |||
>>> a = 42 | |||
>>> message = "La réponse est: " + a | |||
TypeError: can only concatenate str (not "int") to str | |||
*Notre premier message d'erreur !* | |||
On ne mélange pas les torchons et les serviettes! | |||
## Conversions | |||
Conversions | |||
----------- | |||
### Entier vers string | |||
Entier vers string | |||
++++++++++++++++++ | |||
```python | |||
>>> a = 42 | |||
>>> message = "La réponse est: " + str(a) | |||
>>> message | |||
'La réponse est 42' | |||
``` | |||
.. code-block:: python | |||
>>> a = 42 | |||
>>> message = "La réponse est: " + str(a) | |||
>>> message | |||
'La réponse est 42' | |||
### String vers entier | |||
String vers entier | |||
++++++++++++++++++ | |||
```python | |||
>>> answer = int("42") | |||
>>> answer | |||
42 | |||
``` | |||
.. code-block:: python | |||
Notez les parenthèses autour des valeurs. | |||
>>> answer = int("42") | |||
>>> answer | |||
42 | |||
Notez les parenthèses autour des valeurs. |
@@ -1,43 +1,40 @@ | |||
+++ | |||
title = "Booléens et conditions" | |||
weight = 9 | |||
+++ | |||
Booléens et conditions | |||
====================== | |||
# Booléens et conditions | |||
## True et False | |||
True et False | |||
-------------- | |||
En Python ce sont des mots-clés et les valeurs sont en majuscules! | |||
## Assignation | |||
Assignation | |||
----------- | |||
On peut assigner des variables aux valeurs True et False | |||
``` | |||
>>> la_terre_est_plate = False | |||
... | |||
>>> python_c_est_genial = True | |||
``` | |||
.. code-block:: python | |||
la_terre_est_plate = False | |||
python_c_est_genial = True | |||
## Comparaisons | |||
``` | |||
>>> a = 2 | |||
>>> b = 3 | |||
>>> a > b | |||
False | |||
``` | |||
.. code-block:: python | |||
``` | |||
>>> 2 + 2 == 4 | |||
True | |||
``` | |||
>>> a = 2 | |||
>>> b = 3 | |||
>>> a > b | |||
False | |||
.. code-block:: python | |||
>>> 2 + 2 == 4 | |||
True | |||
Note: `==` pour la comparaison, `=` pour l'assignation | |||
Note: ``==`` pour la comparaison, ``=`` pour l'assignation | |||
``` | |||
@@ -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! | |||
## if | |||
if | |||
-- | |||
```python | |||
a = 3 | |||
b = 4 | |||
if a == b: | |||
print("a et b sont égaux") | |||
print("on continue") | |||
``` | |||
.. code-block:: python | |||
a = 3 | |||
b = 4 | |||
if a == b: | |||
print("a et b sont égaux") | |||
print("on continue") | |||
Notes: | |||
@@ -26,17 +23,16 @@ Notes: | |||
* si la condition n'est pas vraie, rien ne se passe | |||
Notez qu'on peut mettre uniquement une variable ou une valeur | |||
après le if. Ceci ne fonctionne pas: | |||
après le if. Ceci ne fonctionne pas:: | |||
```python | |||
if a = 3: | |||
print("a égale 3") | |||
``` | |||
if a = 3: | |||
print("a égale 3") | |||
et fait une erreur de syntaxe | |||
## if / else | |||
if / else | |||
--------- | |||
```python | |||
a = 3 | |||
@@ -48,7 +44,8 @@ else: | |||
``` | |||
## if / elif | |||
if / elif | |||
-------- | |||
```python | |||
if age < 10: | |||
@@ -65,7 +62,8 @@ On peut mettre autont de `elif` qu'on veut! | |||
Le derier `else` s'éxécute en dernier | |||
## while | |||
while | |||
----- | |||
Répéter tant qu'une condition est vraie | |||
@@ -83,7 +81,8 @@ while i < 3: | |||
``` | |||
## Notre première boucle infinie | |||
Notre première boucle infinie | |||
----------------------------- | |||
```python | |||
while True: | |||
@@ -93,7 +92,8 @@ while True: | |||
CTRL-C pour interrompre | |||
## Combiner while et if | |||
Combiner while et if | |||
-------------------- | |||
On peut "sortir" de la boucle `while` avec `break` | |||
@@ -1,13 +1,10 @@ | |||
+++ | |||
title = "Exercice" | |||
weight = 12 | |||
+++ | |||
# Exercice | |||
Exercice | |||
======== | |||
// TODO: explication des exercises | |||
## Lire une entrée utilisateur | |||
Lire une entrée utilisateur | |||
---------------------------- | |||
* `input()` (encore des parenthèses ...) | |||
@@ -15,24 +12,26 @@ weight = 12 | |||
* lit ce que l'utilisateur tape jusqu'à ce qu'il tape "entrée". | |||
* renvoie une string | |||
## Le jeu | |||
Le jeu | |||
------ | |||
On fait deviner un nombre à l'utilisateur, en affichant 'trop grand', 'trop petit' | |||
jusqu'à ce qu'il trouve la valeur exacte. | |||
## Squelette | |||
Squelette | |||
-------- | |||
// TODO: | |||
* explication du Squelette | |||
* pas de solution! | |||
```python | |||
# faites moi confiance, les deux lignes ci-dessous | |||
# permettent de tirer un nombre au hasard entre 0 et 100 | |||
import random | |||
nombre = random.randint(0, 100) | |||
.. code-block:: python | |||
# faites moi confiance, les deux lignes ci-dessous | |||
# permettent de tirer un nombre au hasard entre 0 et 100 | |||
import random | |||
nombre = random.randint(0, 100) | |||
print("devine le nombre auquel je pense") | |||
print("devine le nombre auquel je pense") | |||
# votre code ici | |||
``` | |||
# votre code ici |
@@ -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 un `:` à la fin et un _bloc indenté_ (appelé le "corps") | |||
Appel: | |||
``` | |||
>>> dire_bonjour() | |||
Bonjour | |||
``` | |||
Appel:: | |||
>>> dire_bonjour() | |||
Bonjour | |||
* avec le nom de la fonction et des parenthèses | |||
## Le pouvoir des fonctions | |||
Le pouvoir des fonctions | |||
------------------------ | |||
Ici on vient de créer une nouvelle fonctionnalité | |||
à Python. Avant qu'on définisse la fonction | |||
@@ -37,39 +33,33 @@ c'est une technique extrêmement utile en | |||
programmation. | |||
## Fonction avec un argument | |||
Définition: avec l'argument à l'intérieur des parenthèses | |||
Fonction avec un argument | |||
```python | |||
def dire_bonjour(prénom): | |||
print("Bonjour " + prénom) | |||
``` | |||
Définition: avec l'argument à l'intérieur des parenthèses:: | |||
Appel: en passant une variable ou une valeur dans les parenthèses | |||
def dire_bonjour(prénom): | |||
print("Bonjour " + prénom) | |||
Appel: en passant une variable ou une valeur dans les parenthèses:: | |||
```python | |||
>>> dire_bonjour("Germaine") | |||
Bonjour Germaine | |||
>>> dire_bonjour("Germaine") | |||
Bonjour Germaine | |||
>>> prénom_de_charlotte = "Charlotte" | |||
>>> dire_bonjour(prénom_de_charlotte) | |||
Bonjour Charlotte | |||
``` | |||
>>> prénom_de_charlotte = "Charlotte" | |||
>>> dire_bonjour(prénom_de_charlotte) | |||
Bonjour Charlotte | |||
## Exécution d'une fonction | |||
Exécution d'une fonction | |||
------------------------ | |||
C'est exatement comme si on assignait les arguments de la fonction avant d'éxécuter le code | |||
dans le corps | |||
dans le corps:: | |||
```python | |||
# Ceci: | |||
dire_bonjour("Dimitri") | |||
# Ceci: | |||
dire_bonjour("Dimitri") | |||
# Est équivalent à cela: | |||
prénom_de_dimitri = "Dimitri" | |||
print("Bonjour " + prénom_de_dimitri) | |||
# Est équivalent à cela: | |||
prénom_de_dimitri = "Dimitri" | |||
print("Bonjour " + prénom_de_dimitri) | |||
# Lui-même équivalent à: | |||
print("Bonjour " + "Dimitri") | |||
``` | |||
# Lui-même équivalent à: | |||
print("Bonjour " + "Dimitri") |
@@ -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 | |||
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: | |||
* `print`, `input`: écrire et lire sur la ligne de commande | |||
* `str`, `int`: convertir des entiers en strings et vice-versa | |||
* ``print``, ``input``: écrire et lire sur la ligne de commande | |||
* ``str``, ``int``: convertir des entiers en strings et vice-versa | |||
Il y en a tout un tas! | |||
La liste ici: https://docs.python.org/fr/3/library/functions.html#built-in-funcs | |||
## Retour sur print | |||
Retour sur print | |||
---------------- | |||
On peut passer autant d'arguments qu'on veut à `print` et: | |||
On peut passer autant d'arguments qu'on veut à ``print`` et: | |||
* Il les sépare par des espaces | |||
* Ajoute un retour à la ligne à la fin: | |||
```python | |||
>>> prénom = "Charlotte" | |||
print("Bonjour", pŕenom) | |||
Bonjour Charlotte | |||
``` | |||
* Ajoute un retour à la ligne à la fin:: | |||
On peut demander à `print` de changer son séparateur: | |||
>>> prénom = "Charlotte" | |||
print("Bonjour", pŕenom) | |||
Bonjour Charlotte | |||
```python | |||
>>> a = "chauve" | |||
>>> b = "souris" | |||
>>> print(a, b, sep="-") | |||
chauve-souris | |||
``` | |||
On peut demander à `print` de changer son séparateur:: | |||
Ou de changer le caractère de fin: | |||
```python | |||
>>> print("Ceci tient", end="") | |||
>>> print("sur une seule ligne") | |||
Ceci tient sur une seule ligne | |||
``` | |||
>>> a = "chauve" | |||
>>> b = "souris" | |||
>>> print(a, b, sep="-") | |||
chauve-souris | |||
Ou de changer le caractère de fin:: | |||
>>> print("Ceci tient", end="") | |||
>>> print("sur une seule ligne") | |||
Ceci tient sur une seule ligne |
@@ -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 | |||
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 | |||
peut pas vraiment avoir de blocs vides. Mais parfois, on | |||
a besoin d'un bloc qui ne fasse rien. | |||
Dans ce cas, on peut utiliser le mot-clé `pass`, par exemple | |||
après un if: | |||
après un if:: | |||
```python | |||
une_condition = False | |||
if une_condition: | |||
pass | |||
else: | |||
print("une_condition n'est pas vraie") | |||
``` | |||
une_condition = False | |||
if une_condition: | |||
pass | |||
else: | |||
print("une_condition n'est pas vraie") | |||
On peut aussi - et c'est l'usage le plus courant - utiliser `pass` pour | |||
définir une fonction qui ne fait rien: | |||
```python | |||
def ne_fait_rien(): | |||
pass | |||
``` | |||
définir une fonction qui ne fait rien:: | |||
```python | |||
>>> ne_fait_rien() | |||
<rien> | |||
``` | |||
def ne_fait_rien(): | |||
pass |
@@ -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). | |||
La syntaxe consiste en l'instance à gauche et l'attribut à droite après un point: | |||
La syntaxe consiste en l'instance à gauche et l'attribut à droite après un point:: | |||
```python | |||
>>> mon_instance = MaClasse() | |||
# création de l'attribut `x` dans mon_instance: | |||
>>> mon_instance.x = 42 | |||
# accès à l'attribut `x` dans mon_instance: | |||
>>> mon_instance.x | |||
42 | |||
``` | |||
>>> mon_instance = MaClasse() | |||
# création de l'attribut `x` dans mon_instance: | |||
>>> mon_instance.x = 42 | |||
# accès à l'attribut `x` dans mon_instance: | |||
>>> mon_instance.x | |||
42 | |||
## Méthodes | |||
Méthodes | |||
-------- | |||
Une méthode est une fonction définie à l'intérieur d'une classe: | |||
Définition: | |||
```python | |||
class MaClasse: | |||
def ma_méthode(self): | |||
return 42 | |||
``` | |||
Les méthodes sont des attributs des instances de classes: | |||
Définition:: | |||
```python | |||
class MaClasse: | |||
def ma_méthode(self): | |||
class MaClasse: | |||
def ma_méthode(self): | |||
return 42 | |||
>>> ma_méthode() | |||
Erreur | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.ma_méthode() | |||
42 | |||
Les méthodes sont des attributs des instances de classes:: | |||
class MaClasse: | |||
def ma_méthode(self): | |||
return 42 | |||
>>> ma_méthode() | |||
Erreur | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.ma_méthode() | |||
42 | |||
``` | |||
## self | |||
self | |||
---- | |||
`self` *prend la valeur de l'instance courante* quand la méthode est appelée. | |||
`self` *prend la valeur de l'instance courante* quand la méthode est appelée.:: | |||
```python | |||
class MaClasse: | |||
def affiche_attribut_x(self): | |||
print(self.x) | |||
class MaClasse: | |||
def affiche_attribut_x(self): | |||
print(self.x) | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.x = 42 | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
``` | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.x = 42 | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
On peut aussi *créer* des attributs dans une méthode: | |||
On peut aussi *créer* des attributs dans une méthode:: | |||
```python | |||
class MaClasse: | |||
def crée_attribut_x(self): | |||
self.x = 42 | |||
def affiche_attribut_x(self): | |||
print(self.x) | |||
class MaClasse: | |||
def crée_attribut_x(self): | |||
self.x = 42 | |||
def affiche_attribut_x(self): | |||
print(self.x) | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.affiche_attribut_x() | |||
# Erreur: `mon_instance` n'a pas d'attribut `x` | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.affiche_attribut_x() | |||
# Erreur: `mon_instance` n'a pas d'attribut `x` | |||
>>> mon_instance.crée_attribut_x() | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
``` | |||
>>> mon_instance.crée_attribut_x() | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
## Méthodes avec arguments | |||
Méthodes avec arguments | |||
------------------------ | |||
```python | |||
class MaClasse | |||
def crée_attribut_x(self, valeur_de_x): | |||
self.x = valeur_de_x | |||
.. code-block:: | |||
def affiche_attribut_x(self); | |||
print(self.x) | |||
class MaClasse | |||
def crée_attribut_x(self, valeur_de_x): | |||
self.x = valeur_de_x | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.crée_attribut_x(42) | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
``` | |||
def affiche_attribut_x(self); | |||
print(self.x) | |||
## Méthodes appelant d'autres méthodes | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.crée_attribut_x(42) | |||
>>> mon_instance.affiche_attribut_x() | |||
42 | |||
```python | |||
class MaClasse: | |||
def méthode_1(self): | |||
print("démarrage de la méthode 1") | |||
print("la méthode 1 affiche bonjour") | |||
print("bonjour") | |||
print("fin de la méthode 1") | |||
Méthodes appelant d'autres méthodes | |||
------------------------------------ | |||
.. code-block:: | |||
def méthode_2(self): | |||
print("la méthode 2 appelle la méthode 1") | |||
self.méthode_1() | |||
print("fin de la méthode 2") | |||
``` | |||
class MaClasse: | |||
def méthode_1(self): | |||
print("démarrage de la méthode 1") | |||
print("la méthode 1 affiche bonjour") | |||
print("bonjour") | |||
print("fin de la méthode 1") | |||
```python | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.méthode_2() | |||
``` | |||
def méthode_2(self): | |||
print("la méthode 2 appelle la méthode 1") | |||
self.méthode_1() | |||
print("fin de la méthode 2") | |||
```text | |||
la méthode 2 appelle la méthode 1 | |||
démarrage de la méthode 1 | |||
la méthode 1 affiche bonjour | |||
bonjour | |||
fin de la méthode 1 | |||
fin de la méthode 2 | |||
``` | |||
## Constructeur sans arguments | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.méthode_2() | |||
.. code-block:: | |||
la méthode 2 appelle la méthode 1 | |||
démarrage de la méthode 1 | |||
la méthode 1 affiche bonjour | |||
bonjour | |||
fin de la méthode 1 | |||
fin de la méthode 2 | |||
Un constructeur en Python désigne la méthode nomée `__init__`, | |||
Constructeur sans arguments | |||
--------------------------- | |||
Un constructeur en Python désigne la méthode nomée ``__init__``, | |||
quand celle-ci existe. | |||
La méthode `__init__` est appelée automatiquement quand la | |||
classe est instanciée: | |||
La méthode ``__init__`` est appelée automatiquement quand la | |||
classe est instanciée:: | |||
```python | |||
class MaClasse: | |||
def __init__(self): | |||
self.x = 1 | |||
self.y = 2 | |||
class MaClasse: | |||
def __init__(self): | |||
self.x = 1 | |||
self.y = 2 | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.x | |||
1 | |||
>>> mon_instance.y | |||
2 | |||
``` | |||
>>> mon_instance = MaClasse() | |||
>>> mon_instance.x | |||
1 | |||
>>> mon_instance.y | |||
2 | |||
## Constructeur avec arguments | |||
Constructeur avec arguments | |||
---------------------------- | |||
La méthode `__init__` peut avoir des arguments, | |||
La méthode ``__init__`` peut avoir des arguments, | |||
dans ce cas, ceux ci doivent être fournis | |||
lors de l'instanciation: | |||
lors de l'instanciation:: | |||
```python | |||
class MaClasse: | |||
def __init__(self, x, y): | |||
self.x = x | |||
self.y = y | |||
``` | |||
class MaClasse: | |||
def __init__(self, x, y): | |||
self.x = x | |||
self.y = y | |||
```python | |||
>>> mon_instance = MaClasse(3, 4) | |||
>>> mon_instance.x | |||
3 | |||
>>> mon_instance.y | |||
4 | |||
``` | |||
>>> mon_instance = MaClasse(3, 4) | |||
>>> mon_instance.x | |||
3 | |||
>>> mon_instance.y | |||
4 |
@@ -1,91 +1,82 @@ | |||
+++ | |||
title = "Couplage" | |||
weight = 2 | |||
+++ | |||
Couplage | |||
======== | |||
# Couplage | |||
## Définition | |||
Définition | |||
---------- | |||
Un couplage décrit une relation entre deux classes. | |||
## Exemple | |||
Exemple | |||
------- | |||
Ici on veut représenter des chats et des humains qui adoptent (on non) des chats. | |||
Tous les chats ont un nom, et tous les humains ont un prénom. | |||
On peut utiliser pour cela deux classes: `Chat` et `Humain`: | |||
On peut utiliser pour cela deux classes: `Chat` et `Humain`:: | |||
```python | |||
class Chat: | |||
def __init__(self, nom): | |||
self.nom = nom | |||
class Chat: | |||
def __init__(self, nom): | |||
self.nom = nom | |||
>>> chat = Chat("Monsieur Moustaches") | |||
>>> chat.nom | |||
'Monsieur Moustaches' | |||
``` | |||
>>> chat = Chat("Monsieur Moustaches") | |||
>>> chat.nom | |||
'Monsieur Moustaches' | |||
```python | |||
class Humain: | |||
def __init__(self, prénom): | |||
self.prénom = prénom | |||
>>> alice = Humain(prénom="Alice") | |||
>>> alice.prénom | |||
"Alice" | |||
``` | |||
class Humain: | |||
def __init__(self, prénom): | |||
self.prénom = prénom | |||
>>> alice = Humain(prénom="Alice") | |||
>>> alice.prénom | |||
"Alice" | |||
Maintenant on veut que les humains puissent adopter des chats. | |||
Pour cela, on peut rajouter la méthode `adopte` dans la classe | |||
`Humain`. | |||
Pour cela, on peut rajouter la méthode ``adopte`` dans la classe | |||
``Humain``. | |||
Cette méthode va prendre un argument - une instance de la | |||
classe `Chat`: | |||
```python | |||
class Humain: | |||
def __init__(self, prénom): | |||
self.prénom = prénom | |||
def adopte(self, chat): | |||
print(self.prénom, "adopte un chat") | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Humain("Alice") | |||
>>> alice.adopte(boule_de_poils) | |||
"Alice adopte un chat" | |||
``` | |||
On peut accéder au nom du chat depuis la méthode `adopte`, | |||
en utilisant la syntaxe `nom.attribut` vue précédemment: | |||
```python | |||
class Humain: | |||
def __init__(self, prénom): | |||
self.prénom = prénom | |||
def adopte(self, chat): | |||
print(self.prénom, "adopte", chat.nom) | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Humain("Alice") | |||
>>> alice.adopte(boule_de_poils) | |||
"Alice adopte Boule de Poils" | |||
``` | |||
## Couplage | |||
```python | |||
class Humain: | |||
... | |||
def adopte(self, chat): | |||
print(self.prénom, "adopte", chat.nom) | |||
``` | |||
Notez également que nous avons écrit `chat.nom`. ainsi, la méthode `adopte()` | |||
ne peut être appelée que part une instance qui a un attribut `nom` - sinon | |||
classe ``Chat``:: | |||
class Humain: | |||
def __init__(self, prénom): | |||
self.prénom = prénom | |||
def adopte(self, chat): | |||
print(self.prénom, "adopte un chat") | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Humain("Alice") | |||
>>> alice.adopte(boule_de_poils) | |||
"Alice adopte un chat" | |||
On peut accéder au nom du chat depuis la méthode ``adopte``, | |||
en utilisant la syntaxe ``nom.attribut`` vue précédemment:: | |||
class Humain: | |||
def __init__(self, prénom): | |||
self.prénom = prénom | |||
def adopte(self, chat): | |||
print(self.prénom, "adopte", chat.nom) | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Humain("Alice") | |||
>>> alice.adopte(boule_de_poils) | |||
"Alice adopte Boule de Poils" | |||
Couplage | |||
-------- | |||
.. code-block:: | |||
class Humain: | |||
... | |||
def adopte(self, chat): | |||
print(self.prénom, "adopte", chat.nom) | |||
Notez également que nous avons écrit ``chat.nom``. ainsi, la méthode ``adopte()`` | |||
ne peut être appelée que part une instance qui a un attribut ``nom`` - sinon | |||
on aura une erreur. | |||
Donc si on modifie la classe `Chat` et qu'on renomme l'attribut `nom` en `surnom` par exemple, | |||
la méthode `adopte()` de la classe `Humain` cessera de fonctionner: on dit | |||
qu'on a un *couplage* entre les classes `Chat` et `Humain`. | |||
Donc si on modifie la classe ``Chat`` et qu'on renomme l'attribut ``nom`` en ``surnom`` par exemple, | |||
la méthode ``adopte()`` de la classe ``Humain`` cessera de fonctionner: on dit | |||
qu'on a un *couplage* entre les classes ``Chat`` et ``Humain``. |
@@ -1,117 +1,106 @@ | |||
+++ | |||
title = "Composition" | |||
weight = 3 | |||
+++ | |||
Composition | |||
============ | |||
# Composition | |||
## Définition | |||
Définition | |||
----------- | |||
Une classe à l'intérieur d'une autre classe. | |||
## Dépendances entre fonctions | |||
Dépendances entre fonctions | |||
----------------------------- | |||
Exemple: on veut dessiner un sapin dans le terminal: | |||
Exemple: on veut dessiner un sapin dans le terminal:: | |||
```python | |||
def main(): | |||
largeur = demander_largeur() | |||
dessine_sapin(largeur) | |||
def main(): | |||
largeur = demander_largeur() | |||
dessine_sapin(largeur) | |||
main() | |||
``` | |||
main() | |||
On voit que la fonction `dessine_sapin()` prend un argument `largeur`, qui est retourné | |||
par la fonction `demander_largeur()`. | |||
On voit que la fonction ``dessine_sapin()`` prend un argument ``largeur``, qui est retourné | |||
par la fonction ``demander_largeur()``. | |||
`dessine_sapin()` doit donc être appelée *après* `demander_largeur()`. On dit que `dessine_sapin()` | |||
_dépend_ de `demander_largeur()`. | |||
``dessine_sapin()`` doit donc être appelée *après* ``demander_largeur()``. On dit que ``dessine_sapin()`` | |||
_dépend_ de ``demander_largeur()``. | |||
## Dépendances entre classes | |||
Dépendances entre classes | |||
------------------------- | |||
Un bon moyen d'introduire une dépendance entre deux classes est d'utiliser les constructeurs. | |||
Revoyons la classe Chat: | |||
Revoyons la classe Chat:: | |||
```python | |||
class Chat: | |||
def __init__(self, nom): | |||
self.nom = nome | |||
``` | |||
class Chat: | |||
def __init__(self, nom): | |||
self.nom = nome | |||
Comme le constructeur de la classe Chat prend un nom en argument, il est impossible de construire | |||
des chats sans nom: | |||
des chats sans nom:: | |||
```python | |||
>>> chat = Chat() | |||
TypeError: __init__() missing 1 required positional argument: 'nom' | |||
``` | |||
>>> chat = Chat() | |||
TypeError: __init__() missing 1 required positional argument: 'nom' | |||
De la même façon, si on veut que tous les enfants aient un chat (pourquoi pas, après tout), on peut | |||
avoir une classe Enfant, dont le constructeur prend une instance de chat en plus du prénom: | |||
avoir une classe Enfant, dont le constructeur prend une instance de chat en plus du prénom:: | |||
```python | |||
class Enfant: | |||
def __init__(self, prénom, chat): | |||
self.prénom = prénom | |||
self.chat = chat | |||
class Enfant: | |||
def __init__(self, prénom, chat): | |||
self.prénom = prénom | |||
self.chat = chat | |||
>>> alice = Enfant("Alice") | |||
TypeError: __init__() missing 1 required positional argument: 'chat' | |||
>>> alice = Enfant("Alice") | |||
TypeError: __init__() missing 1 required positional argument: 'chat' | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Enfant("Alice", boule_de_poils) | |||
# OK! | |||
``` | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Enfant("Alice", boule_de_poils) | |||
# OK! | |||
## Utilisation de la composition | |||
Utilisation de la composition | |||
----------------------------- | |||
Maintenant qu'on vit dans un monde où tous les enfants ont chacun un chat, on peut | |||
par exemple consoler tous les enfants en leur demandant de caresser leur chat, chat | |||
qui va ronronner et faire plaisir à son propriétaire. | |||
Voici comment on peut coder cela: d'abord, on rajoute les méthodes `caresse()` | |||
et `ronronne()` dans la classe Chat: | |||
voici comment on peut coder cela: d'abord, on rajoute les méthodes ``caresse()`` | |||
et ``ronronne()`` dans la classe chat:: | |||
```python | |||
class Chat: | |||
def __init__(self, nom): | |||
self.nom = nom | |||
class Chat: | |||
def __init__(self, nom): | |||
self.nom = nom | |||
def ronronne(self): | |||
print(self.nom, 'fait: "prrrrr"') | |||
def ronronne(self): | |||
print(self.nom, 'fait: "prrrrr"') | |||
def caresse(self): | |||
self.ronronne() | |||
def caresse(self): | |||
self.ronronne() | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> boule_de_poils.caresse() | |||
Boule de Poils fait "prrrrr" | |||
``` | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> boule_de_poils.caresse() | |||
Boule de Poils fait "prrrrr" | |||
Ensuite, on peut rajouter la méthode `console()` dans la classe Enfant, | |||
Ensuite, on peut rajouter la méthode ``console()`` dans la classe Enfant, | |||
qui va: | |||
* récupérer l'instance de la classe Chat dans `self` - comme n'importe quel attribut | |||
* puis appeler la méthode `caresse()` de cette instance | |||
* récupérer l'instance de la classe Chat dans ``self`` - comme n'importe quel attribut | |||
* puis appeler la méthode ``caresse()`` de cette instance:: | |||
```python | |||
class Enfant: | |||
def __init__(self, prénom, chat): | |||
self.chat = chat | |||
class Enfant: | |||
def __init__(self, prénom, chat): | |||
self.chat = chat | |||
def console(self): | |||
self.chat.caresse() | |||
def console(self): | |||
self.chat.caresse() | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Enfant("Alice", boule_de_poils) | |||
# Alice est triste, on la console | |||
>>> alice.console() | |||
Boule de Poils fait "prrrrr" | |||
# Alice est consolée :) | |||
``` | |||
>>> boule_de_poils = Chat("Boule de Poils") | |||
>>> alice = Enfant("Alice", boule_de_poils) | |||
# Alice est triste, on la console | |||
>>> alice.console() | |||
Boule de Poils fait "prrrrr" | |||
# Alice est consolée :) | |||
On dit parfois qu'on a *délégué* l'implémentation de la méthode `console()` de la classe Enfant | |||
à la méthode `caresse()` de la classe Chat. | |||
On dit parfois qu'on a *délégué* l'implémentation de la méthode ``console()`` de la classe Enfant | |||
à la méthode ``caresse()`` de la classe Chat. |
@@ -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 | |||
Ce code fonctionne s'il y a un ficher `foo.py` quelque part qui contient la fonction | |||
`bar` [^1]: | |||
``bar``::: | |||
```python | |||
import foo | |||
foo.bar() | |||
``` | |||
import foo | |||
foo.bar() | |||
Ce fichier peut être présent soit dans le répertoire courant, soit dans la bibliothèque standard Python. | |||
## La variable PATH | |||
La variable PATH | |||
------------------- | |||
Vous connaissez peut-être le rôle de la variable d'environnement `PATH`. Celle-ci contient une liste de chemins, | |||
séparés par le caractère `:` et est utilisée par votre shell pour trouver le chemin complet des commandes que vous lancez. | |||
Vous connaissez peut-être le rôle de la variable d'environnement ``PATH``. Celle-ci contient une liste de chemins, | |||
séparés par le caractère ``:`` et est utilisée par votre shell pour trouver le chemin complet des commandes que vous lancez. | |||
Par exemple: | |||
```bash | |||
PATH="/bin:/usr/bin:/usr/sbin" | |||
$ ifconfig | |||
# lance le binaire /usr/sbin/ifconfig | |||
$ ls | |||
# lance le binaire /bin/ls | |||
``` | |||
.. code-block:: console | |||
PATH="/bin:/usr/bin:/usr/sbin" | |||
$ ifconfig | |||
# lance le binaire /usr/sbin/ifconfig | |||
$ ls | |||
# lance le binaire /bin/ls | |||
Le chemin est "résolu" par le shell en parcourant la liste de tout les | |||
chemins de la variable `PATH`, et en regardant si le chemin complet | |||
existe. La résolution s'arrête dès le premier chemin trouvé. | |||
Par exemple, si vous avez `PATH="/home/user/bin:/usr/bin"` et un fichier `ls` dans `/home/user/bin/ls`, c'est ce fichier-là | |||
(et non `/bin/ls`) qui sera utilisé quand vous taperez `ls`. | |||
Par exemple, si vous avez ``PATH="/home/user/bin:/usr/bin"`` et un fichier ``ls`` dans ``/home/user/bin/ls``, c'est ce fichier-là | |||
(et non ``/bin/ls``) qui sera utilisé quand vous taperez ``ls``. |
@@ -1,439 +1,57 @@ | |||
+++ | |||
title = "sys.path" | |||
weight = 2 | |||
+++ | |||
sys.path | |||
======== | |||
# sys.path | |||
En Python, il existe une variable ``path`` prédéfinie dans le module ``sys`` qui fonctionne de manière similaire | |||
à la variable d'environnement ``PATH``. | |||
En Python, il existe une variable `path` prédéfinie dans le module `sys` qui fonctionne de manière similaire | |||
à la variable d'environnement `PATH`. | |||
Si j'essaye de l'afficher sur ma machine, voici ce que j'obtiens: | |||
Si j'essaye de l'afficher sur ma machine, voici ce que j'obtiens : | |||
import sys | |||
print(sys.path) | |||
```python | |||
import sys | |||
print(sys.path) | |||
``` | |||
.. code-block:: text | |||
``` | |||
[ | |||
"", | |||
"/usr/lib/python3.8", | |||
"/usr/lib/python3.8/lib-dynload", | |||
"/home/dmerej/.local/lib/python3.8/", | |||
"/usr/lib/python3.8/site-packages", | |||
] | |||
``` | |||
[ | |||
"", | |||
"/usr/lib/python3.8", | |||
"/usr/lib/python3.8/lib-dynload", | |||
"/home/dmerej/.local/lib/python3.8/", | |||
"/usr/lib/python3.8/site-packages", | |||
] | |||
Le résultat dépend: | |||
* du système d'exploitation | |||
* de la façon dont Python a été installé | |||
* et de la présence ou non de certains réportoires. | |||
En fait, `sys.path` est construit dynamiquement par l'interpréteur Python au démarrage. | |||
En fait, ``sys.path`` est construit dynamiquement par l'interpréteur Python au démarrage. | |||
Notez également que `sys.path` commence par une chaîne vide. En pratique, cela signifie que le répertoire courant a la priorité sur tout le reste. | |||
Notez également que ``sys.path`` commence par une chaîne vide. En pratique, cela signifie que le répertoire courant a la priorité sur tout le reste. | |||
## Priorité du répertoire courant | |||
Priorité du répertoire courant | |||
------------------------------ | |||
Prenons un exemple. Si vous ouvrez un explorateur de fichiers dans le deuxième | |||
élément de la liste de `sys.path` (`/usr/lib/python3.8/` sur ma machine), vous trouverez | |||
élément de la liste de ``sys.path`` (``/usr/lib/python3.8/`` sur ma machine), vous trouverez | |||
un grand nombre de fichiers Python. | |||
Notamment, vous devriez trouver un fichier `random.py` dans ce répertoire. | |||
notamment, vous devriez trouver un fichier ``random.py`` dans ce répertoire. | |||
En fait, vous trouverez la plupart des modules de la bibliothèque standard dans | |||
ce répertoire. | |||
Maintenant, imaginons que vous avez un deuxième fichier `random.py` dans votre répertoire courant. Finalement, imaginez | |||
que vous lancez un fichier `foo.py` contentant `import random` dans ce même réportoire. | |||
Maintenant, imaginons que vous avez un deuxième fichier ``random.py`` dans votre répertoire courant. Finalement, imaginez | |||
que vous lancez un fichier ``foo.py`` contentant ``import random`` dans ce même réportoire. | |||
Et bien, c'est le fichier `random.py` de votre répertoire qui sera utilisé, et non celui de la bibliothèque standard! | |||
Et bien, c'est le fichier ``random.py`` de votre répertoire qui sera utilisé, et non celui de la bibliothèque standard! | |||
## Permissions des répertoires de sys.path | |||
Permissions des répertoires de sys.path | |||
--------------------------------------- | |||
Un autre aspect notable de `sys.path` est qu'il ne contient que deux répertoires dans lesquels l'utilisateur courant peut potentiellement écrire : le chemin courant et le chemin dans `~/.local/lib`. Tous les autres (`/usr/lib/python3.8/`, etc.) sont des chemins "système" et ne peuvent être modifiés que par un compte administrateur (avec `root` ou `sudo`, donc). | |||
Un autre aspect notable de ``sys.path`` est qu'il ne contient que deux | |||
répertoires dans lesquels l'utilisateur courant peut potentiellement écrire | |||
: le chemin courant et le chemin dans ``~/.local/lib``. Tous les autres | |||
(``/usr/lib/python3.8/``, etc.) sont des chemins "système" et ne peuvent | |||
être modifiés que par un compte administrateur (avec ``root`` ou ``sudo``, donc). | |||
La situation est semblable sur macOS et Windows [^2]. | |||
## Bibliothèques tierces | |||
Prenons un exemple : | |||
```python | |||
# dans foo.py | |||
import tabulate | |||
scores = [ | |||
["John", 345], | |||
["Mary-Jane", 2], | |||
["Bob", 543], | |||
] | |||
table = tabulate.tabulate(scores) | |||
print(table) | |||
``` | |||
``` | |||
$ python3 foo.py | |||
--------- --- | |||
John 345 | |||
Mary-Jane 2 | |||
Bob 543 | |||
--------- --- | |||
``` | |||
Ici, le module `tabulate` n'est ni dans la bibliothèque standard, ni écrit par l'auteur du script `foo.py`. On dit que c'est une bibliothèque tierce. | |||
On peut trouver [le code source de tabulate](https://bitbucket.org/astanin/python-tabulate/src/master/) facilement. La question qui se pose alors est: comment faire en sorte que `sys.path` contienne le module `tabulate`? | |||
Eh bien, plusieurs solutions s'offrent à vous. | |||
# Le gestionnaire de paquets | |||
Si vous utilisez une distribution Linux, peut-être pourrez-vous utiliser votre gestionnaire de paquets : | |||
```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. | |||
## 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. | |||
Voici une marche à suivre possible : | |||
1. Récupérer les sources de la version qui vous intéresse dans la [section téléchargement de bitbucket](https://bitbucket.org/astanin/python-tabulate/downloads/?tab=tags). | |||
1. Extraire l'archive, par exemple dans `src/tabulate` | |||
1. Se rendre dans `src/tabulate` et lancer `python3 setup.py install --user` | |||
1. Récupérer les sources de la version qui vous intéresse dans la `section téléchargement de bitbucket <https://bitbucket.org/astanin/python-tabulate/downloads/?tab=tags>`_. | |||
1. Extraire l'archive, par exemple dans ``src/tabulate`` | |||
1. Se rendre dans ``src/tabulate`` et lancer ``python3 setup.py install --user`` | |||
## Anatomie du fichier setup.py | |||
Anatomie du fichier setup.py | |||
----------------------------- | |||
La plupart des bibliothèques Python contiennent un `setup.py` à | |||
la racine de leurs sources. Il sert à plein de choses, la commande `install` | |||
La plupart des bibliothèques Python contiennent un ``setup.py`` à | |||
la racine de leurs sources. Il sert à plein de choses, la commande ``install`` | |||
n'étant qu'une parmi d'autres. | |||
Le fichier `setup.py` contient en général simplement un `import` de `setuptools`, et un appel à la fonction `setup()`, avec de nombreux arguments : | |||
Le fichier ``setup.py`` contient en général simplement un ``import`` de | |||
``setuptools``, et un appel à la fonction ``setup()``, avec de nombreux | |||
arguments:: | |||
# tabulate/setup.py | |||
from setuptools import setup | |||
setup( | |||
name='tabulate', | |||
version='0.8.1', | |||
description='Pretty-print tabular data', | |||
py_modules=["tabulate"], | |||
scripts=["bin/tabulate"], | |||
... | |||
) | |||
Résultat de l'invocation de setup.py | |||
------------------------------------- | |||
Par défaut, ``setup.py`` essaiera d'écrire dans un des chemins système de | |||
``sys.path``, d'où l'utilisation de l'option ``--user``. | |||
Voici à quoi ressemble la sortie de la commande: | |||
.. code-block:: console | |||
$ cd src/tabulate | |||
$ python3 setup.py install --user | |||
running install | |||
... | |||
Copying tabulate-0.8.4-py3.7.egg to /home/dmerej/.local/lib/python3.7/site-packages | |||
... | |||
Installing tabulate script to /home/dmerej/.local/bin | |||
Notez que module a été copié dans ``~/.local/lib/python3.7/site-packages/`` | |||
et le script dans ``~/.local/bin``. Cela signifie que *tous* les scripts Python | |||
lancés par l'utilisateur courant auront accès au module ``tabulate``. | |||
Notez également qu'un script a été installé dans ``~/.local/bin`` - Une | |||
bibliothèque Python peut contenir aussi bien des modules que des scripts. | |||
Un point important est que vous n'avez en général pas besoin de lancer le | |||
script directement. Vous pouvez utiliser ``python3 -m tabulate``. Procéder | |||
de cette façon est intéressant puisque vous n'avez pas à vous soucier de | |||
rajouter le chemin d'installation des scripts dans la variable d'environnement | |||
PATH. | |||
Dépendances | |||
----------- | |||
Prenons une autre bibliothèque : ``cli-ui``. | |||
Elle permet d'afficher du texte en couleur dans un terminal:: | |||
import cli_ui | |||
cli_ui.info("Ceci est en", cli_ui.red, "rouge") | |||
Elle permet également d'afficher des tableaux en couleur:: | |||
headers=["name", "score"] | |||
data = [ | |||
[(bold, "John"), (green, 10.0)], | |||
[(bold, "Jane"), (green, 5.0)], | |||
] | |||
cli_ui.info_table(data, headers=headers) | |||
Pour ce faire, elle repose sur la bibliothèque ``tabulate`` vue | |||
précédemment. On dit que ``cli-ui`` *dépend* de ``tabulate``. | |||
Déclaration des dépendances | |||
---------------------------- | |||
La déclaration de la dépendance de ``cli-ui`` vers ``tabulate`` s'effectue également dans le fichier ``setup.py``:: | |||
setup( | |||
name="cli-ui", | |||
version="0.9.1", | |||
install_requires=[ | |||
"tabulate", | |||
... | |||
], | |||
... | |||
) | |||
pypi.org | |||
--------- | |||
On comprend dès lors qu'il doit nécessairement exister un *annuaire* permettant de relier les noms de dépendances à leur code source. | |||
Cet annuaire, c'est le site `pypi.org <https://pypi.org/>`_. Vous y trouverez | |||
les pages correspondant à `tabulate <https://pypi.org/project/tabulate/>`_ | |||
et `cli-ui <https://pypi.org/project/python-cli-ui/>`_. | |||
pip | |||
--- | |||
``pip`` est un outil qui vient par défaut avec Python3[^4]. Vous pouvez également l'installer grâce au script `get-pip.py <https://bootstrap.pypa.io/get-pip.py>`_, en lançant ``python3 get-pip.py --user``. | |||
Il est conseillé de *toujours* lancer ``pip`` avec ``python3 -m pip``. De cette | |||
façon, vous êtes certains d'utiliser le module ``pip`` correspondant à votre | |||
binaire ``python3``, et vous ne dépendez pas de ce qu'il y a dans votre ``PATH``. | |||
``pip`` est capable d'interroger le site ``pypi.org`` pour retrouver les | |||
dépendances, et également de lancer les différents scripts ``setup.py``. | |||
Comme de nombreux outils, il s'utilise à l'aide de *commandes*. Voici | |||
comment installer ``cli-ui`` à l'aide de la commande 'install' de ``pip``: | |||
.. code-block:: console | |||
$ python3 -m pip install cli-ui --user | |||
Collecting cli-ui | |||
... | |||
Requirement already satisfied: unidecode in /usr/lib/python3.7/site-packages (from cli-ui) (1.0.23) | |||
Requirement already satisfied: colorama in /usr/lib/python3.7/site-packages (from cli-ui) (0.4.1) | |||
Requirement already satisfied: tabulate in /mnt/data/dmerej/src/python-tabulate (from cli-ui) (0.8.4) | |||
Installing collected packages: cli-ui | |||
Successfully installed cli-ui-0.9.1 | |||
On constate ici quelques limitations de ``pip``: | |||
* Il faut penser à utiliser ``--user`` (de la même façon que lorsqu'on lance ``setup.py`` à la main) | |||
* Si le paquet est déjà installé dans le système, pip ne saura pas le | |||
mettre à jour - il faudra passer par le gestionnaire de paquet de | |||
la distribution | |||
En revanche, `pip` contient de nombreuses fonctionnalités intéressantes: | |||
* Il est capable de désinstaller des bibliothèques (à condition toutefois | |||
qu'elles ne soient pas dans un répertoire système) | |||
* Il est aussi capable d'afficher la liste complète des bibliothèques | |||
Python accessibles par l'utilisateur courant avec `freeze`. | |||
Voici un extrait de la commande ``python3 -m pip freeze`` au moment de la rédaction de cet article sur ma machine: | |||
.. code-block:: console | |||
$ python3 -m pip freeze | |||
apipkg==1.5 | |||
cli-ui==0.9.1 | |||
gaupol==1.5 | |||
tabulate==0.8.4 | |||
On y retrouve les bibliothèques ``cli-ui`` et ``tabulate``, bien sûr, mais | |||
aussi la bibliothèque ``gaupol``, qui correspond au [programme d'édition de | |||
sous-titres](https://otsaloma.io/gaupol/) que j'ai installé à l'aide du | |||
gestionnaire de paquets de ma distribution. Précisons que les modules de | |||
la bibliothèque standard et ceux utilisés directement par pip sont omis | |||
de la liste. | |||
On constate également que chaque bibliothèque possède un *numéro de version*. | |||
Numéros de version | |||
------------------- | |||
Les numéros de version remplissent plusieurs rôles, mais l'un des principaux | |||
est de spécifier des changements incompatibles. | |||
Par exemple, pour ``cli-ui``, la façon d'appeler la fonction ``ask_choice`` | |||
a changé entre les versions 0.7 et 0.8, comme le montre cet extrait du | |||
`changelog <https://tankerhq.github.io/python-cli-ui/changelog.html#v0-8-0)>`_: | |||
*The list of choices used by ask_choice is now a named keyword argument:* | |||
.. code-block:: | |||
# Old (<= 0.7) | |||
ask_choice("select a fruit", ["apple", "banana"]) | |||
# New (>= 0.8) | |||
ask_choice("select a fruit", choices=["apple", "banana"]) | |||
```python | |||
# tabulate/setup.py | |||
from setuptools import setup | |||
Ceci s'appelle un *changement d'API*. | |||
setup( | |||
name='tabulate', | |||
version='0.8.1', | |||
description='Pretty-print tabular data', | |||
py_modules=["tabulate"], | |||
scripts=["bin/tabulate"], | |||
... | |||
) | |||
``` | |||
Réagir aux changements d'API | |||
----------------------------- | |||
Plusieurs possibilités: | |||
## Résultat de l'invocation de setup.py | |||
* On peut bien sûr adapter le code pour utiliser la nouvelle API, mais cela | |||
n'est pas toujours possible ni souhaitable. | |||
* Une autre solution est de spécifier des *contraintes* sur le numéro de | |||
version dans la déclaration des dépendances. Par exemple:: | |||
setup( | |||
install_requires=[ | |||
"cli-ui < 0.8", | |||
... | |||
] | |||
) | |||
Par défaut, `setup.py` essaiera d'écrire dans un des chemins système de | |||
`sys.path` [^3], d'où l'utilisation de l'option `--user`. | |||
Aparté : pourquoi éviter sudo pip | |||
--------------------------------- | |||
Voici à quoi ressemble la sortie de la commande : | |||
Souvenez-vous que les fichiers systèmes sont contrôlés par votre gestionnaire de paquets. | |||
```bash | |||
$ cd src/tabulate | |||
$ python3 setup.py install --user | |||
running install | |||
... | |||
Copying tabulate-0.8.4-py3.7.egg to /home/dmerej/.local/lib/python3.7/site-packages | |||
... | |||
Installing tabulate script to /home/dmerej/.local/bin | |||
``` | |||
Les mainteneurs de votre distribution font en sorte qu'ils fonctionnent bien les uns | |||
avec les autres. Par exemple, le paquet ``python3-cli-ui`` ne sera mis à jour | |||
que lorsque tous les paquets qui en dépendent seront prêts à utiliser la | |||
nouvelle API. | |||
En revanche, si vous lancez ``sudo pip`` (où ``pip`` avec un compte root), | |||
vous allez écrire dans ces mêmes répertoire et vous risquez de "casser" | |||
certains programmes de votre système. | |||
Notez que module a été copié dans `~/.local/lib/python3.7/site-packages/` et le script dans `~/.local/bin`. Cela signifie que *tous* les scripts Python lancés par l'utilisateur courant auront accès au module `tabulate`. | |||
Mais il y a un autre problème encore pire. | |||
Notez également qu'un script a été installé dans `~/.local/bin` - Une bibliothèque Python peut contenir aussi bien des modules que des scripts. | |||
Conflit de dépendances | |||
---------------------- | |||
Un point important est que vous n'avez en général pas besoin de lancer le script directement. Vous pouvez utiliser `python3 -m tabulate`. Procéder de cette façon est intéressant puisque vous n'avez pas à vous soucier de rajouter le chemin d'installation des scripts dans la variable d'environnement PATH. | |||
Supposons deux projets A et B dans votre répertoire personnel. Ils dépendent | |||
tous les deux de ``cli-ui``, mais l'un des deux utilise ``cli-ui 0.7`` et l'autre | |||
``cli-ui 0.9``. Que faire ? | |||
@@ -1,147 +1,166 @@ | |||
+++ | |||
title = "Dépendances" | |||
weight = 4 | |||
+++ | |||
Dépendances | |||
=========== | |||
# Dépendances | |||
Prenons une autre bibliothèque : ``cli-ui``. | |||
## Un autre exemple | |||
Elle permet d'afficher du texte en couleur dans un terminal:: | |||
Prenons une autre bibliothèque : `cli-ui`. | |||
import cli_ui | |||
Elle permet d'afficher du texte en couleur dans un terminal | |||
cli_ui.info("Ceci est en", cli_ui.red, "rouge") | |||
```python | |||
import cli_ui | |||
Elle permet également d'afficher des tableaux en couleur:: | |||
cli_ui.info("Ceci est en", cli_ui.red, "rouge") | |||
``` | |||
headers=["name", "score"] | |||
data = [ | |||
[(bold, "John"), (green, 10.0)], | |||
[(bold, "Jane"), (green, 5.0)], | |||
] | |||
cli_ui.info_table(data, headers=headers) | |||
Elle permet également d'afficher des tableaux en couleur : | |||
Pour ce faire, elle repose sur la bibliothèque ``tabulate`` vue | |||
précédemment. On dit que ``cli-ui`` *dépend* de ``tabulate``. | |||
```python | |||
headers=["prénom", "score"] | |||
data = [ | |||
[(bold, "John"), (green, 10.0)], | |||
[(bold, "Jane"), (green, 5.0)], | |||
] | |||
cli_ui.info_table(data, headers=headers) | |||
``` | |||
Déclaration des dépendances | |||
---------------------------- | |||
Pour ce faire, elle repose sur la bibliothèque `tabulate` vue précédemment. On dit que `cli-ui` *dépend* de `tabulate`. | |||
La déclaration de la dépendance de ``cli-ui`` vers ``tabulate`` s'effectue également dans le fichier ``setup.py``:: | |||
## Déclaration des dépendances | |||
setup( | |||
name="cli-ui", | |||
version="0.9.1", | |||
install_requires=[ | |||
"tabulate", | |||
... | |||
], | |||
... | |||
) | |||
La déclaration de la dépendance de `cli-ui` vers `tabulate` s'effectue également dans le fichier `setup.py`: | |||
```python | |||
setup( | |||
name="cli-ui", | |||
version="0.9.1", | |||
install_requires=[ | |||
"tabulate", | |||
... | |||
], | |||
... | |||
) | |||
``` | |||
## pypi.org | |||
pypi.org | |||
--------- | |||
On comprend dès lors qu'il doit nécessairement exister un *annuaire* permettant de relier les noms de dépendances à leur code source. | |||
Cet annuaire, c'est le site [pypi.org](https://pypi.org/). Vous y trouverez les pages correspondant à [tabulate](https://pypi.org/project/tabulate/) et [cli-ui](https://pypi.org/project/python-cli-ui/). | |||
Cet annuaire, c'est le site `pypi.org <https://pypi.org/>`_. Vous y trouverez | |||
les pages correspondant à `tabulate <https://pypi.org/project/tabulate/>`_ | |||
et `cli-ui <https://pypi.org/project/python-cli-ui/>`_. | |||
pip | |||
--- | |||
# pip | |||
``pip`` est un outil qui vient par défaut avec Python3[^4]. Vous pouvez également l'installer grâce au script `get-pip.py <https://bootstrap.pypa.io/get-pip.py>`_, en lançant ``python3 get-pip.py --user``. | |||
`pip` est un outil qui vient par défaut avec Python3[^4]. Vous pouvez également l'installer grâce au script [get-pip.py](https://bootstrap.pypa.io/get-pip.py), en lançant `python3 get-pip.py --user`. | |||
Il est conseillé de *toujours* lancer ``pip`` avec ``python3 -m pip``. De cette | |||
façon, vous êtes certains d'utiliser le module ``pip`` correspondant à votre | |||
binaire ``python3``, et vous ne dépendez pas de ce qu'il y a dans votre ``PATH``. | |||
Il est conseillé de *toujours* lancer `pip` avec `python3 -m pip`. De cette façon, vous êtes certains d'utiliser le module `pip` correspondant à votre binaire `python3`, et vous ne dépendez pas de ce qu'il y a dans votre `PATH`. | |||
``pip`` est capable d'interroger le site ``pypi.org`` pour retrouver les | |||
dépendances, et également de lancer les différents scripts ``setup.py``. | |||
`pip` est capable d'interroger le site `pypi.org` pour retrouver les dépendances, et également de lancer les différents scripts `setup.py`. | |||
Comme de nombreux outils, il s'utilise à l'aide de *commandes*. Voici | |||
comment installer ``cli-ui`` à l'aide de la commande 'install' de ``pip``: | |||
Comme de nombreux outils, il s'utilise à l'aide de *commandes*. Voici comment installer `cli-ui` à l'aide de la commande 'install' de `pip`: | |||
.. code-block:: console | |||
```bash | |||
$ python3 -m pip install cli-ui --user | |||
Collecting cli-ui | |||
... | |||
Requirement already satisfied: unidecode in /usr/lib/python3.7/site-packages (from cli-ui) (1.0.23) | |||
Requirement already satisfied: colorama in /usr/lib/python3.7/site-packages (from cli-ui) (0.4.1) | |||
Requirement already satisfied: tabulate in /mnt/data/dmerej/src/python-tabulate (from cli-ui) (0.8.4) | |||
Installing collected packages: cli-ui | |||
Successfully installed cli-ui-0.9.1 | |||
``` | |||
$ python3 -m pip install cli-ui --user | |||
Collecting cli-ui | |||
... | |||
Requirement already satisfied: unidecode in /usr/lib/python3.7/site-packages (from cli-ui) (1.0.23) | |||
Requirement already satisfied: colorama in /usr/lib/python3.7/site-packages (from cli-ui) (0.4.1) | |||
Requirement already satisfied: tabulate in /mnt/data/dmerej/src/python-tabulate (from cli-ui) (0.8.4) | |||
Installing collected packages: cli-ui | |||
Successfully installed cli-ui-0.9.1 | |||
On constate ici quelques limitations de `pip`: | |||
On constate ici quelques limitations de ``pip``: | |||
* Il faut penser à utiliser `--user` (de la même façon que lorsqu'on lance `setup.py` à la main) | |||
* Si le paquet est déjà installé dans le système, pip ne saura pas le mettre à jour - il faudra passer par le gestionnaire de paquet de | |||
* Il faut penser à utiliser ``--user`` (de la même façon que lorsqu'on lance ``setup.py`` à la main) | |||
* Si le paquet est déjà installé dans le système, pip ne saura pas le | |||
mettre à jour - il faudra passer par le gestionnaire de paquet de | |||
la distribution | |||
En revanche, `pip` contient de nombreuses fonctionnalités intéressantes: | |||
* Il est capable de désinstaller des bibliothèques (à condition toutefois qu'elles ne soient pas dans un répertoire système) | |||
* Il est aussi capable d'afficher la liste complète des bibliothèques Python accessibles par l'utilisateur courant avec `freeze`. | |||
* Il est capable de désinstaller des bibliothèques (à condition toutefois | |||
qu'elles ne soient pas dans un répertoire système) | |||
* Il est aussi capable d'afficher la liste complète des bibliothèques | |||
Python accessibles par l'utilisateur courant avec `freeze`. | |||
Voici un extrait de la commande `python3 -m pip freeze` au moment de la rédaction de cet article sur ma machine: | |||
Voici un extrait de la commande ``python3 -m pip freeze`` au moment de la rédaction de cet article sur ma machine: | |||
``` | |||
$ python3 -m pip freeze | |||
apipkg==1.5 | |||
cli-ui==0.9.1 | |||
gaupol==1.5 | |||
tabulate==0.8.4 | |||
``` | |||
.. code-block:: console | |||
On y retrouve les bibliothèques `cli-ui` et `tabulate`, bien sûr, mais aussi la bibliothèque `gaupol`, qui correspond au [programme d'édition de sous-titres](https://otsaloma.io/gaupol/) que j'ai installé à l'aide du gestionnaire de paquets de ma distribution. Précisons que les modules de la bibliothèque standard et ceux utilisés directement par pip sont omis de la liste. | |||
$ python3 -m pip freeze | |||
apipkg==1.5 | |||
cli-ui==0.9.1 | |||
gaupol==1.5 | |||
tabulate==0.8.4 | |||
On y retrouve les bibliothèques ``cli-ui`` et ``tabulate``, bien sûr, mais | |||
aussi la bibliothèque ``gaupol``, qui correspond au [programme d'édition de | |||
sous-titres](https://otsaloma.io/gaupol/) que j'ai installé à l'aide du | |||
gestionnaire de paquets de ma distribution. Précisons que les modules de | |||
la bibliothèque standard et ceux utilisés directement par pip sont omis | |||
de la liste. | |||
On constate également que chaque bibliothèque possède un *numéro de version*. | |||
## Numéros de version | |||
Numéros de version | |||
------------------- | |||
Les numéros de version remplissent plusieurs rôles, mais l'un des principaux | |||
est de spécifier des changements incompatibles. | |||
Les numéros de version remplissent plusieurs rôles, mais l'un des principaux est de spécifier des changements incompatibles. | |||
Par exemple, pour ``cli-ui``, la façon d'appeler la fonction ``ask_choice`` | |||
a changé entre les versions 0.7 et 0.8, comme le montre cet extrait du | |||
`changelog <https://tankerhq.github.io/python-cli-ui/changelog.html#v0-8-0)>`_: | |||
Par exemple, pour `cli-ui`, la façon d'appeler la fonction `ask_choice` a changé entre les versions 0.7 et 0.8, comme le montre cet extrait du [changelog](https://tankerhq.github.io/python-cli-ui/changelog.html#v0-8-0): | |||
*The list of choices used by ask_choice is now a named keyword argument:* | |||
> the list of choices used by ask_choice is now a named keyword argument: | |||
.. code-block:: | |||
```python | |||
# Old (<= 0.7) | |||
ask_choice("select a fruit", ["apple", "banana"]) | |||
# New (>= 0.8) | |||
ask_choice("select a fruit", choices=["apple", "banana"]) | |||
``` | |||
# Old (<= 0.7) | |||
ask_choice("select a fruit", ["apple", "banana"]) | |||
# New (>= 0.8) | |||
ask_choice("select a fruit", choices=["apple", "banana"]) | |||
Ceci s'appelle un *changement d'API*. | |||
## Réagir aux changements d'API | |||
Réagir aux changements d'API | |||
----------------------------- | |||
Plusieurs possibilités: | |||
* On peut bien sûr adapter le code pour utiliser la nouvelle API, mais cela n'est pas toujours possible ni souhaitable. | |||
* Une autre solution est de spécifier des *contraintes* sur le numéro de version dans la déclaration des dépendances. Par exemple : | |||
* On peut bien sûr adapter le code pour utiliser la nouvelle API, mais cela | |||
n'est pas toujours possible ni souhaitable. | |||
* Une autre solution est de spécifier des *contraintes* sur le numéro de | |||
version dans la déclaration des dépendances. Par exemple:: | |||
```python | |||
setup( | |||
install_requires=[ | |||
"cli-ui < 0.8", | |||
... | |||
] | |||
) | |||
``` | |||
setup( | |||
install_requires=[ | |||
"cli-ui < 0.8", | |||
... | |||
] | |||
) | |||
# Aparté : pourquoi éviter sudo pip | |||
Aparté : pourquoi éviter sudo pip | |||
--------------------------------- | |||
Souvenez-vous que les fichiers systèmes sont contrôlés par votre gestionnaire de paquets. | |||
Les mainteneurs de votre distribution font en sorte qu'ils fonctionnent bien les uns | |||
avec les autres. Par exemple, le paquet `python3-cli-ui` ne sera mis à jour que lorsque tous les paquets qui en dépendent seront prêts à utiliser la nouvelle API. | |||
avec les autres. Par exemple, le paquet ``python3-cli-ui`` ne sera mis à jour | |||
que lorsque tous les paquets qui en dépendent seront prêts à utiliser la | |||
nouvelle API. | |||
En revanche, si vous lancez `sudo pip` (où `pip` avec un compte root), vous allez écrire dans ces mêmes répertoire et vous risquez de "casser" certains programmes de votre système. | |||
En revanche, si vous lancez ``sudo pip`` (où ``pip`` avec un compte root), | |||
vous allez écrire dans ces mêmes répertoire et vous risquez de "casser" | |||
certains programmes de votre système. | |||
Mais il y a un autre problème encore pire. | |||
# Conflit de dépendances | |||
Conflit de dépendances | |||
---------------------- | |||
Supposons deux projets A et B dans votre répertoire personnel. Ils dépendent tous les deux de `cli-ui`, mais l'un des deux utilise `cli-ui 0.7` et l'autre `cli-ui 0.9`. Que faire ? | |||
Supposons deux projets A et B dans votre répertoire personnel. Ils dépendent | |||
tous les deux de ``cli-ui``, mais l'un des deux utilise ``cli-ui 0.7`` et l'autre | |||
``cli-ui 0.9``. Que faire ? |
@@ -1,137 +1,168 @@ | |||
+++ | |||
title = "Environnements virtuels" | |||
weight = 5 | |||
+++ | |||
Environnements virtuels | |||
======================== | |||
# Environnements virtuels | |||
La solution est d'utiliser un environnement virtuel (*virtualenv* en | |||
abrégé). C'est un répertoire *isolé* du reste du système. | |||
La solution à la question de la fin du chapitre précédent est d'utiliser un *environnement virtuel* | |||
(*virtualenv* en abrégé). | |||
Il se crée par exemple avec la commande ``python3 -m venv foo-venv``. où | |||
``foo-venv`` est un répertoire quelconque. | |||
C'est un répertoire *isolé* du reste du système. | |||
Aparté : python3 -m venv sur Debian | |||
------------------------------------ | |||
Il se crée par exemple avec la commande `python3 -m venv foo-venv`. où `foo-venv` est un répertoire quelconque. | |||
La commande `python3 -m venv` fonctionne en général partout, dès | |||
l'installation de Python3 (*out of the box*, en Anglais), *sauf* sur Debian | |||
et ses dérivées. | |||
## Aparté : python3 -m venv sur Debian | |||
Si vous utilisez Debian, la commande pourrait ne pas fonctionner. En fonction | |||
des messages d'erreur que vous obtenez, il est possible de résoudre le | |||
problème en : | |||
La commande `python3 -m venv` fonctionne en général partout, dès l'installation de Python3 (*out of the box*, en Anglais), *sauf* sur Debian et ses dérivées [^5]. | |||
* installant le paquet ``python3-venv``, | |||
* ou en utilisant d'abord ``pip`` pour installer ``virtualenv``, avec | |||
``python3 -m pip install virtualenv --user`` puis en lançant ``python3 -m | |||
virtualenv foo-venv``. | |||
Si vous utilisez Debian, la commande pourrait ne pas fonctionner. En fonction des messages d'erreur que vous obtenez, il est possible de résoudre le problème en : | |||
Comportement de python dans le virtualenv | |||
----------------------------------------- | |||
* installant le paquet `python3-venv`, | |||
* ou en utilisant d'abord `pip` pour installer `virtualenv`, avec `python3 -m pip install virtualenv --user` puis en lançant `python3 -m virtualenv foo-venv`. | |||
Ce répertoire contient de nombreux fichiers et dossiers, et notamment un | |||
binaire dans ``foo-venv/bin/python3``. | |||
## Comportement de python dans le virtualenv | |||
Voyons comment il se comporte en le comparant au binaire ``/usr/bin/python3`` | |||
habituel: | |||
Ce répertoire contient de nombreux fichiers et dossiers, et notamment un binaire dans `foo-venv/bin/python3`. | |||
.. code-block:: console | |||
Voyons comment il se comporte en le comparant au binaire `/usr/bin/python3` habituel : | |||
$ /usr/bin/python3 -c 'import sys; print(sys.path)' | |||
['', | |||
... | |||
'/usr/lib/python3.7', | |||
'/usr/lib/python3.7.zip', | |||
'/usr/lib/python3.7/lib-dynload', | |||
'/home/dmerej/.local/lib/python3.7/site-packages', | |||
'/usr/lib/python3.7/site-packages' | |||
] | |||
``` | |||
$ /usr/bin/python3 -c 'import sys; print(sys.path)' | |||
['', | |||
... | |||
'/usr/lib/python3.7', | |||
'/usr/lib/python3.7.zip', | |||
'/usr/lib/python3.7/lib-dynload', | |||
'/home/dmerej/.local/lib/python3.7/site-packages', | |||
'/usr/lib/python3.7/site-packages' | |||
] | |||
.. code-block:: console | |||
$ /home/dmerej/foo-venv/bin/python -c 'import sys; print(sys.path)' | |||
['', | |||
'/usr/lib/python3.7', | |||
'/usr/lib/python3.7.zip', | |||
'/usr/lib/python3.7/lib-dynload', | |||
'/home/dmerej/foo-venv/lib/python3.7/site-packages, | |||
] | |||
``` | |||
$ /home/dmerej/foo-venv/bin/python -c 'import sys; print(sys.path)' | |||
['', | |||
'/usr/lib/python3.7', | |||
'/usr/lib/python3.7.zip', | |||
'/usr/lib/python3.7/lib-dynload', | |||
'/home/dmerej/foo-venv/lib/python3.7/site-packages, | |||
] | |||
À noter: | |||
* Le répertoire "global" dans `~/.local/lib` a disparu | |||
* Seuls quelques répertoires systèmes sont présents (ils correspondent plus ou moins à l'emplacement des modules de la bibliothèque standard) | |||
* Le répertoire "global" dans ``~/.local/lib`` a disparu | |||
* Seuls quelques répertoires systèmes sont présents (ils correspondent | |||
plus ou moins à l'emplacement des modules de la bibliothèque standard) | |||
* Un répertoire *au sein* du virtualenv a été rajouté | |||
Ainsi, l'isolation du virtualenv est reflété dans la différence de la valeur de `sys.path`. | |||
Ainsi, l'isolation du virtualenv est reflété dans la différence de la | |||
valeur de ``sys.path``. | |||
Il faut aussi préciser que le virtualenv n'est pas complètement isolé du reste du système. En particulier, il dépend encore du binaire Python utilisé pour le créer. | |||
Il faut aussi préciser que le virtualenv n'est pas complètement isolé | |||
du reste du système. En particulier, il dépend encore du binaire Python | |||
utilisé pour le créer. | |||
Par exemple, si vous utilisez `/usr/local/bin/python3.7 -m venv foo-37`, le virtualenv dans `foo-37` utilisera Python 3.7 et fonctionnera tant que le binaire `/usr/local/bin/python3.7` existe. | |||
Par exemple, si vous utilisez ``/usr/local/bin/python3.7 -m venv foo-37``, | |||
le virtualenv dans ``foo-37`` utilisera Python 3.7 et fonctionnera tant que | |||
le binaire ``/usr/local/bin/python3.7`` existe. | |||
Cela signifie également qu'il est possible qu'en mettant à jour le paquet `python3` sur votre distribution, vous rendiez inutilisables les virtualenvs créés avec l'ancienne version du paquet. | |||
Cela signifie également qu'il est possible qu'en mettant à jour le paquet | |||
``python3`` sur votre distribution, vous rendiez inutilisables les virtualenvs | |||
créés avec l'ancienne version du paquet. | |||
## Comportement de pip dans le virtualenv | |||
Comportement de pip dans le virtualenv | |||
--------------------------------------- | |||
D'après ce qui précède, le virtualenv ne devrait contenir aucun module en dehors de la bibliothèque standard et de `pip` lui-même. | |||
D'après ce qui précède, le virtualenv ne devrait contenir aucun module | |||
en dehors de la bibliothèque standard et de ``pip`` lui-même. | |||
On peut s'en assurer en lançant `python3 -m pip freeze` depuis le virtualenv et en vérifiant que rien ne s'affiche. | |||
On peut s'en assurer en lançant ``python3 -m pip freeze`` depuis le virtualenv | |||
et en vérifiant que rien ne s'affiche: | |||
``` | |||
$ python3 -m pip freeze | |||
# de nombreuses bibliothèques en dehors du virtualenv | |||
apipkg==1.5 | |||
cli-ui==0.9.1 | |||
gaupol==1.5 | |||
tabulate==0.8.4 | |||
.. code-block:: console | |||
$ /home/dmerej/foo-venv/bin/python3 -m pip freeze | |||
# rien :) | |||
``` | |||
$ python3 -m pip freeze | |||
# de nombreuses bibliothèques en dehors du virtualenv | |||
apipkg==1.5 | |||
cli-ui==0.9.1 | |||
gaupol==1.5 | |||
tabulate==0.8.4 | |||
On peut alors utiliser le module `pip` *du virtualenv* pour installer des bibliothèques dans celui-ci : | |||
.. code-block:: console | |||
``` | |||
$ /home/dmerej/foo-venv/bin/python3 -m pip install cli-ui | |||
Collecting cli-ui | |||
Using cached https://pythonhosted.org/..cli_ui-0.9.1-py3-none-any.whl | |||
Collecting colorama (from cli-ui) | |||
Using cached https://pythonhosted.org/..colorama-0.4.1-py2.py3-none-any.whl | |||
Collecting unidecode (from cli-ui) | |||
Using cached https://pythonhosted.org/..Unidecode-1.0.23-py2.py3-none-any.whl | |||
Collecting tabulate (from cli-ui) | |||
Installing collected packages: colorama, unidecode, tabulate, cli-ui | |||
Successfully installed cli-ui-0.9.1 colorama-0.4.1 tabulate-0.8.3 | |||
unidecode-1.0.23 | |||
``` | |||
$ /home/dmerej/foo-venv/bin/python3 -m pip freeze | |||
# rien :) | |||
Cette fois, aucune bibliothèque n'est marquée comme déjà installée, et on récupère donc `cli-ui` et toutes ses dépendances. | |||
On peut alors utiliser le module ``pip`` *du virtualenv* pour installer des | |||
bibliothèques dans celui-ci : | |||
.. code-block:: console | |||
$ /home/dmerej/foo-venv/bin/python3 -m pip install cli-ui | |||
Collecting cli-ui | |||
Using cached https://pythonhosted.org/..cli_ui-0.9.1-py3-none-any.whl | |||
Collecting colorama (from cli-ui) | |||
Using cached https://pythonhosted.org/..colorama-0.4.1-py2.py3-none-any.whl | |||
Collecting unidecode (from cli-ui) | |||
Using cached https://pythonhosted.org/..Unidecode-1.0.23-py2.py3-none-any.whl | |||
Collecting tabulate (from cli-ui) | |||
Installing collected packages: colorama, unidecode, tabulate, cli-ui | |||
Successfully installed cli-ui-0.9.1 colorama-0.4.1 tabulate-0.8.3 | |||
unidecode-1.0.23 | |||
Cette fois, aucune bibliothèque n'est marquée comme déjà installée, | |||
et on récupère donc ``cli-ui`` et toutes ses dépendances. | |||
On a enfin notre solution pour résoudre notre conflit de dépendances : | |||
on peut simplement créer un virtualenv par projet. Ceci nous permettra | |||
d'avoir effectivement deux versions différentes de `cli-ui`, isolées les | |||
d'avoir effectivement deux versions différentes de ``cli-ui``, isolées les | |||
unes des autres. | |||
## Activer un virtualenv | |||
Activer un virtualenv | |||
---------------------- | |||
Devoir préciser le chemin du virtualenv en entier pour chaque commande peut devenir fastidieux ; heureusement, il est possible *d'activer* un virtualenv, en lançant une des commandes suivantes : | |||
Devoir préciser le chemin du virtualenv en entier pour chaque commande peut | |||
devenir fastidieux ; heureusement, il est possible *d'activer* un virtualenv, | |||
en lançant une des commandes suivantes : | |||
* `source foo-venv/bin/activate` - si vous utilisez un shell POSIX | |||
* `source foo-venv/bin/activate.fish` - si vous utilisez Fish | |||
* `foo-venv\bin\activate.bat` - sous Windows | |||
* ``source foo-venv/bin/activate`` - si vous utilisez un shell POSIX | |||
* ``source foo-venv/bin/activate.fish`` - si vous utilisez Fish | |||
* ``foo-venv\bin\activate.bat`` - sous Windows | |||
Une fois le virtualenv activé, taper `python`, `python3` ou `pip` utilisera les binaires correspondants dans le virtualenv automatiquement, | |||
Une fois le virtualenv activé, taper ``python``, ``python3`` ou ``pip`` utilisera | |||
les binaires correspondants dans le virtualenv automatiquement, | |||
et ce, tant que la session du shell sera ouverte. | |||
Le script d'activation ne fait en réalité pas grand-chose à part modifier la variable `PATH` et rajouter le nom du virtualenv au début de l'invite de commandes : | |||
Le script d'activation ne fait en réalité pas grand-chose à part modifier | |||
la variable ``PATH`` et rajouter le nom du virtualenv au début de l'invite | |||
de commandes: | |||
.. code-block:: console | |||
``` | |||
# Avant | |||
user@host:~/src $ source foo-env/bin/activate | |||
# Après | |||
(foo-env) user@host:~/src $ | |||
``` | |||
# Avant | |||
user@host:~/src $ source foo-env/bin/activate | |||
# Après | |||
(foo-env) user@host:~/src $ | |||
Pour sortir du virtualenv, entrez la commande `deactivate`. | |||
Pour sortir du virtualenv, entrez la commande ``deactivate``. | |||
## Conclusion | |||
Conclusion | |||
---------- | |||
Le système de gestions des dépendances de Python peut paraître compliqué et bizarre, surtout venant d'autres langages. | |||
Le système de gestions des dépendances de Python peut paraître compliqué | |||
et bizarre, surtout venant d'autres langages. | |||
Mon conseil est de toujours suivre ces deux règles : | |||
Mon conseil est de toujours suivre ces deux règles: | |||
* Un virtualenv par projet et par version de Python | |||
* Toujours utiliser `pip` *depuis* un virtualenv | |||
* Toujours utiliser ``pip`` *depuis* un virtualenv | |||
Certes, cela peut paraître fastidieux, mais c'est une méthode qui vous évitera probablement de vous arracher les cheveux (croyez-en mon expérience). | |||
Certes, cela peut paraître fastidieux, mais c'est une méthode qui vous | |||
évitera probablement de vous arracher les cheveux (croyez-en mon expérience). |
@@ -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"] | |||
exclude_patterns = [] | |||
html_show_sourcelink = False | |||
html_theme_path = [sphinx_nameko_theme.get_html_theme_path()] | |||
html_theme = "nameko" | |||
html_static_path = ["_static"] |
@@ -2,20 +2,20 @@ Programmation en Python | |||
======================= | |||
.. toctree:: | |||
:maxdepth: 2 | |||
:maxdepth: 1 | |||
:caption: Table des matières: | |||
01-introduction/index | |||
02-premiers-pas/index | |||
03-variables-et-types/index | |||
04-code-source/index | |||
05-flot-de-controle/index | |||
06-fonctions/index | |||
07-listes/index | |||
08-none-et-pass/index | |||
09-dictionnaires/index | |||
10-tuples/index | |||
11-classes-01/index | |||
12-modules-01/index | |||
13-classes-02/index | |||
14-bibliothèques-01/index | |||
01-introduction/index | |||
02-premiers-pas/index | |||
03-variables-et-types/index | |||
04-code-source/index | |||
05-flot-de-controle/index | |||
06-fonctions/index | |||
07-listes/index | |||
08-none-et-pass/index | |||
09-dictionnaires/index | |||
10-tuples/index | |||
11-classes-01/index | |||
12-modules-01/index | |||
13-classes-02/index | |||
14-bibliothèques-01/index |