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