| @@ -6,6 +6,8 @@ Ce dépôt contient: | |||||
| * Un répertoire par saison, contenant à chaque fois les sources des présentations (dans un sous-répertoire 'sessions/'), et le code écrit pendant les ateliers (dans un sous-répertoire 'sources/') | * Un répertoire par saison, contenant à chaque fois les sources des présentations (dans un sous-répertoire 'sessions/'), et le code écrit pendant les ateliers (dans un sous-répertoire 'sources/') | ||||
| * Enfin, un répertoire cours/ contenant les sources du cours en ligne. | |||||
| # License | # License | ||||
| Le contenu de ce dépôt (cours et code source) est mis à disposition gratuitement selon les termes de la [Licence Creative Commons Attribution 3.0 France](https://creativecommons.org/licenses/by/3.0/fr/). | Le contenu de ce dépôt (cours et code source) est mis à disposition gratuitement selon les termes de la [Licence Creative Commons Attribution 3.0 France](https://creativecommons.org/licenses/by/3.0/fr/). | ||||
| @@ -0,0 +1,2 @@ | |||||
| explication des exercices | |||||
| insérer exercices pour chaque épisode de la saison 2 | |||||
| @@ -0,0 +1,6 @@ | |||||
| --- | |||||
| title: "{{ replace .Name "-" " " | title }}" | |||||
| date: {{ .Date }} | |||||
| draft: true | |||||
| --- | |||||
| @@ -0,0 +1,49 @@ | |||||
| baseURL: https://dmerej.info/books/python | |||||
| title: Programmation en Python | |||||
| theme: book | |||||
| disableKinds: ['taxonomy', 'taxonomyTerm'] | |||||
| # Book configuration | |||||
| disablePathToLower: true | |||||
| enableGitInfo: true | |||||
| # Code highlight | |||||
| pygmentsStyle: native | |||||
| pygmentsCodeFences: true | |||||
| params: | |||||
| # (Optional, default 6) Set how many table of contents levels to be showed on page. | |||||
| # Use false to hide ToC, note that 0 will default to 6 (https://gohugo.io/functions/default/) | |||||
| # You can also specify this parameter per page in front matter | |||||
| BookToC: 3 | |||||
| # (Optional, default none) Set the path to a logo for the book. If the logo is | |||||
| # /static/logo.png then the path would be logo.png | |||||
| # BookLogo: /logo.png | |||||
| # (Optional, default none) Set leaf bundle to render as side menu | |||||
| # When not specified file structure and weights will be used | |||||
| # BookMenuBundle: /menu | |||||
| # (Optional, default docs) Specify section of content to render as menu | |||||
| # You can also set value to '*' to render all sections to menu | |||||
| BookSection: docs | |||||
| # Set source repository location. | |||||
| # Used for 'Last Modified' and 'Edit this page' links. | |||||
| BookRepo: ~ | |||||
| # Enable "Edit this page" links for 'doc' page type. | |||||
| # Disabled by default. Uncomment to enable. Requires 'BookRepo' param. | |||||
| # Path must point to 'content' directory of repo. | |||||
| BookEditPath: ~ | |||||
| # Configure the date format used on the pages | |||||
| # - In git information | |||||
| # - In blog posts | |||||
| BookDateFormat: '2 Jan 2006' | |||||
| # (Optional, default true) Enables search function with flexsearch, | |||||
| # Index is built on fly, therefore it might slowdown your website. | |||||
| BookSearch: false | |||||
| @@ -0,0 +1,18 @@ | |||||
| --- | |||||
| title: Introduction | |||||
| type: docs | |||||
| --- | |||||
| # Présentation de ce livre | |||||
| Bienvenue! | |||||
| Ce livre contient les supports de cours destinés aux élève de l'École du Logiciel Libre, | |||||
| où je donne des cours sur Python depuis 2018. | |||||
| Il est destiné à toute personne qui voudrait découvrir la programmation. | |||||
| J'espère qu'il vous plaira! Sinon, vous pouvez vous rendre sur [ma page de contact](https://dmerej.info/blog/fr/pages/about/) et | |||||
| me faire part de vos remarques. | |||||
| @@ -0,0 +1,20 @@ | |||||
| +++ | |||||
| title = "Objet de ce livre" | |||||
| weight = 1 | |||||
| +++ | |||||
| # Objet de ce livre | |||||
| Apprendre la programmation en partant de rien, en utilisant Python et la ligne de commande | |||||
| # Pourquoi Python? | |||||
| * Excellent langage pour débuter | |||||
| * Mon langage préféré | |||||
| * Vraiment cross-platform (sauf pour le mobile) | |||||
| # Pourquoi la ligne de commande? | |||||
| Interface universelle | |||||
| La plus simple à utiliser (en Python en tout cas) | |||||
| @@ -0,0 +1,54 @@ | |||||
| +++ | |||||
| title = "Le langage Python" | |||||
| weight = 3 | |||||
| +++ | |||||
| # Présentation du langage Python | |||||
| ## Utilisation de Python | |||||
| * Aussi appelé "langage de script", `glue language` | |||||
| * Bon partout, excellent nulle part | |||||
| * Exemples d'utilisation: | |||||
| * Sciences (physique, chimie, linguistique ...) | |||||
| * Animation (Pixar, Disney ...) | |||||
| * Sites web (journaux, youtube, ...) | |||||
| * Ligne de commande | |||||
| * ... | |||||
| ## Petit détour: version d'un programme | |||||
| * Comme les versions d'un document | |||||
| * Si le nombre est plus grand, c'est plus récent | |||||
| * Souvent en plusieurs morceaux: `1.3, 1.4, 3.2.5`. etc | |||||
| * Plus l'écart est grand, plus le programme a changé. | |||||
| * `3.2.5 -> 3.2.6`: pas grand-chose | |||||
| * `1.5.1 -> 4.3`: beaucoup de changements | |||||
| * On omet souvent le reste des numéros quand c'est pas nécessaire | |||||
| ## Historique | |||||
| * Créé par Guido van Rossum. Conçu à la base pour l'enseignement. | |||||
| * Le nom vient des Monty Python (si, si) | |||||
| * Python 1: Sortie en 1991 | |||||
| * Python 2: en 2000 | |||||
| * Python 3: en 2008 | |||||
| ## Le grand schisme | |||||
| La plupart des langages continuent à être compatibles d'une version à l'autre. | |||||
| *Ce n'est pas le cas pour Python3*, et ça a causé beaucoup de confusion et de débats. | |||||
| Heureusement, 10 ans plus tard, la situation s'est arrangée, et Python2 cessera d'être maintenu le premier janvier 2020. | |||||
| ## Python3 | |||||
| Ce cours fonctionne donc uniquement avec Python3. | |||||
| N'utilisez *pas* Python2, sinon certaines choses expliquées ici ne marcheront pas :/ | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 1 - Introduction" | |||||
| weight = 1 | |||||
| +++ | |||||
| @@ -0,0 +1,25 @@ | |||||
| +++ | |||||
| title = "Ligne de commande" | |||||
| weight = 2 | |||||
| +++ | |||||
| # La ligne de commande | |||||
| ## Pourquoi la ligne de commande? | |||||
| * Très puissant | |||||
| * Ancien, mais toujours d'actualité | |||||
| * Indispensable dans de nombreux cas | |||||
| * Écrire des programmes qui marche dans la ligne de commande est (relativement) simple | |||||
| * Possibilités infines, même si on ne fait que manipuler du texte | |||||
| ## Les bases | |||||
| On tape un commande, on appuie sur entrée, l'ordinateur interprète ce qui a été tapé et affiche un message: | |||||
| * `cd chemin/vers/fichier` | |||||
| * `ls` (ou `dir` sous Windows) | |||||
| * `pwd` | |||||
| * Le premier mot est une *commande*, les autres mots sont des *arguments* | |||||
| @@ -0,0 +1,35 @@ | |||||
| +++ | |||||
| title = "L'interpréteur interactif" | |||||
| weight = 4 | |||||
| +++ | |||||
| # L'interpréteur interactif | |||||
| ## Installation | |||||
| Il se lance depuis l'invite de commande du système d'exploitation: | |||||
| ``` | |||||
| $ python3 | |||||
| Python 3.7.1 (default, Oct 22 2018, 10:41:28) | |||||
| [GCC 8.2.1 20180831] on linux | |||||
| Type "help", "credits" or "license" for more information. | |||||
| >>> | |||||
| ``` | |||||
| ## Deux invites de commandes | |||||
| Notez les trois chevrons: `>>>`. Cela vous permet de différencier l'invite | |||||
| de commandes du système d'exploitation de celle de Python. | |||||
| * Système d'exploitation -> Python: taper `python3` (sans arguments) | |||||
| * Python -> Système d'exploitation: taper `quit()` | |||||
| ## Note | |||||
| À partir de maintenant, recopiez les entrées sur les slides dans votre propre | |||||
| interpréteur. | |||||
| Vous devez taper la même chose après l'invite de commande ('>>>') et vous devez voir les mêmes réponses. | |||||
| Si ce n'est pas le cas, relisez attentitivement ce que vous avez tapé, sinon [contactez-moi](https://dmerej.info/blog/fr/pages/about/). | |||||
| @@ -0,0 +1,62 @@ | |||||
| +++ | |||||
| title = "Maths simples" | |||||
| weight = 5 | |||||
| +++ | |||||
| # Maths simples | |||||
| ## Opérations avec des entiers | |||||
| ``` | |||||
| >>> 1 | |||||
| 1 | |||||
| >>> 2 | |||||
| 2 | |||||
| >>> 1 + 2 | |||||
| 3 | |||||
| >>> 2 * 3 | |||||
| 6 | |||||
| ``` | |||||
| ## Opérantions avec des flottants | |||||
| C'est le `.` qui fait le flottant | |||||
| ``` | |||||
| >>> 0.5 | |||||
| 0.5 | |||||
| >>> 0.5 + 0.2 | |||||
| 0.7 | |||||
| >>> 10 / 3 | |||||
| 3.3333333333333335 | |||||
| ``` | |||||
| *Note: les flottants sont imprécis* | |||||
| ## Division entières et modulo | |||||
| ``` | |||||
| >>> 14 // 3 | |||||
| 4 | |||||
| >>> 14 % 3 | |||||
| 2 | |||||
| ``` | |||||
| *Le `%` n'a rien à voir avec un pourcentage!* | |||||
| ## Priorité des opérations | |||||
| ``` | |||||
| >>> 1 + 2 * 3 | |||||
| 7 | |||||
| >>> (1 + 2) * 3 | |||||
| 9 | |||||
| ``` | |||||
| *Les parenthèses permettent de grouper les expressions* | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 2 - Premiers pas" | |||||
| weight = 2 | |||||
| +++ | |||||
| @@ -0,0 +1,49 @@ | |||||
| +++ | |||||
| title = "Variables" | |||||
| weight = 6 | |||||
| +++ | |||||
| # Variables | |||||
| ```python | |||||
| >>> a = 2 | |||||
| >>> a | |||||
| 2 | |||||
| >>> b = 3 | |||||
| >>> a + b | |||||
| 5 | |||||
| ``` | |||||
| * On peut nommer des valeurs | |||||
| * On peut afficher la valeur d'une variable entrant son nom dans le REPL | |||||
| ```python | |||||
| >>> a = 2 | |||||
| >>> a | |||||
| 2 | |||||
| >>> a = 3 | |||||
| >>> a | |||||
| 3 | |||||
| ``` | |||||
| * On peut changer la valeur d'une variable (d'où son nom) | |||||
| * Quand Python essaie d'exécuter du code, il commence par remplacer les | |||||
| variables par leurs valeurs | |||||
| ## Nom des variables | |||||
| Préférez des noms longs et descriptifs | |||||
| Toujours en minuscules | |||||
| Séparez les "mots" par des tirets bas (underscore) | |||||
| ```python | |||||
| >>> score = 42 | |||||
| >>> age_moyen = 22 | |||||
| ``` | |||||
| @@ -0,0 +1,60 @@ | |||||
| +++ | |||||
| title = "Chaînes de caractères" | |||||
| weight = 7 | |||||
| +++ | |||||
| # Chaînes de caractères | |||||
| Aussi appelées "string". | |||||
| Avec des simple quotes (`'`) | |||||
| ```python | |||||
| >>> 'Bonjour monde!' | |||||
| 'Bonjour monde!' | |||||
| ``` | |||||
| Marche aussi avec des double quotes (`"`) | |||||
| ```python | |||||
| >>> "Bonjour, monde!" | |||||
| 'Bonjour monde' | |||||
| ``` | |||||
| ## Double et simple quotes | |||||
| On peut mettre des simples quotes dans des double quotes et vice-versa. | |||||
| ```python | |||||
| >>> "Il a dit: 'bonjour' ce matin." | |||||
| "Il a dit: 'bonjour' ce matin." | |||||
| >>> 'Il a dit: "bonjour" ce matin' | |||||
| 'Il a dit: "bonjour" ce matin!' | |||||
| ``` | |||||
| ## Échappement | |||||
| Avec la barre oblique inversée "backslash" | |||||
| ```python | |||||
| >>> 'Il a dit: "bonjour". C\'est sympa!' | |||||
| 'Il a dit: "bonjour". C\'est sympa!' | |||||
| ``` | |||||
| ## Concaténation | |||||
| ```python | |||||
| >>> name = "John" | |||||
| >>> message = "Bonjour " + name + " !" | |||||
| >>> message | |||||
| "Bonjour John !" | |||||
| ``` | |||||
| @@ -0,0 +1,39 @@ | |||||
| +++ | |||||
| title = "Types" | |||||
| weight = 8 | |||||
| +++ | |||||
| # Types | |||||
| ```python | |||||
| >>> a = 42 | |||||
| >>> message = "La réponse est: " + a | |||||
| TypeError: can only concatenate str (not "int") to str | |||||
| ``` | |||||
| *Notre premier message d'erreur !* | |||||
| On ne mélange pas les torchons et les serviettes! | |||||
| ## Conversions | |||||
| ### Entier vers string | |||||
| ```python | |||||
| >>> a = 42 | |||||
| >>> message = "La réponse est: " + str(a) | |||||
| >>> message | |||||
| 'La réponse est 42' | |||||
| ``` | |||||
| ### String vers entier | |||||
| ```python | |||||
| >>> answer = int("42") | |||||
| >>> answer | |||||
| 42 | |||||
| ``` | |||||
| Notez les parenthèses autour des valeurs. | |||||
| @@ -0,0 +1,60 @@ | |||||
| +++ | |||||
| title = "Booléens et conditions" | |||||
| weight = 9 | |||||
| +++ | |||||
| # Booléens et conditions | |||||
| ## True et False | |||||
| En Python ce sont des mots-clés et les valeurs sont en majuscules! | |||||
| ## Assignation | |||||
| On peut assigner des variables aux valeurs True et False | |||||
| ``` | |||||
| >>> la_terre_est_plate = False | |||||
| ... | |||||
| >>> python_c_est_genial = True | |||||
| ``` | |||||
| ## Comparaisons | |||||
| ``` | |||||
| >>> a = 2 | |||||
| >>> b = 3 | |||||
| >>> a > b | |||||
| False | |||||
| ``` | |||||
| ``` | |||||
| >>> 2 + 2 == 4 | |||||
| True | |||||
| ``` | |||||
| Note: `==` pour la comparaison, `=` pour l'assignation | |||||
| ``` | |||||
| >>> a = 2 | |||||
| >>> b = 3 | |||||
| >>> a != b | |||||
| True | |||||
| >>> 2 + 2 >= 4 | |||||
| True | |||||
| ``` | |||||
| ``` | |||||
| >>> a = 2 | |||||
| >>> a < 2 | |||||
| False | |||||
| >>> 1 < a < 3 | |||||
| True | |||||
| ``` | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 3 - Variables et types" | |||||
| weight = 3 | |||||
| +++ | |||||
| @@ -0,0 +1,107 @@ | |||||
| +++ | |||||
| title = "Code source" | |||||
| weight = 10 | |||||
| +++ | |||||
| # Code source | |||||
| ## Non persistance des variables | |||||
| ```python | |||||
| $ python3 | |||||
| >>> a = 2 | |||||
| >>> quit() | |||||
| ``` | |||||
| ```python | |||||
| $ python3 | |||||
| >>> a | |||||
| Traceback (most recent call last): | |||||
| File "<stdin>", line 1, in <module> | |||||
| NameError: name 'a' is not defined | |||||
| ``` | |||||
| ## Du code dans un fichier | |||||
| Aussi appelé: "code source", ou "source". | |||||
| L'essence du logiciel libre :) | |||||
| ## Installation d'un éditeur de texte simple | |||||
| * Linux; `gedit`, `kate`, ... | |||||
| * macOS: `CotEditor` | |||||
| * Windows: `Notepad++` | |||||
| J'insiste sur **simple**. Inutile d'installer un IDE pour le moment. | |||||
| ## Configuration | |||||
| * Police de caractères à chasse fixe | |||||
| * Indentation de *4 espaces* | |||||
| * Remplacer tabulation par des espaces | |||||
| * Activer la coloration syntaxique | |||||
| ## Notre premier fichier source | |||||
| Insérez le code suivant dans votre éditeur de texte | |||||
| ```python | |||||
| # Affiche un message | |||||
| print("Bonjour, monde") | |||||
| ``` | |||||
| Sauvegardez dans un fichier `bonjour.py` dans `Documents/e2l/python` par exemple | |||||
| # Démonstration avec `kate` | |||||
| // TODO: conseiller un éditeur par plateforme | |||||
| C'est l'éditeur que nous utiliserons pour nos ateliers. | |||||
| * Pour l'environement KDE, mais ça marche bien sous Gnome aussi | |||||
| * Coloration syntaxique | |||||
| * Auto-complétion | |||||
| ## Lancer du code en ligne de commande | |||||
| ```text | |||||
| cd Documents/e2l/python/ | |||||
| python3 bonjour.py | |||||
| Bonjour, monde | |||||
| ``` | |||||
| * Les lignes commençant par dièse (`#`) ont été ignorées - ce sont des *commentaires*. | |||||
| * `print()` affiche la valeur, comme dans le REPL. | |||||
| ## Note importante | |||||
| Vous avez juste besoin: | |||||
| * d'un éditeur de texte | |||||
| * de Python3 installé | |||||
| * d'une ligne de commande | |||||
| Pas la peine d'installer quoique ce soit de plus pour le moment | |||||
| // TODO: dupliqué? | |||||
| 1. *Recopiez* le code affiché dans votre éditeur, à la suite du code | |||||
| déjà écrit | |||||
| 2. Lancez le code depuis la ligne de commande | |||||
| 3. Réparez les erreurs éventuelles | |||||
| 4. Recommencez | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 4 - code source" | |||||
| weight = 4 | |||||
| +++ | |||||
| @@ -0,0 +1,115 @@ | |||||
| +++ | |||||
| title = "Flôt de contrôle" | |||||
| weight = 11 | |||||
| +++ | |||||
| # Flot de contrôle | |||||
| L'essence de la programmation! | |||||
| ## if | |||||
| ```python | |||||
| a = 3 | |||||
| b = 4 | |||||
| if a == b: | |||||
| print("a et b sont égaux") | |||||
| print("on continue") | |||||
| ``` | |||||
| Notes: | |||||
| * deux points à la fin de la ligne | |||||
| * indentation après les deux points | |||||
| * si la condition n'est pas vraie, rien ne se passe | |||||
| Notez qu'on peut mettre uniquement une variable ou une valeur | |||||
| après le if. Ceci ne fonctionne pas: | |||||
| ```python | |||||
| if a = 3: | |||||
| print("a égale 3") | |||||
| ``` | |||||
| et fait une erreur de syntaxe | |||||
| ## if / else | |||||
| ```python | |||||
| a = 3 | |||||
| b = 4 | |||||
| if a == b: | |||||
| print("a et b sont égaux") | |||||
| else: | |||||
| print("a et b sont différent") | |||||
| ``` | |||||
| ## if / elif | |||||
| ```python | |||||
| if age < 10: | |||||
| print("inférieur à dix") | |||||
| elif 10 <= age < 20: | |||||
| print("âge entre 10 et 20") | |||||
| elif 20 <= age < 40: | |||||
| print("âge entre 20 et 40") | |||||
| else: | |||||
| print("âge supérieur à 40") | |||||
| ``` | |||||
| On peut mettre autont de `elif` qu'on veut! | |||||
| Le derier `else` s'éxécute en dernier | |||||
| ## while | |||||
| Répéter tant qu'une condition est vraie | |||||
| ```python | |||||
| i = 0 | |||||
| while i < 3: | |||||
| print(i) | |||||
| i = i + 1 | |||||
| ``` | |||||
| ``` | |||||
| 0 | |||||
| 1 | |||||
| 2 | |||||
| ``` | |||||
| ## Notre première boucle infinie | |||||
| ```python | |||||
| while True: | |||||
| print("spam!") | |||||
| ``` | |||||
| CTRL-C pour interrompre | |||||
| ## Combiner while et if | |||||
| On peut "sortir" de la boucle `while` avec `break` | |||||
| ```python | |||||
| i = 0 | |||||
| while True: | |||||
| i = i + 1 | |||||
| print(i) | |||||
| if i > 3: | |||||
| break | |||||
| ``` | |||||
| ``` | |||||
| 1 | |||||
| 2 | |||||
| 3 | |||||
| 4 | |||||
| ``` | |||||
| @@ -0,0 +1,38 @@ | |||||
| +++ | |||||
| title = "Exercice" | |||||
| weight = 12 | |||||
| +++ | |||||
| # Exercice | |||||
| // TODO: explication des exercises | |||||
| ## Lire une entrée utilisateur | |||||
| * `input()` (encore des parenthèses ...) | |||||
| * interrompt le script | |||||
| * lit ce que l'utilisateur tape jusqu'à ce qu'il tape "entrée". | |||||
| * renvoie une string | |||||
| ## Le jeu | |||||
| On fait deviner un nombre à l'utilisateur, en affichant 'trop grand', 'trop petit' | |||||
| jusqu'à ce qu'il trouve la valeur exacte. | |||||
| ## Squelette | |||||
| // TODO: | |||||
| * explication du Squelette | |||||
| * pas de solution! | |||||
| ```python | |||||
| # faites moi confiance, les deux lignes ci-dessous | |||||
| # permettent de tirer un nombre au hasard entre 0 et 100 | |||||
| import random | |||||
| nombre = random.randint(0, 100) | |||||
| print("devine le nombre auquel je pense") | |||||
| # votre code ici | |||||
| ``` | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 5 - Flot de contrôle" | |||||
| weight = 5 | |||||
| +++ | |||||
| @@ -0,0 +1,75 @@ | |||||
| +++ | |||||
| title = "Fonctions" | |||||
| weight = 1 | |||||
| +++ | |||||
| # Fonctions | |||||
| ## Fonction sans argument | |||||
| Définition: | |||||
| ```python | |||||
| def dire_bonjour(): | |||||
| print("Bonjour") | |||||
| ``` | |||||
| * avec `def` | |||||
| * avec un `:` à la fin et un _bloc indenté_ (appelé le "corps") | |||||
| Appel: | |||||
| ``` | |||||
| >>> dire_bonjour() | |||||
| Bonjour | |||||
| ``` | |||||
| * avec le nom de la fonction et des parenthèses | |||||
| ## Le pouvoir des fonctions | |||||
| Ici on vient de créer une nouvelle fonctionnalité | |||||
| à Python. Avant qu'on définisse la fonction | |||||
| `dire_bonjour()`, il ne savait pas dire bonjour, | |||||
| il savait uniquement afficher des messages à | |||||
| l'écran. | |||||
| On dit qu'on a _créé une abstraction_. Et | |||||
| c'est une technique extrêmement utile en | |||||
| programmation. | |||||
| ## Fonction avec un argument | |||||
| Définition: avec l'argument à l'intérieur des parenthèses | |||||
| ```python | |||||
| def dire_bonjour(prénom): | |||||
| print("Bonjour " + prénom) | |||||
| ``` | |||||
| Appel: en passant une variable ou une valeur dans les parenthèses | |||||
| ```python | |||||
| >>> dire_bonjour("Germaine") | |||||
| Bonjour Germaine | |||||
| >>> prénom_de_charlotte = "Charlotte" | |||||
| >>> dire_bonjour(prénom_de_charlotte) | |||||
| Bonjour Charlotte | |||||
| ``` | |||||
| ## Exécution d'une fonction | |||||
| C'est exatement comme si on assignait les arguments de la fonction avant d'éxécuter le code | |||||
| dans le corps | |||||
| ```python | |||||
| # Ceci: | |||||
| dire_bonjour("Dimitri") | |||||
| # Est équivalent à cela: | |||||
| prénom_de_dimitri = "Dimitri" | |||||
| print("Bonjour " + prénom_de_dimitri) | |||||
| # Lui-même équivalent à: | |||||
| print("Bonjour " + "Dimitri") | |||||
| ``` | |||||
| @@ -0,0 +1,39 @@ | |||||
| +++ | |||||
| title = "Portée des variables" | |||||
| weight = 2 | |||||
| +++ | |||||
| # Portée des variables | |||||
| Les arguments d'une fonction n'existent que dans le corps de celle-ci | |||||
| ```python | |||||
| def dire_bonjour(prénom): | |||||
| print("Bonjour " + prénom) | |||||
| dire_bonjour("Dimitri") # Ok | |||||
| print(prénom) # Erreur | |||||
| ``` | |||||
| Les variables en dehors des fonctions sont disponibles partout: | |||||
| ```python | |||||
| salutation = "Bonjour " | |||||
| def dire_bonjour(prénom): | |||||
| print(salutation + prénom) | |||||
| dire_bonjour("Dimitri") | |||||
| ``` | |||||
| Une variable peut avoir en "cacher" une autre si elle a une portée différente | |||||
| ```python | |||||
| def dire_bonjour(prénom): | |||||
| print("Bonjour " + prénom) # portée: uniquement dans | |||||
| # le corps dire_bonjour | |||||
| prénom = "Dimitri" # portée: dans tout le programme | |||||
| dire_bonjour(prénom) # Ok | |||||
| ``` | |||||
| @@ -0,0 +1,43 @@ | |||||
| +++ | |||||
| title = "Fonctions à plusieurs arguments" | |||||
| weight = 2 | |||||
| +++ | |||||
| # Fonctions à plusieurs arguments | |||||
| On peut mettre autant d'arguments qu'on veut, séparés | |||||
| par des virgules: | |||||
| ```python | |||||
| def afficher_addition(x, y): | |||||
| résultat = x + y | |||||
| print(résultat) | |||||
| ``` | |||||
| ```python | |||||
| >>> a = 4 | |||||
| >>> b = 5 | |||||
| >>> afficher_addition(a, b) | |||||
| 9 | |||||
| ``` | |||||
| ## Arguments nommés | |||||
| En Python, on peut aussi utiliser le *nom* des arguments au lieu de | |||||
| leur position: | |||||
| ```python | |||||
| def dire_bonjour(prénom): | |||||
| print("Bonjour " + prénom) | |||||
| ``` | |||||
| ```python | |||||
| >>> dire_bonjour(prénom="Gertrude") | |||||
| Bonjour Gertrude | |||||
| >>> afficher_addition(y=3, x=4) | |||||
| 7 | |||||
| ``` | |||||
| // TODO: soustraction | |||||
| @@ -0,0 +1 @@ | |||||
| @@ -0,0 +1,28 @@ | |||||
| +++ | |||||
| title = "Arguments par défaut" | |||||
| weight = 4 | |||||
| +++ | |||||
| # Arguments par défaut | |||||
| On peut aussi mettre des valeurs par défaut: | |||||
| Définition: | |||||
| ```python | |||||
| def dire_bonjour(prénom, enthousiaste=False): | |||||
| message = "Bonjour " + prénom | |||||
| if enthousiaste: | |||||
| message += "!" | |||||
| print(message) | |||||
| ``` | |||||
| Appel: | |||||
| ```python | |||||
| >>> dire_bonjour("Thomas", enthousiaste=True) | |||||
| Bonjour Thomas! | |||||
| >>> dire_bonjour("Thomas", enthousiaste=False) | |||||
| Bonjour Thomas | |||||
| >>> dire_bonjour("Thomas") | |||||
| Bonjour Thomas | |||||
| ``` | |||||
| @@ -0,0 +1,46 @@ | |||||
| +++ | |||||
| title = "Fonctions natives" | |||||
| weight = 5 | |||||
| +++ | |||||
| # Fonctions natives | |||||
| Fonctions qui sont toujours présentes dans l'interpréteur. On en a déjà vu quelques unes: | |||||
| * `print`, `input`: écrire et lire sur la ligne de commande | |||||
| * `str`, `int`: convertir des entiers en strings et vice-versa | |||||
| Il y en a tout un tas! | |||||
| La liste ici: https://docs.python.org/fr/3/library/functions.html#built-in-funcs | |||||
| ## Retour sur print | |||||
| On peut passer autant d'arguments qu'on veut à `print` et: | |||||
| * Il les sépare par des espaces | |||||
| * Ajoute un retour à la ligne à la fin: | |||||
| ```python | |||||
| >>> prénom = "Charlotte" | |||||
| print("Bonjour", pŕenom) | |||||
| Bonjour Charlotte | |||||
| ``` | |||||
| On peut demander à `print` de changer son séparateur: | |||||
| ```python | |||||
| >>> a = "chauve" | |||||
| >>> b = "souris" | |||||
| >>> print(a, b, sep="-") | |||||
| chauve-souris | |||||
| ``` | |||||
| Ou de changer le caractère de fin: | |||||
| ```python | |||||
| >>> print("Ceci tient", end="") | |||||
| >>> print("sur une seule ligne") | |||||
| Ceci tient sur une seule ligne | |||||
| ``` | |||||
| @@ -0,0 +1,43 @@ | |||||
| +++ | |||||
| title = "return" | |||||
| weight = 6 | |||||
| +++ | |||||
| # Valeur de retour d'une fonction | |||||
| Définition avec le mot `return` | |||||
| ```python | |||||
| def additionner(x, y): | |||||
| return x + y | |||||
| ``` | |||||
| Récupérer la valeur de retour | |||||
| ```python | |||||
| >>> a = 3 | |||||
| >>> b = 4 | |||||
| >>> c = additionner(a, b) # encore une assignation | |||||
| >>> c | |||||
| 7 | |||||
| ``` | |||||
| # Sortir d'une fonction avec return | |||||
| `return` interrompt également l'éxécution du | |||||
| corps de la fonction: | |||||
| ```python | |||||
| def dire_bonjour(prénom, première_fois=False): | |||||
| print("Bonjour", prénom) | |||||
| if not première_fois: | |||||
| return | |||||
| print("Heureux de faire votre connaissance") | |||||
| ``` | |||||
| ```python | |||||
| >>> dire_bonjour("Dimitri", première_fois=True) | |||||
| Bonjour Dimitri | |||||
| Heureux de faire votre connaissance | |||||
| >>> dire_bonjour("Dimitri", première_fois=False) | |||||
| Bonjour Dimitri | |||||
| ``` | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 6 - Fonctions" | |||||
| weight = 6 | |||||
| +++ | |||||
| @@ -0,0 +1,164 @@ | |||||
| +++ | |||||
| title = "Listes" | |||||
| weight = 1 | |||||
| +++ | |||||
| # Listes | |||||
| // TODO: split in pages | |||||
| ## Définition | |||||
| Une liste est une _suite ordonée_ d'éléments. | |||||
| ## Créer une liste | |||||
| Avec `[]`, et les élements séparés par des virgules: | |||||
| ```python | |||||
| liste_vide = [] | |||||
| trois_entiers = [1, 2, 3] | |||||
| ``` | |||||
| ## Listes hétérogènes | |||||
| On peut mettre des types différents dans la même liste | |||||
| ```python | |||||
| ma_liste = [True, 2, "trois"] | |||||
| ``` | |||||
| On peut aussi mettre des listes dans des listes: | |||||
| ```python | |||||
| liste_de_listes = [[1, 2], ["Germaine", "Gertrude"]] | |||||
| ``` | |||||
| ## Connaître la taille d'une liste | |||||
| Avec `len()` - encore une fonction native | |||||
| ```python | |||||
| >>> liste_vide = [] | |||||
| >>> len(liste_vide) | |||||
| 0 | |||||
| >>> trois_entiers = [1, 2, 3] | |||||
| >>> len(trois_entiers) | |||||
| 3 | |||||
| ``` | |||||
| ## Concaténation de listes | |||||
| Avec `+` | |||||
| ```python | |||||
| >>> prénoms = ["Alice", "Bob"] | |||||
| >>> prénoms += ["Charlie", "Eve"] | |||||
| >>> prénoms | |||||
| ['Alice', 'Bob', "Charlie", 'Eve'] | |||||
| ``` | |||||
| On ne peut concaténer des listes que avec d'autres listes: | |||||
| ```python | |||||
| >>> scores = [1, 2, 3] | |||||
| >>> scores += 4 # TypeError | |||||
| >>> scores += [4] # OK | |||||
| ``` | |||||
| ## Test d'appartenance | |||||
| Avec `in`: | |||||
| ```python | |||||
| >>> prénoms = ["Alice", "Bob"] | |||||
| >>> "Alice" in prénoms | |||||
| True | |||||
| ``` | |||||
| ```python | |||||
| >>> prénoms = ["Alice", "Bob"] | |||||
| >>> "Charlie" in prénoms | |||||
| False | |||||
| ``` | |||||
| ## Itérer sur les élements d'une liste | |||||
| Avec `for ... in` | |||||
| ```python | |||||
| prénoms = ["Alice", "Bob", "Charlie"] | |||||
| for prénom in prénoms: | |||||
| # La variable 'prénom" est assignée à chaque | |||||
| # élément de la liste | |||||
| print("Bonjour", prénom) | |||||
| Bonjour Alice | |||||
| Bonjour Bob | |||||
| Bonjour Charlie | |||||
| ``` | |||||
| ## Indéxer une liste | |||||
| * Avec `[]` et un entier | |||||
| * Les index valides vont de 0 à `n-1` où `n` est la | |||||
| taille de la liste. | |||||
| ```python | |||||
| >>> fruits = ["pomme", "orange", "poire"] | |||||
| >>> fruits[0] | |||||
| "pomme" | |||||
| >>> fruits[1] | |||||
| "orange" | |||||
| >>> list[2] | |||||
| "poire" | |||||
| >>> fruits[3] # IndexError | |||||
| ``` | |||||
| ## Modifier une liste | |||||
| Encore une assignation: | |||||
| ```python | |||||
| >>> fruits = ["pomme", "orange", "poire"] | |||||
| >>> fruits[0] = "abricot" | |||||
| >>> fruits | |||||
| ["abricot", "orange", "poire"] | |||||
| ``` | |||||
| ## Les strings sont aussi des listes (presque) | |||||
| On peut itérer sur les caractères d'une string: | |||||
| ```python | |||||
| for c in "vache": | |||||
| print(c) | |||||
| v | |||||
| a | |||||
| c | |||||
| h | |||||
| e | |||||
| ``` | |||||
| On peut tester si un caractère est présent: | |||||
| ```python | |||||
| >>> "e" in "vache" | |||||
| True | |||||
| >>> "x" in "vache" | |||||
| False | |||||
| ``` | |||||
| Mais on neut peut pas modifier une string | |||||
| ```python | |||||
| >>> prénom = "Charlotte" | |||||
| >>> prénom[0] | |||||
| "C" | |||||
| >>> prénom[3] | |||||
| "r" | |||||
| >>> prénom[0] = "X" # TypeError | |||||
| ``` | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 7 - Listes" | |||||
| weight = 7 | |||||
| +++ | |||||
| @@ -0,0 +1,98 @@ | |||||
| +++ | |||||
| title = "None" | |||||
| weight = 1 | |||||
| +++ | |||||
| # None | |||||
| ## Définition | |||||
| `None` est une "valeur magique" natif en Python. Il est toujours présent, et il est unique. | |||||
| Un peu comme `True` et `False` qui sont deux valeurs qui servent à représenter tous les booléens. | |||||
| ## Représenter l'absence | |||||
| L'interpréteur intéractif n'affiche rien quand la valeur est None | |||||
| ```python | |||||
| >>> a = 42 | |||||
| >>> a | |||||
| 42 | |||||
| >>> b = None | |||||
| >>> b | |||||
| ``` | |||||
| ## Retourner None | |||||
| En réalité, *toutes* les fonctions pythons retournent *quelque chose*, même quand | |||||
| elle ne contiennent pas le mot-clé `return`. | |||||
| ```python | |||||
| def ne_renvoie_rien(): | |||||
| print("je ne fais qu'afficher quelque chose") | |||||
| ``` | |||||
| ```python | |||||
| >>> resultat = ne_renvoie_rien() | |||||
| "je ne fais qu'afficher quelque chose" | |||||
| >>> resultat | |||||
| ``` | |||||
| ## Opérations avec None | |||||
| La plupart des fonctions que nous avons vues échouent si on leur passe None | |||||
| en argument: | |||||
| ```python | |||||
| >>> len(None) | |||||
| TypeError: object of type 'NoneType' has no len() | |||||
| >>> None < 3 | |||||
| TypeError: '<' not supported between instances of | |||||
| 'NoneType' and 'int' | |||||
| >>> int(None) | |||||
| TypeError: int() argument must be a string, | |||||
| a bytes-like object or a number, | |||||
| not 'NoneType' | |||||
| >>> str(None) | |||||
| 'None' | |||||
| ``` | |||||
| ## Example d'utilisation: | |||||
| ```python | |||||
| def trouve_dans_liste(valeur, liste): | |||||
| for element in liste: | |||||
| if element == valeur: | |||||
| return element | |||||
| return None | |||||
| ``` | |||||
| ```python | |||||
| >>> trouve_dans_liste(2, [1, 2, 3]) | |||||
| 2 | |||||
| >>> trouve_dans_liste(False, [True, False]) | |||||
| False | |||||
| >>> trouve_dans_liste(1, [3, 4]) | |||||
| ``` | |||||
| ```python | |||||
| def trouve_dans_liste(liste, valeur): | |||||
| for element in liste: | |||||
| if element == valeur: | |||||
| return element | |||||
| ``` | |||||
| None est Falsy, et on peut vérifier si une variable vaut None avec `is None` | |||||
| ```python | |||||
| # hypothèse: `ma_valeur` n'est pas None | |||||
| mon_element = trouve_dans_liste(ma_valeur, ma_liste) | |||||
| if mon_element is None: | |||||
| print("élément absent de la liste") | |||||
| if not mon_element: | |||||
| # Peut-être que l'élément n'était pas dans la liste, | |||||
| # ou peut-être y était-il, mais avec une valeur falsy | |||||
| ... | |||||
| ``` | |||||
| @@ -0,0 +1,34 @@ | |||||
| +++ | |||||
| title = "pass" | |||||
| weight = 2 | |||||
| +++ | |||||
| # pass | |||||
| En Python, à cause de l'organisation en blocs indentés, on ne | |||||
| peut pas vraiment avoir de blocs vides. Mais parfois, on | |||||
| a besoin d'un bloc qui ne fasse rien. | |||||
| Dans ce cas, on peut utiliser le mot-clé `pass`, par exemple | |||||
| après un if: | |||||
| ```python | |||||
| une_condition = False | |||||
| if une_condition: | |||||
| pass | |||||
| else: | |||||
| print("une_condition n'est pas vraie") | |||||
| ``` | |||||
| On peut aussi - et c'est l'usage le plus courant - utiliser `pass` pour | |||||
| définir une fonction qui ne fait rien: | |||||
| ```python | |||||
| def ne_fait_rien(): | |||||
| pass | |||||
| ``` | |||||
| ```python | |||||
| >>> ne_fait_rien() | |||||
| <rien> | |||||
| ``` | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 8 - None et pass" | |||||
| weight = 8 | |||||
| +++ | |||||
| @@ -0,0 +1,205 @@ | |||||
| +++ | |||||
| title = "Dictionnaires" | |||||
| weight = 1 | |||||
| +++ | |||||
| # Dictionnaires | |||||
| ## Définition | |||||
| Un dictionaire est une _association_ entre des clés et des valeurs. | |||||
| * Les clés sont uniques | |||||
| * Les valeurs sont arbitraires | |||||
| ## Création de dictionnaires | |||||
| ```python | |||||
| # dictionaire vide | |||||
| >>> {} | |||||
| # une clé, une valeur | |||||
| >>> {"a": 42} | |||||
| # deux clés, deux valeurs | |||||
| >>> {"a": 42, "b": 53} | |||||
| # les clés sont uniques: | |||||
| >>> {"a": 42, "a": 53} | |||||
| {"a": 53} | |||||
| ``` | |||||
| Note: tous les dictionnaires sont truthy, sauf les dictionnaires vides. | |||||
| ## Accès aux valeurs | |||||
| Avec `[]`, comme pour les listes, mais avec une *clé* à la place d'un *index*. | |||||
| ```python | |||||
| >>> scores = {"john": 10, "bob": 42} | |||||
| >>> scores["john"] | |||||
| 10 | |||||
| >>> scores["bob"] | |||||
| 42 | |||||
| >>> scores["charlie"] | |||||
| KeyError | |||||
| ``` | |||||
| ## Test d'appartenance | |||||
| Avec `in`, comme le listes: | |||||
| ```python | |||||
| >>> scores = {"john": 10, "bob": 42} | |||||
| >>> "charlie" in scores | |||||
| False | |||||
| ``` | |||||
| ## Modifier la valeur d'une clé | |||||
| Comme pour les listes: on assigne la nouvelle variable: | |||||
| ```python | |||||
| >>> scores = {"john": 10, "bob": 42} | |||||
| >>> scores["john"] = 20 | |||||
| >>> scores | |||||
| {"john": 20, "bob": 42} | |||||
| ``` | |||||
| ## Créer une nouvelle clé | |||||
| Même méchanisme que pour la modification des clés existantes | |||||
| ```python | |||||
| >>> scores = {"john": 10, "bob": 42} | |||||
| >>> scores["charlie"] = 30 | |||||
| >>> scores | |||||
| {"john": 20, "bob": 42, "charlie": 30} | |||||
| ``` | |||||
| *rappel*: ceci ne fonctionne pas avec les listes! | |||||
| ```python | |||||
| >>> ma_liste = ["a", "b"] | |||||
| >>> ma_liste[1] = "c" # ok | |||||
| ["a", "c"] | |||||
| >>> ma_liste[3] = "d" | |||||
| IndexError | |||||
| ``` | |||||
| ## Itérer sur les clés | |||||
| Avec `for ... in ...`, comme pour les listes | |||||
| ```python | |||||
| scores = {"john": 10, "bob": 42} | |||||
| for nom in scores: | |||||
| # `nom` est assigné à "john" puis "bob" | |||||
| score_associé_au_nom = scores[nom] | |||||
| print(nom, score_associé_au_nom) | |||||
| ``` | |||||
| ## Détruire une clé | |||||
| Avec `del` - un nouveau mot-clé: | |||||
| ```python | |||||
| >>> scores = {"john": 10, "bob": 42} | |||||
| >>> del scores["bob"] | |||||
| >>> scores | |||||
| {"john": 10} | |||||
| ``` | |||||
| ## Détruire un élément d'une liste | |||||
| ```python | |||||
| >>> fruits = ["pomme", "banane", "poire"] | |||||
| >>> del fruits[1] | |||||
| >>> fruits | |||||
| ["pomme", "poire"] | |||||
| ``` | |||||
| ## Détruire une variable | |||||
| ```python | |||||
| >>> mon_entier = 42 | |||||
| >>> mon_entier += 3 | |||||
| >>> mon_entier | |||||
| 45 | |||||
| >>> del mon_entier | |||||
| >>> mon_entier == 45 | |||||
| NameError: name 'mon_entier' is not defined | |||||
| ``` | |||||
| ## Détruire une fonction | |||||
| On peu aussi supprimer des fonctions: | |||||
| ```python | |||||
| def ma_fonction(): | |||||
| print("bonjour") | |||||
| del ma_fonction | |||||
| >>> ma_fonction() | |||||
| NameError: name 'ma_fonction' is not defined | |||||
| ``` | |||||
| ## Des dictionnaires partout | |||||
| Les variables globales d'un programme Python sont dans un dictionnaire, | |||||
| accessible avec la fonction native `globals()`: | |||||
| ```python | |||||
| $ python3 | |||||
| >>> globals() | |||||
| { | |||||
| ... | |||||
| '__doc__': None, | |||||
| '__name__': '__main__', | |||||
| ... | |||||
| } | |||||
| ``` | |||||
| On reparlera de `__doc__` et `__name__` un autre jour ... | |||||
| ```python | |||||
| $ python3 | |||||
| >>> a = 42 | |||||
| >>> globals() | |||||
| { | |||||
| ... | |||||
| '__doc__': None, | |||||
| '__name__': '__main__', | |||||
| ... | |||||
| 'a': 42 | |||||
| } | |||||
| ``` | |||||
| ```python | |||||
| $ python3 | |||||
| >>> a = 42 | |||||
| >>> del globals()["a"] | |||||
| >>> a | |||||
| NameError: name 'a' is not defined | |||||
| ``` | |||||
| On peut accéder aux variables locales d'une fonction avec `locals()` | |||||
| ```python | |||||
| def ma_fonction(): | |||||
| a = 42 | |||||
| b = 3 | |||||
| c = a + b | |||||
| print(locals()) | |||||
| >>> ma_fonction() | |||||
| {'a': 42, 'b': 3, 'c': 45} | |||||
| ``` | |||||
| En revanche, il n'est pas conseillé de modifier le dictionaire renvoyé par `locals()` ... | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 9 - Dictionnaires" | |||||
| weight = 9 | |||||
| +++ | |||||
| @@ -0,0 +1,169 @@ | |||||
| +++ | |||||
| title = "Tuples" | |||||
| weight = 1 | |||||
| +++ | |||||
| # Tuples | |||||
| ## Définition | |||||
| Un tuple est un ensemble *ordonné* et *immuable* d'éléments. Le nombre, l'ordre et la valeur des éléments sont fixes. | |||||
| ## Création de tuples | |||||
| ```python | |||||
| # Un tuple vide | |||||
| () | |||||
| # Un tuple à un élément | |||||
| (1,) # notez la virgule | |||||
| # Un tuple à deux éléments, aussi appelé couple | |||||
| (1, 2) | |||||
| ``` | |||||
| Sauf pour le tuple vide, c'est la *virgule* qui fait le tuple | |||||
| Note: tous les tuples sont truthy, sauf les tuples vides. | |||||
| # Tuples hétérogènes | |||||
| Comme les listes, les tuples peuvent contenir des éléments de types différents: | |||||
| ```python | |||||
| # Un entier et une string | |||||
| mon_tuple = (42, "bonjour") | |||||
| # Un entier et un autre tuple | |||||
| mon_tuple = (21, (True, "au revoir")) | |||||
| ``` | |||||
| ## Accès | |||||
| Avec `[]` et l'index de l'élément dans le tuple: | |||||
| ```python | |||||
| mon_tuple = (42, "bonjour") | |||||
| mon_tuple[0] | |||||
| 42 | |||||
| mon_tuple[1] | |||||
| "bonjour" | |||||
| ``` | |||||
| ## Modification | |||||
| Interdit! | |||||
| ```python | |||||
| mon_tuple = (42, "bonjour") | |||||
| mon_tuple[0] = 44 | |||||
| TypeError: 'tuple' object does not support item assignment | |||||
| ``` | |||||
| ## Test d'appartenance | |||||
| Avec `in` | |||||
| ```python | |||||
| >>> mon_tuple = (42, 14) | |||||
| >>> 42 in mon_tuple | |||||
| True | |||||
| >>> 14 in mon_tuple | |||||
| True | |||||
| >>> 13 in mon_tuple | |||||
| False | |||||
| ``` | |||||
| ## Déstructuration | |||||
| Créer plusieurs variables en une seule ligne: | |||||
| ```python | |||||
| >>> couple = ("Batman", "Robin") | |||||
| >>> héros, side_kick = couple | |||||
| >>> héros | |||||
| 'Batman' | |||||
| >>> side_kick | |||||
| 'Robin' | |||||
| ``` | |||||
| ## Quelques erreurs classiques | |||||
| ```python | |||||
| >>> héros, side_kick, ennemi = couple | |||||
| ValueError (3 != 2) | |||||
| >>> (héros,) = couple | |||||
| ValueError (1 != 2) | |||||
| # Gare à la virgule: | |||||
| >>> héros, = couple | |||||
| ValueError (1 != 2) | |||||
| ``` | |||||
| ## Pièges | |||||
| ```python | |||||
| f(a, b, c) # appelle f() avec trois arguments | |||||
| f((a, b, c)) # appelle f() avec un seul argument | |||||
| # (qui est lui-même un tuple à 3 valeurs) | |||||
| f(()) # appelle f() avec un tuple vide | |||||
| (a) # juste la valeur de a entre parenthèses | |||||
| (a,) # un tuple à un élément, qui vaut la valeur de a | |||||
| ``` | |||||
| ## On peut aussi déstructurer des listes | |||||
| ```python | |||||
| >>> fruits = ["pomme", "banane", "orange"] | |||||
| >>> premier, deuxième, troisième = fruits | |||||
| >>> premier | |||||
| "pomme" | |||||
| >>> deuxième | |||||
| "banane" | |||||
| >>> troisième | |||||
| "orange" | |||||
| ``` | |||||
| On dit aussi: unpacking | |||||
| ## Utilisations des tuples | |||||
| Pour simplifier des conditions: | |||||
| ```python | |||||
| # Avant | |||||
| if ( | |||||
| ma_valeur == "nord" or | |||||
| ma_valeur == "sud" or | |||||
| ma_valeur == "ouest" or | |||||
| ma_valeur == "est"): | |||||
| print("direction", ma_valeur) | |||||
| ``` | |||||
| ```python | |||||
| # Après | |||||
| if ma_valeur in ("nord", "sud", "est", "ouest"): | |||||
| print("direction", ma_valeur) | |||||
| ``` | |||||
| ## Pour retourner plusieurs valeurs | |||||
| ```python | |||||
| def tire_carte(): | |||||
| valeur = "10" | |||||
| couleur = "trèfle" | |||||
| return (valeur, couleur) | |||||
| v, c = tire_carte() | |||||
| print(v, "de", c) | |||||
| # 10 de trèfle | |||||
| ``` | |||||
| Ce n'est pas une nouvelle syntaxe, juste de la manipulation de tuples! | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 10 - tuples" | |||||
| weight = 10 | |||||
| +++ | |||||
| @@ -0,0 +1,319 @@ | |||||
| +++ | |||||
| title = "Classes" | |||||
| weight = 1 | |||||
| +++ | |||||
| # Classes | |||||
| Ce qu’on a vu jusqu’ici: | |||||
| * Des types simples (entiers, booléens, ...) | |||||
| * Des structures de données (listes, dictionnaires, ...) | |||||
| * Des fonctions qui manipulent ces types ou ces types | |||||
| * Des fonctions qui s’appellent les unes les autres | |||||
| On appelle cet ensemble de concepts, cette façon d'écrire du code, un *paradigme* - | |||||
| et c'est un paradigme *procédural*. | |||||
| On va passer à un autre paradigme: l'*orienté objet*. | |||||
| ## Orienté objet - une première définition | |||||
| Un "objet" informatique *représente* un véritable "objet" physique | |||||
| dans le vrai monde véritable. | |||||
| Ce n'est pas une très bonne définition: | |||||
| 1. Ce n'est pas nécessaire | |||||
| 2. Ce n'est même pas forcément souhaitable! | |||||
| Je le mentionne juste parce que c'est une idée reçue très répandue. | |||||
| ## Orienté objet - 2ème définition | |||||
| Une meilleure définition, c'est de dire que la programmation | |||||
| orientée objet permet de mettre au même endroit: | |||||
| * des données | |||||
| * des fonctions qui opèrent sur ces données | |||||
| L'important c'est que les deux aillent ensemble! | |||||
| *Note: ce n'est pas **la** meilleure définition de l'orienté objet, mais on s'en contentera | |||||
| pour le moment ...* | |||||
| ## Les classes | |||||
| On va parler *d'une* façon de faire de l'orienté objet: avec des classes. | |||||
| Mais notez bien qu'on peut faire de l'orienté objet *sans* classes! | |||||
| ## Le plan de construction | |||||
| On dit souvent qu'en Python, "tout est objet". | |||||
| Pour bien comprendre cela, il faut d'abord parler des *classes* et des *instances de classes*. | |||||
| Une classe est un *plan de construction*, et est définie ainsi: | |||||
| ```python | |||||
| class MaClasse: | |||||
| # du code ici | |||||
| ``` | |||||
| Comme les fonctions, les classes contienent un *corps*, qui est le bloc *identé* en dessous | |||||
| du mot-clé `class`, de nom de la classe et du `:` en fin de ligne. | |||||
| Les classes sont utilisées pour construire des *instances*. | |||||
| ## Créons des instances | |||||
| On peut faire un plan de construction vide avec le mot clé pass: | |||||
| ```python | |||||
| class MaClasse: | |||||
| pass | |||||
| ``` | |||||
| Dans ce cas, on crée une instance en mettant le nom de la classe suivi d'une paire de parenthèses - | |||||
| un peu comme pour appeler une fonction: | |||||
| ```python | |||||
| >>> mon_instance = MaClasse() | |||||
| ``` | |||||
| Ici, `mon_instance` est une *instance* de la classe `MaClasse`. | |||||
| ## Attributs | |||||
| Les attributs sont des éléments **nommés** à *l'intérieur* d'une instance. | |||||
| On peut y accéder avec la syntaxe `<instance>.<attribut>`: | |||||
| ```python | |||||
| y = a.x | |||||
| ``` | |||||
| Ici, `y` est l'attribut `x` de l'instance `a`. | |||||
| Les attributs peuvent être des fonctions: | |||||
| ```python | |||||
| func = a.x | |||||
| func(10) | |||||
| ``` | |||||
| Ici, on crée une variable `func` qui prend la valeur de l'attribut `x` dans l'instance `a`, puis | |||||
| on l'appelle avec l'argument `10` à la ligne suivante. | |||||
| Le code suivant fait exactement la même chose, mais avec une ligne de moins: | |||||
| ```python | |||||
| a.x(10) | |||||
| ``` | |||||
| On peut *créer* des attributs dans *n'importe quel instance*, en utilisant l'*assignation*: | |||||
| ```python | |||||
| >>> mon_instance = MaClasse() | |||||
| # Création de l'attribut `x` dans `mon_instance` | |||||
| >>> mon_instance.x = 42 | |||||
| # Accés à l'attribut `x` dans `mon_instance` | |||||
| >>> mon_instance.mon_attribut | |||||
| 42 | |||||
| ``` | |||||
| ## Méthodes - définition | |||||
| On peut aussi mettre des *méthodes* dans des classes. | |||||
| On utilise `def`, comme pour les fonctions, mais les méthodes *doivent* avoir au | |||||
| moins un argument appelé `self`, et être à l'intérieur du bloc de la classe: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def ma_méthode(self): | |||||
| return 42 | |||||
| ``` | |||||
| ## Méthodes - appel | |||||
| Une méthode ne peut être appelée que depuis une *instance* de | |||||
| la classe: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def ma_méthode(self): | |||||
| return 42 | |||||
| >>> ma_méthode() | |||||
| Erreur | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.ma_méthode() | |||||
| 42 | |||||
| ``` | |||||
| Notez qu'on ne passe *pas* d'argument quand on apelle `ma_méthode` depuis l'instance. | |||||
| ## Méthodes et attributs | |||||
| `self` *prend la valeur de l'instance courante* quand la méthode est appelée. | |||||
| On peut le voir en utilisant des attributs: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def affiche_attribut_x(self): | |||||
| # Accès à l'attribut `x` dans `self` | |||||
| print(self.x) | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.x = 42 | |||||
| >>> mon_instance.affiche_attribut_x() | |||||
| 42 | |||||
| ``` | |||||
| On peut aussi *créer* des attributs dans une méthode: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def crée_attribut_x(self): | |||||
| self.x = 42 | |||||
| def affiche_attribut_x(self): | |||||
| print(self.x) | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.affiche_attribut_x() | |||||
| # Erreur: `mon_instance` n'a pas d'attribut `x` | |||||
| >>> mon_instance.crée_attribut_x() | |||||
| >>> mon_instance.affiche_attribut_x() | |||||
| 42 | |||||
| ``` | |||||
| Les méthodes peuveunt aussi prendre plusieurs arguments, en plus de `self` - mais `self` doit | |||||
| toujours être le premier argument. | |||||
| Par example, pour créer un attribut avec une certaine valeur: | |||||
| ```python | |||||
| class MaClasse | |||||
| def crée_attribut_x(self, valeur_de_x): | |||||
| self.x = valeur_de_x | |||||
| def affiche_attribut_x(self); | |||||
| print(self.x) | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.crée_attribut_x(42) | |||||
| >>> mon_instance.affiche_attribut_x() | |||||
| 42 | |||||
| ``` | |||||
| ## Méthodes appelant d'autres méthodes | |||||
| Comme les méthodes sont *aussi* des attributs, les méthodes d'une instance peuvent s'appeler | |||||
| les unes les autres: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def méthode_1(self): | |||||
| print("démarrage de la méthode 1") | |||||
| print("la méthode 1 affiche bonjour") | |||||
| print("bonjour") | |||||
| print("fin de la méthode 1") | |||||
| def méthode_2(self): | |||||
| print("la méthode 2 appelle la méthode 1") | |||||
| self.méthode_1() | |||||
| print("fin de la méthode 2") | |||||
| ``` | |||||
| ```python | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.méthode_2() | |||||
| ``` | |||||
| ```text | |||||
| la méthode 2 appelle la méthode 1 | |||||
| démarrage de la méthode 1 | |||||
| la méthode 1 affiche bonjour | |||||
| bonjour | |||||
| fin de la méthode 1 | |||||
| fin de la méthode 2 | |||||
| ``` | |||||
| ## Une méthode spéciale | |||||
| Si vous définissez une méthode `__init__`, celle-ci est appelée *automatiquement* | |||||
| quand l'instance est construite. | |||||
| On dit que c'est une méthode "magique" parce qu'elle fait quelque chose _sans_ qu'on | |||||
| l'appelle explicitement. | |||||
| On utilise souvent `__init__` pour créer des attributs | |||||
| ```python | |||||
| class MaClasse: | |||||
| def __init__(self): | |||||
| self.x = 1 | |||||
| self.y = 2 | |||||
| >>> mon_instance = MaClasse() | |||||
| # __init__ est appelée automatiquement! | |||||
| >>> mon_instance.x | |||||
| 1 | |||||
| >>> mon_instance.y | |||||
| 2 | |||||
| ``` | |||||
| On prend souvent les *valeurs* des attributs à créer en arguments de la méthode `__init__ `. | |||||
| ```python | |||||
| class MaClasse: | |||||
| def __init__(self, x, y): | |||||
| self.x = x | |||||
| self.y = y | |||||
| ``` | |||||
| Dans ce cas, les arguments de la méthode `__init__` apparaissent à l'intérieur des parenthèses après le | |||||
| nom de la classe: | |||||
| ``` | |||||
| >>> mon_instance = MaClasse(3, 4) | |||||
| >>> mon_instance.x | |||||
| 3 | |||||
| >>> mon_instance.y | |||||
| 4 | |||||
| ``` | |||||
| *Pour cette raison, `__init__` est souvent appelé le _constructeur_ de la classe.* | |||||
| ## Récapitulatif | |||||
| * Classe: plan de construction | |||||
| * Instance: valeur issue d'une classe | |||||
| * Attribut: variable dans une instance | |||||
| * Méthode: fonction dans une instance (qui prend `self` en premier argument) | |||||
| * `__init__`: méthode magique appelée automatiquement pendant l'instanciation | |||||
| ## Classes et programmation orienté objet | |||||
| Ainsi, on peut ranger au même endroit des données et des fonctions opérant sur ces données. | |||||
| Les données sont les attributs, et les fonctions opérant sur ces attributs sont les méthodes. | |||||
| On peut ainsi séparer les *responsabilités* à l'intérieur d'un code en les répartissant | |||||
| entres plusieurs classes. | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 11 - Classes (1ère partie)" | |||||
| weight = 11 | |||||
| +++ | |||||
| @@ -0,0 +1,148 @@ | |||||
| +++ | |||||
| title = "Modules" | |||||
| weight = 1 | |||||
| +++ | |||||
| # Modules | |||||
| ## Un fichier = un module | |||||
| Et oui, vous faites des modules sans le savoir depuis le début :) | |||||
| Un fichier `foo.py` correspond *toujours* module `foo` | |||||
| **Attention: Ce n'est pas tout à fait réciproque. Le module `foo` peut venir d'autre chose | |||||
| qu'un fichier foo.py.** | |||||
| # Importer un module | |||||
| Ou: accéder à du code provenant d'un *autre* fichier source. | |||||
| Imaginons un fichier `bonjour.py` contenant seulement une assignation | |||||
| d'une variable `a` à l'entier 42 : | |||||
| ```python | |||||
| # Dans bonjour.py | |||||
| a = 42 | |||||
| ``` | |||||
| On peut accéder à cette variable en important le module, par | |||||
| exemple depuis l'interpréteur, en utilisant le mot-clé `import` | |||||
| suivi du nom du module: | |||||
| ```python | |||||
| $ python | |||||
| >>> import bonjour | |||||
| >>> bonjour.a | |||||
| 42 | |||||
| ``` | |||||
| Notez que pour que cela fonctionne: | |||||
| * Le nom du module est écrit directement, ce n'est *pas* une | |||||
| chaîne de caractères. | |||||
| * Il faut lancer la commande `python` sans argument | |||||
| * Il faut la lancer depuis le répertoire qui contient `bonjour.py`. | |||||
| On voit que l'assignation de la variable `a` dans `bonjour.py` est devenue | |||||
| un *attribut* du module `bonjour` lorsque `bonjour` a été importé | |||||
| \newpage | |||||
| Si maintenant on rajoute une fonction `dire_bonjour` dans `bonjour.py`: | |||||
| ```python | |||||
| # toujours dans bonjour.py | |||||
| a = 42 | |||||
| def dire_bonjour(): | |||||
| print("Bonjour!") | |||||
| ``` | |||||
| On peut appeler la fonction `dire_bonjour` depuis l'interpréteur en accédant | |||||
| à l'attribut `dire_bonjour` du module `bonjour`: | |||||
| ```python | |||||
| >>> import bonjour | |||||
| >>> bonjour.dire_bonjour() | |||||
| Bonjour! | |||||
| ``` | |||||
| ## Différence avec la commande python | |||||
| Notez bien que lancer l'interpréteur et taper `import bonjour` dedans n'est pas | |||||
| la même chose que lancer `python bonjour.py`. | |||||
| Dans le deuxième cas, tout le code dans `bonjour.py` est exécuté, puis la commande python | |||||
| se termine. | |||||
| Dans le cas de l'interpréteur, on peut utiliser tous les attributs du module et appeler | |||||
| les fonctions autant de fois qu'on veut: | |||||
| ```python | |||||
| >>> import bonjour | |||||
| >>> bonjour.dire_bonjour() | |||||
| Bonjour! | |||||
| >>> bonjour.dire_bonjour() | |||||
| Bonjour! | |||||
| ``` | |||||
| On peut aussi modifier les valeurs des attributs: | |||||
| ```python | |||||
| >>> import bonjour | |||||
| >>> bonjour.a | |||||
| 42 | |||||
| >>> bonjour.a = 36 | |||||
| >>> bonjour.a | |||||
| 36 | |||||
| ``` | |||||
| ## Les imports ne sont faits qu'une seule fois | |||||
| Il est important de noter que le code à l'intérieur d'un | |||||
| module n'est *pas* ré-éxécuté si le module a déjà été | |||||
| importé auparavant. | |||||
| On peut le voir en mettant du code dans `bonjour.py`, | |||||
| en plus des simples définitions de fonctions et assignations | |||||
| de variables | |||||
| ```python | |||||
| # Dans bonjour.py | |||||
| print("Je suis le module bonjour et tu viens de m’importer") | |||||
| ``` | |||||
| ```python | |||||
| >>> import bonjour | |||||
| Je suis le module foo et tu viens de m’importer | |||||
| >>> import bonjour | |||||
| <rien> | |||||
| ``` | |||||
| Il faudra donc redémarrer l'interpréteur à chaque fois que le code dans `bonjour.py` change. | |||||
| ## La bibliothèque standard | |||||
| La bibliothèque standard est une collection de modules directement utilisables fournis à l'installation de Python. | |||||
| Exemple: `sys`, `random`, ... | |||||
| Toute la bibliothèque standard est documentée - et la traduction en Français est en cours: | |||||
| https://docs.python.org/fr/3/library/index.html | |||||
| Mettez ce lien dans vos favoris - il vous sera très utile. | |||||
| ## Quelques exemples de modules de la bibliothèque standard | |||||
| ### Easter eggs | |||||
| (Ou fonctionnalités cachées) | |||||
| * `import antigravity` | |||||
| * `import this` | |||||
| Je vous laisse découvrir ce que fait le premier. Quant au deuxième, il contient | |||||
| une liste de préceptes que la plupart des développeurs Python s'efforcent de | |||||
| respecter. On en reparlera ... | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 12 - Modules - 1ère partie" | |||||
| weight = 12 | |||||
| +++ | |||||
| @@ -0,0 +1,186 @@ | |||||
| +++ | |||||
| title = "Rappels" | |||||
| weight = 1 | |||||
| +++ | |||||
| # Rappels | |||||
| _Note: ceci est surtout un rappel du chapitre 11. N'hésitez pas à vous y | |||||
| reporter si les exemples de code ne vous paraissent pas clairs._ | |||||
| ## Classes vides | |||||
| Définition: | |||||
| ```python | |||||
| class MaClasse: | |||||
| pass | |||||
| ``` | |||||
| Instanciation: | |||||
| ```python | |||||
| >>> instance_1 = MaClasse() | |||||
| ``` | |||||
| ## Attributs | |||||
| Un attribut est une variable _à l'intérieur_ d'autre chose (par exemple une instance de classe). | |||||
| La syntaxe consiste en l'instance à gauche et l'attribut à droite après un point: | |||||
| ```python | |||||
| >>> mon_instance = MaClasse() | |||||
| # création de l'attribut `x` dans mon_instance: | |||||
| >>> mon_instance.x = 42 | |||||
| # accès à l'attribut `x` dans mon_instance: | |||||
| >>> mon_instance.x | |||||
| 42 | |||||
| ``` | |||||
| ## Méthodes | |||||
| Une méthode est une fonction définie à l'intérieur d'une classe: | |||||
| Définition: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def ma_méthode(self): | |||||
| return 42 | |||||
| ``` | |||||
| Les méthodes sont des attributs des instances de classes: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def ma_méthode(self): | |||||
| return 42 | |||||
| >>> ma_méthode() | |||||
| Erreur | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.ma_méthode() | |||||
| 42 | |||||
| ``` | |||||
| ## self | |||||
| `self` *prend la valeur de l'instance courante* quand la méthode est appelée. | |||||
| ```python | |||||
| class MaClasse: | |||||
| def affiche_attribut_x(self): | |||||
| print(self.x) | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.x = 42 | |||||
| >>> mon_instance.affiche_attribut_x() | |||||
| 42 | |||||
| ``` | |||||
| On peut aussi *créer* des attributs dans une méthode: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def crée_attribut_x(self): | |||||
| self.x = 42 | |||||
| def affiche_attribut_x(self): | |||||
| print(self.x) | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.affiche_attribut_x() | |||||
| # Erreur: `mon_instance` n'a pas d'attribut `x` | |||||
| >>> mon_instance.crée_attribut_x() | |||||
| >>> mon_instance.affiche_attribut_x() | |||||
| 42 | |||||
| ``` | |||||
| ## Méthodes avec arguments | |||||
| ```python | |||||
| class MaClasse | |||||
| def crée_attribut_x(self, valeur_de_x): | |||||
| self.x = valeur_de_x | |||||
| def affiche_attribut_x(self); | |||||
| print(self.x) | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.crée_attribut_x(42) | |||||
| >>> mon_instance.affiche_attribut_x() | |||||
| 42 | |||||
| ``` | |||||
| ## Méthodes appelant d'autres méthodes | |||||
| ```python | |||||
| class MaClasse: | |||||
| def méthode_1(self): | |||||
| print("démarrage de la méthode 1") | |||||
| print("la méthode 1 affiche bonjour") | |||||
| print("bonjour") | |||||
| print("fin de la méthode 1") | |||||
| def méthode_2(self): | |||||
| print("la méthode 2 appelle la méthode 1") | |||||
| self.méthode_1() | |||||
| print("fin de la méthode 2") | |||||
| ``` | |||||
| ```python | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.méthode_2() | |||||
| ``` | |||||
| ```text | |||||
| la méthode 2 appelle la méthode 1 | |||||
| démarrage de la méthode 1 | |||||
| la méthode 1 affiche bonjour | |||||
| bonjour | |||||
| fin de la méthode 1 | |||||
| fin de la méthode 2 | |||||
| ``` | |||||
| ## Constructeur sans arguments | |||||
| Un constructeur en Python désigne la méthode nomée `__init__`, | |||||
| quand celle-ci existe. | |||||
| La méthode `__init__` est appelée automatiquement quand la | |||||
| classe est instanciée: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def __init__(self): | |||||
| self.x = 1 | |||||
| self.y = 2 | |||||
| >>> mon_instance = MaClasse() | |||||
| >>> mon_instance.x | |||||
| 1 | |||||
| >>> mon_instance.y | |||||
| 2 | |||||
| ``` | |||||
| ## Constructeur avec arguments | |||||
| La méthode `__init__` peut avoir des arguments, | |||||
| dans ce cas, ceux ci doivent être fournis | |||||
| lors de l'instanciation: | |||||
| ```python | |||||
| class MaClasse: | |||||
| def __init__(self, x, y): | |||||
| self.x = x | |||||
| self.y = y | |||||
| ``` | |||||
| ```python | |||||
| >>> mon_instance = MaClasse(3, 4) | |||||
| >>> mon_instance.x | |||||
| 3 | |||||
| >>> mon_instance.y | |||||
| 4 | |||||
| ``` | |||||
| @@ -0,0 +1,91 @@ | |||||
| +++ | |||||
| title = "Couplage" | |||||
| weight = 2 | |||||
| +++ | |||||
| # Couplage | |||||
| ## Définition | |||||
| Un couplage décrit une relation entre deux classes. | |||||
| ## Exemple | |||||
| Ici on veut représenter des chats et des humains qui adoptent (on non) des chats. | |||||
| Tous les chats ont un nom, et tous les humains ont un prénom. | |||||
| On peut utiliser pour cela deux classes: `Chat` et `Humain`: | |||||
| ```python | |||||
| class Chat: | |||||
| def __init__(self, nom): | |||||
| self.nom = nom | |||||
| >>> chat = Chat("Monsieur Moustaches") | |||||
| >>> chat.nom | |||||
| 'Monsieur Moustaches' | |||||
| ``` | |||||
| ```python | |||||
| class Humain: | |||||
| def __init__(self, prénom): | |||||
| self.prénom = prénom | |||||
| >>> alice = Humain(prénom="Alice") | |||||
| >>> alice.prénom | |||||
| "Alice" | |||||
| ``` | |||||
| Maintenant on veut que les humains puissent adopter des chats. | |||||
| Pour cela, on peut rajouter la méthode `adopte` dans la classe | |||||
| `Humain`. | |||||
| Cette méthode va prendre un argument - une instance de la | |||||
| classe `Chat`: | |||||
| ```python | |||||
| class Humain: | |||||
| def __init__(self, prénom): | |||||
| self.prénom = prénom | |||||
| def adopte(self, chat): | |||||
| print(self.prénom, "adopte un chat") | |||||
| >>> boule_de_poils = Chat("Boule de Poils") | |||||
| >>> alice = Humain("Alice") | |||||
| >>> alice.adopte(boule_de_poils) | |||||
| "Alice adopte un chat" | |||||
| ``` | |||||
| On peut accéder au nom du chat depuis la méthode `adopte`, | |||||
| en utilisant la syntaxe `nom.attribut` vue précédemment: | |||||
| ```python | |||||
| class Humain: | |||||
| def __init__(self, prénom): | |||||
| self.prénom = prénom | |||||
| def adopte(self, chat): | |||||
| print(self.prénom, "adopte", chat.nom) | |||||
| >>> boule_de_poils = Chat("Boule de Poils") | |||||
| >>> alice = Humain("Alice") | |||||
| >>> alice.adopte(boule_de_poils) | |||||
| "Alice adopte Boule de Poils" | |||||
| ``` | |||||
| ## Couplage | |||||
| ```python | |||||
| class Humain: | |||||
| ... | |||||
| def adopte(self, chat): | |||||
| print(self.prénom, "adopte", chat.nom) | |||||
| ``` | |||||
| Notez également que nous avons écrit `chat.nom`. ainsi, la méthode `adopte()` | |||||
| ne peut être appelée que part une instance qui a un attribut `nom` - sinon | |||||
| on aura une erreur. | |||||
| Donc si on modifie la classe `Chat` et qu'on renomme l'attribut `nom` en `surnom` par exemple, | |||||
| la méthode `adopte()` de la classe `Humain` cessera de fonctionner: on dit | |||||
| qu'on a un *couplage* entre les classes `Chat` et `Humain`. | |||||
| @@ -0,0 +1,117 @@ | |||||
| +++ | |||||
| title = "Composition" | |||||
| weight = 3 | |||||
| +++ | |||||
| # Composition | |||||
| ## Définition | |||||
| Une classe à l'intérieur d'une autre classe. | |||||
| ## Dépendances entre fonctions | |||||
| Exemple: on veut dessiner un sapin dans le terminal: | |||||
| ```python | |||||
| def main(): | |||||
| largeur = demander_largeur() | |||||
| dessine_sapin(largeur) | |||||
| main() | |||||
| ``` | |||||
| On voit que la fonction `dessine_sapin()` prend un argument `largeur`, qui est retourné | |||||
| par la fonction `demander_largeur()`. | |||||
| `dessine_sapin()` doit donc être appelée *après* `demander_largeur()`. On dit que `dessine_sapin()` | |||||
| _dépend_ de `demander_largeur()`. | |||||
| ## Dépendances entre classes | |||||
| Un bon moyen d'introduire une dépendance entre deux classes est d'utiliser les constructeurs. | |||||
| Revoyons la classe Chat: | |||||
| ```python | |||||
| class Chat: | |||||
| def __init__(self, nom): | |||||
| self.nom = nome | |||||
| ``` | |||||
| Comme le constructeur de la classe Chat prend un nom en argument, il est impossible de construire | |||||
| des chats sans nom: | |||||
| ```python | |||||
| >>> chat = Chat() | |||||
| TypeError: __init__() missing 1 required positional argument: 'nom' | |||||
| ``` | |||||
| De la même façon, si on veut que tous les enfants aient un chat (pourquoi pas, après tout), on peut | |||||
| avoir une classe Enfant, dont le constructeur prend une instance de chat en plus du prénom: | |||||
| ```python | |||||
| class Enfant: | |||||
| def __init__(self, prénom, chat): | |||||
| self.prénom = prénom | |||||
| self.chat = chat | |||||
| >>> alice = Enfant("Alice") | |||||
| TypeError: __init__() missing 1 required positional argument: 'chat' | |||||
| >>> boule_de_poils = Chat("Boule de Poils") | |||||
| >>> alice = Enfant("Alice", boule_de_poils) | |||||
| # OK! | |||||
| ``` | |||||
| ## Utilisation de la composition | |||||
| Maintenant qu'on vit dans un monde où tous les enfants ont chacun un chat, on peut | |||||
| par exemple consoler tous les enfants en leur demandant de caresser leur chat, chat | |||||
| qui va ronronner et faire plaisir à son propriétaire. | |||||
| Voici comment on peut coder cela: d'abord, on rajoute les méthodes `caresse()` | |||||
| et `ronronne()` dans la classe Chat: | |||||
| ```python | |||||
| class Chat: | |||||
| def __init__(self, nom): | |||||
| self.nom = nom | |||||
| def ronronne(self): | |||||
| print(self.nom, 'fait: "prrrrr"') | |||||
| def caresse(self): | |||||
| self.ronronne() | |||||
| >>> boule_de_poils = Chat("Boule de Poils") | |||||
| >>> boule_de_poils.caresse() | |||||
| Boule de Poils fait "prrrrr" | |||||
| ``` | |||||
| Ensuite, on peut rajouter la méthode `console()` dans la classe Enfant, | |||||
| qui va: | |||||
| * récupérer l'instance de la classe Chat dans `self` - comme n'importe quel attribut | |||||
| * puis appeler la méthode `caresse()` de cette instance | |||||
| ```python | |||||
| class Enfant: | |||||
| def __init__(self, prénom, chat): | |||||
| self.chat = chat | |||||
| def console(self): | |||||
| self.chat.caresse() | |||||
| >>> boule_de_poils = Chat("Boule de Poils") | |||||
| >>> alice = Enfant("Alice", boule_de_poils) | |||||
| # Alice est triste, on la console | |||||
| >>> alice.console() | |||||
| Boule de Poils fait "prrrrr" | |||||
| # Alice est consolée :) | |||||
| ``` | |||||
| On dit parfois qu'on a *délégué* l'implémentation de la méthode `console()` de la classe Enfant | |||||
| à la méthode `caresse()` de la classe Chat. | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 13 - Classes (2ème partie)" | |||||
| weight = 13 | |||||
| +++ | |||||
| @@ -0,0 +1,42 @@ | |||||
| +++ | |||||
| title = "Introduction" | |||||
| weight = 1 | |||||
| +++ | |||||
| # Introduction | |||||
| ## Importer un module | |||||
| Souvenez-vous, dans le chapitre 12 nous avons vu que le code suivant | |||||
| Ce code fonctionne s'il y a un ficher `foo.py` quelque part qui contient la fonction | |||||
| `bar` [^1]: | |||||
| ```python | |||||
| import foo | |||||
| foo.bar() | |||||
| ``` | |||||
| Ce fichier peut être présent soit dans le répertoire courant, soit dans la bibliothèque standard Python. | |||||
| ## La variable PATH | |||||
| Vous connaissez peut-être le rôle de la variable d'environnement `PATH`. Celle-ci contient une liste de chemins, | |||||
| séparés par le caractère `:` et est utilisée par votre shell pour trouver le chemin complet des commandes que vous lancez. | |||||
| Par exemple: | |||||
| ```bash | |||||
| PATH="/bin:/usr/bin:/usr/sbin" | |||||
| $ ifconfig | |||||
| # lance le binaire /usr/sbin/ifconfig | |||||
| $ ls | |||||
| # lance le binaire /bin/ls | |||||
| ``` | |||||
| Le chemin est "résolu" par le shell en parcourant la liste de tout les | |||||
| chemins de la variable `PATH`, et en regardant si le chemin complet | |||||
| existe. La résolution s'arrête dès le premier chemin trouvé. | |||||
| Par exemple, si vous avez `PATH="/home/user/bin:/usr/bin"` et un fichier `ls` dans `/home/user/bin/ls`, c'est ce fichier-là | |||||
| (et non `/bin/ls`) qui sera utilisé quand vous taperez `ls`. | |||||
| @@ -0,0 +1,439 @@ | |||||
| +++ | |||||
| title = "sys.path" | |||||
| weight = 2 | |||||
| +++ | |||||
| # sys.path | |||||
| En Python, il existe une variable `path` prédéfinie dans le module `sys` qui fonctionne de manière similaire | |||||
| à la variable d'environnement `PATH`. | |||||
| Si j'essaye de l'afficher sur ma machine, voici ce que j'obtiens : | |||||
| ```python | |||||
| import sys | |||||
| print(sys.path) | |||||
| ``` | |||||
| ``` | |||||
| [ | |||||
| "", | |||||
| "/usr/lib/python3.8", | |||||
| "/usr/lib/python3.8/lib-dynload", | |||||
| "/home/dmerej/.local/lib/python3.8/", | |||||
| "/usr/lib/python3.8/site-packages", | |||||
| ] | |||||
| ``` | |||||
| Le résultat dépend: | |||||
| * du système d'exploitation | |||||
| * de la façon dont Python a été installé | |||||
| * et de la présence ou non de certains réportoires. | |||||
| En fait, `sys.path` est construit dynamiquement par l'interpréteur Python au démarrage. | |||||
| Notez également que `sys.path` commence par une chaîne vide. En pratique, cela signifie que le répertoire courant a la priorité sur tout le reste. | |||||
| ## Priorité du répertoire courant | |||||
| Prenons un exemple. Si vous ouvrez un explorateur de fichiers dans le deuxième | |||||
| élément de la liste de `sys.path` (`/usr/lib/python3.8/` sur ma machine), vous trouverez | |||||
| un grand nombre de fichiers Python. | |||||
| Notamment, vous devriez trouver un fichier `random.py` dans ce répertoire. | |||||
| En fait, vous trouverez la plupart des modules de la bibliothèque standard dans | |||||
| ce répertoire. | |||||
| Maintenant, imaginons que vous avez un deuxième fichier `random.py` dans votre répertoire courant. Finalement, imaginez | |||||
| que vous lancez un fichier `foo.py` contentant `import random` dans ce même réportoire. | |||||
| Et bien, c'est le fichier `random.py` de votre répertoire qui sera utilisé, et non celui de la bibliothèque standard! | |||||
| ## Permissions des répertoires de sys.path | |||||
| Un autre aspect notable de `sys.path` est qu'il ne contient que deux répertoires dans lesquels l'utilisateur courant peut potentiellement écrire : le chemin courant et le chemin dans `~/.local/lib`. Tous les autres (`/usr/lib/python3.8/`, etc.) sont des chemins "système" et ne peuvent être modifiés que par un compte administrateur (avec `root` ou `sudo`, donc). | |||||
| La situation est semblable sur macOS et Windows [^2]. | |||||
| ## Bibliothèques tierces | |||||
| Prenons un exemple : | |||||
| ```python | |||||
| # dans foo.py | |||||
| import tabulate | |||||
| scores = [ | |||||
| ["John", 345], | |||||
| ["Mary-Jane", 2], | |||||
| ["Bob", 543], | |||||
| ] | |||||
| table = tabulate.tabulate(scores) | |||||
| print(table) | |||||
| ``` | |||||
| ``` | |||||
| $ python3 foo.py | |||||
| --------- --- | |||||
| John 345 | |||||
| Mary-Jane 2 | |||||
| Bob 543 | |||||
| --------- --- | |||||
| ``` | |||||
| Ici, le module `tabulate` n'est ni dans la bibliothèque standard, ni écrit par l'auteur du script `foo.py`. On dit que c'est une bibliothèque tierce. | |||||
| On peut trouver [le code source de tabulate](https://bitbucket.org/astanin/python-tabulate/src/master/) facilement. La question qui se pose alors est: comment faire en sorte que `sys.path` contienne le module `tabulate`? | |||||
| Eh bien, plusieurs solutions s'offrent à vous. | |||||
| # Le gestionnaire de paquets | |||||
| Si vous utilisez une distribution Linux, peut-être pourrez-vous utiliser votre gestionnaire de paquets : | |||||
| ```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._ | |||||
| @@ -0,0 +1,106 @@ | |||||
| +++ | |||||
| title = "Bibliothèques tierces" | |||||
| weight = 3 | |||||
| +++ | |||||
| # Bibliothèques tierces | |||||
| Prenons un exemple : | |||||
| ```python | |||||
| # dans foo.py | |||||
| import tabulate | |||||
| scores = [ | |||||
| ["John", 345], | |||||
| ["Mary-Jane", 2], | |||||
| ["Bob", 543], | |||||
| ] | |||||
| table = tabulate.tabulate(scores) | |||||
| print(table) | |||||
| ``` | |||||
| ``` | |||||
| $ python3 foo.py | |||||
| --------- --- | |||||
| John 345 | |||||
| Mary-Jane 2 | |||||
| Bob 543 | |||||
| --------- --- | |||||
| ``` | |||||
| Ici, le module `tabulate` n'est ni dans la bibliothèque standard, ni écrit par l'auteur du script `foo.py`. On dit que c'est une bibliothèque tierce. | |||||
| On peut trouver [le code source de tabulate](https://bitbucket.org/astanin/python-tabulate/src/master/) facilement. La question qui se pose alors est: comment faire en sorte que `sys.path` contienne le module `tabulate`? | |||||
| Eh bien, plusieurs solutions s'offrent à vous. | |||||
| ## Le gestionnaire de paquets | |||||
| Si vous utilisez une distribution Linux, peut-être pourrez-vous utiliser votre gestionnaire de paquets : | |||||
| ```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. | |||||
| @@ -0,0 +1,147 @@ | |||||
| +++ | |||||
| title = "Dépendances" | |||||
| weight = 4 | |||||
| +++ | |||||
| # Dépendances | |||||
| ## Un autre exemple | |||||
| Prenons une autre bibliothèque : `cli-ui`. | |||||
| Elle permet d'afficher du texte en couleur dans un terminal | |||||
| ```python | |||||
| import cli_ui | |||||
| cli_ui.info("Ceci est en", cli_ui.red, "rouge") | |||||
| ``` | |||||
| Elle permet également d'afficher des tableaux en couleur : | |||||
| ```python | |||||
| headers=["prénom", "score"] | |||||
| data = [ | |||||
| [(bold, "John"), (green, 10.0)], | |||||
| [(bold, "Jane"), (green, 5.0)], | |||||
| ] | |||||
| cli_ui.info_table(data, headers=headers) | |||||
| ``` | |||||
| Pour ce faire, elle repose sur la bibliothèque `tabulate` vue précédemment. On dit que `cli-ui` *dépend* de `tabulate`. | |||||
| ## Déclaration des dépendances | |||||
| La déclaration de la dépendance de `cli-ui` vers `tabulate` s'effectue également dans le fichier `setup.py`: | |||||
| ```python | |||||
| setup( | |||||
| name="cli-ui", | |||||
| version="0.9.1", | |||||
| install_requires=[ | |||||
| "tabulate", | |||||
| ... | |||||
| ], | |||||
| ... | |||||
| ) | |||||
| ``` | |||||
| ## pypi.org | |||||
| On comprend dès lors qu'il doit nécessairement exister un *annuaire* permettant de relier les noms de dépendances à leur code source. | |||||
| Cet annuaire, c'est le site [pypi.org](https://pypi.org/). Vous y trouverez les pages correspondant à [tabulate](https://pypi.org/project/tabulate/) et [cli-ui](https://pypi.org/project/python-cli-ui/). | |||||
| # pip | |||||
| `pip` est un outil qui vient par défaut avec Python3[^4]. Vous pouvez également l'installer grâce au script [get-pip.py](https://bootstrap.pypa.io/get-pip.py), en lançant `python3 get-pip.py --user`. | |||||
| Il est conseillé de *toujours* lancer `pip` avec `python3 -m pip`. De cette façon, vous êtes certains d'utiliser le module `pip` correspondant à votre binaire `python3`, et vous ne dépendez pas de ce qu'il y a dans votre `PATH`. | |||||
| `pip` est capable d'interroger le site `pypi.org` pour retrouver les dépendances, et également de lancer les différents scripts `setup.py`. | |||||
| Comme de nombreux outils, il s'utilise à l'aide de *commandes*. Voici comment installer `cli-ui` à l'aide de la commande 'install' de `pip`: | |||||
| ```bash | |||||
| $ python3 -m pip install cli-ui --user | |||||
| Collecting cli-ui | |||||
| ... | |||||
| Requirement already satisfied: unidecode in /usr/lib/python3.7/site-packages (from cli-ui) (1.0.23) | |||||
| Requirement already satisfied: colorama in /usr/lib/python3.7/site-packages (from cli-ui) (0.4.1) | |||||
| Requirement already satisfied: tabulate in /mnt/data/dmerej/src/python-tabulate (from cli-ui) (0.8.4) | |||||
| Installing collected packages: cli-ui | |||||
| Successfully installed cli-ui-0.9.1 | |||||
| ``` | |||||
| On constate ici quelques limitations de `pip`: | |||||
| * Il faut penser à utiliser `--user` (de la même façon que lorsqu'on lance `setup.py` à la main) | |||||
| * Si le paquet est déjà installé dans le système, pip ne saura pas le mettre à jour - il faudra passer par le gestionnaire de paquet de | |||||
| la distribution | |||||
| En revanche, `pip` contient de nombreuses fonctionnalités intéressantes: | |||||
| * Il est capable de désinstaller des bibliothèques (à condition toutefois qu'elles ne soient pas dans un répertoire système) | |||||
| * Il est aussi capable d'afficher la liste complète des bibliothèques Python accessibles par l'utilisateur courant avec `freeze`. | |||||
| Voici un extrait de la commande `python3 -m pip freeze` au moment de la rédaction de cet article sur ma machine: | |||||
| ``` | |||||
| $ python3 -m pip freeze | |||||
| apipkg==1.5 | |||||
| cli-ui==0.9.1 | |||||
| gaupol==1.5 | |||||
| tabulate==0.8.4 | |||||
| ``` | |||||
| On y retrouve les bibliothèques `cli-ui` et `tabulate`, bien sûr, mais aussi la bibliothèque `gaupol`, qui correspond au [programme d'édition de sous-titres](https://otsaloma.io/gaupol/) que j'ai installé à l'aide du gestionnaire de paquets de ma distribution. Précisons que les modules de la bibliothèque standard et ceux utilisés directement par pip sont omis de la liste. | |||||
| On constate également que chaque bibliothèque possède un *numéro de version*. | |||||
| ## Numéros de version | |||||
| Les numéros de version remplissent plusieurs rôles, mais l'un des principaux est de spécifier des changements incompatibles. | |||||
| Par exemple, pour `cli-ui`, la façon d'appeler la fonction `ask_choice` a changé entre les versions 0.7 et 0.8, comme le montre cet extrait du [changelog](https://tankerhq.github.io/python-cli-ui/changelog.html#v0-8-0): | |||||
| > the list of choices used by ask_choice is now a named keyword argument: | |||||
| ```python | |||||
| # Old (<= 0.7) | |||||
| ask_choice("select a fruit", ["apple", "banana"]) | |||||
| # New (>= 0.8) | |||||
| ask_choice("select a fruit", choices=["apple", "banana"]) | |||||
| ``` | |||||
| Ceci s'appelle un *changement d'API*. | |||||
| ## Réagir aux changements d'API | |||||
| Plusieurs possibilités: | |||||
| * On peut bien sûr adapter le code pour utiliser la nouvelle API, mais cela n'est pas toujours possible ni souhaitable. | |||||
| * Une autre solution est de spécifier des *contraintes* sur le numéro de version dans la déclaration des dépendances. Par exemple : | |||||
| ```python | |||||
| setup( | |||||
| install_requires=[ | |||||
| "cli-ui < 0.8", | |||||
| ... | |||||
| ] | |||||
| ) | |||||
| ``` | |||||
| # Aparté : pourquoi éviter sudo pip | |||||
| Souvenez-vous que les fichiers systèmes sont contrôlés par votre gestionnaire de paquets. | |||||
| Les mainteneurs de votre distribution font en sorte qu'ils fonctionnent bien les uns | |||||
| avec les autres. Par exemple, le paquet `python3-cli-ui` ne sera mis à jour que lorsque tous les paquets qui en dépendent seront prêts à utiliser la nouvelle API. | |||||
| En revanche, si vous lancez `sudo pip` (où `pip` avec un compte root), vous allez écrire dans ces mêmes répertoire et vous risquez de "casser" certains programmes de votre système. | |||||
| Mais il y a un autre problème encore pire. | |||||
| # Conflit de dépendances | |||||
| Supposons deux projets A et B dans votre répertoire personnel. Ils dépendent tous les deux de `cli-ui`, mais l'un des deux utilise `cli-ui 0.7` et l'autre `cli-ui 0.9`. Que faire ? | |||||
| @@ -0,0 +1,137 @@ | |||||
| +++ | |||||
| title = "Environnements virtuels" | |||||
| weight = 5 | |||||
| +++ | |||||
| # Environnements virtuels | |||||
| La solution à la question de la fin du chapitre précédent est d'utiliser un *environnement virtuel* | |||||
| (*virtualenv* en abrégé). | |||||
| C'est un répertoire *isolé* du reste du système. | |||||
| Il se crée par exemple avec la commande `python3 -m venv foo-venv`. où `foo-venv` est un répertoire quelconque. | |||||
| ## Aparté : python3 -m venv sur Debian | |||||
| La commande `python3 -m venv` fonctionne en général partout, dès l'installation de Python3 (*out of the box*, en Anglais), *sauf* sur Debian et ses dérivées [^5]. | |||||
| Si vous utilisez Debian, la commande pourrait ne pas fonctionner. En fonction des messages d'erreur que vous obtenez, il est possible de résoudre le problème en : | |||||
| * installant le paquet `python3-venv`, | |||||
| * ou en utilisant d'abord `pip` pour installer `virtualenv`, avec `python3 -m pip install virtualenv --user` puis en lançant `python3 -m virtualenv foo-venv`. | |||||
| ## Comportement de python dans le virtualenv | |||||
| Ce répertoire contient de nombreux fichiers et dossiers, et notamment un binaire dans `foo-venv/bin/python3`. | |||||
| Voyons comment il se comporte en le comparant au binaire `/usr/bin/python3` habituel : | |||||
| ``` | |||||
| $ /usr/bin/python3 -c 'import sys; print(sys.path)' | |||||
| ['', | |||||
| ... | |||||
| '/usr/lib/python3.7', | |||||
| '/usr/lib/python3.7.zip', | |||||
| '/usr/lib/python3.7/lib-dynload', | |||||
| '/home/dmerej/.local/lib/python3.7/site-packages', | |||||
| '/usr/lib/python3.7/site-packages' | |||||
| ] | |||||
| $ /home/dmerej/foo-venv/bin/python -c 'import sys; print(sys.path)' | |||||
| ['', | |||||
| '/usr/lib/python3.7', | |||||
| '/usr/lib/python3.7.zip', | |||||
| '/usr/lib/python3.7/lib-dynload', | |||||
| '/home/dmerej/foo-venv/lib/python3.7/site-packages, | |||||
| ] | |||||
| ``` | |||||
| À noter: | |||||
| * Le répertoire "global" dans `~/.local/lib` a disparu | |||||
| * Seuls quelques répertoires systèmes sont présents (ils correspondent plus ou moins à l'emplacement des modules de la bibliothèque standard) | |||||
| * Un répertoire *au sein* du virtualenv a été rajouté | |||||
| Ainsi, l'isolation du virtualenv est reflété dans la différence de la valeur de `sys.path`. | |||||
| Il faut aussi préciser que le virtualenv n'est pas complètement isolé du reste du système. En particulier, il dépend encore du binaire Python utilisé pour le créer. | |||||
| Par exemple, si vous utilisez `/usr/local/bin/python3.7 -m venv foo-37`, le virtualenv dans `foo-37` utilisera Python 3.7 et fonctionnera tant que le binaire `/usr/local/bin/python3.7` existe. | |||||
| Cela signifie également qu'il est possible qu'en mettant à jour le paquet `python3` sur votre distribution, vous rendiez inutilisables les virtualenvs créés avec l'ancienne version du paquet. | |||||
| ## Comportement de pip dans le virtualenv | |||||
| D'après ce qui précède, le virtualenv ne devrait contenir aucun module en dehors de la bibliothèque standard et de `pip` lui-même. | |||||
| On peut s'en assurer en lançant `python3 -m pip freeze` depuis le virtualenv et en vérifiant que rien ne s'affiche. | |||||
| ``` | |||||
| $ python3 -m pip freeze | |||||
| # de nombreuses bibliothèques en dehors du virtualenv | |||||
| apipkg==1.5 | |||||
| cli-ui==0.9.1 | |||||
| gaupol==1.5 | |||||
| tabulate==0.8.4 | |||||
| $ /home/dmerej/foo-venv/bin/python3 -m pip freeze | |||||
| # rien :) | |||||
| ``` | |||||
| On peut alors utiliser le module `pip` *du virtualenv* pour installer des bibliothèques dans celui-ci : | |||||
| ``` | |||||
| $ /home/dmerej/foo-venv/bin/python3 -m pip install cli-ui | |||||
| Collecting cli-ui | |||||
| Using cached https://pythonhosted.org/..cli_ui-0.9.1-py3-none-any.whl | |||||
| Collecting colorama (from cli-ui) | |||||
| Using cached https://pythonhosted.org/..colorama-0.4.1-py2.py3-none-any.whl | |||||
| Collecting unidecode (from cli-ui) | |||||
| Using cached https://pythonhosted.org/..Unidecode-1.0.23-py2.py3-none-any.whl | |||||
| Collecting tabulate (from cli-ui) | |||||
| Installing collected packages: colorama, unidecode, tabulate, cli-ui | |||||
| Successfully installed cli-ui-0.9.1 colorama-0.4.1 tabulate-0.8.3 | |||||
| unidecode-1.0.23 | |||||
| ``` | |||||
| Cette fois, aucune bibliothèque n'est marquée comme déjà installée, et on récupère donc `cli-ui` et toutes ses dépendances. | |||||
| On a enfin notre solution pour résoudre notre conflit de dépendances : | |||||
| on peut simplement créer un virtualenv par projet. Ceci nous permettra | |||||
| d'avoir effectivement deux versions différentes de `cli-ui`, isolées les | |||||
| unes des autres. | |||||
| ## Activer un virtualenv | |||||
| Devoir préciser le chemin du virtualenv en entier pour chaque commande peut devenir fastidieux ; heureusement, il est possible *d'activer* un virtualenv, en lançant une des commandes suivantes : | |||||
| * `source foo-venv/bin/activate` - si vous utilisez un shell POSIX | |||||
| * `source foo-venv/bin/activate.fish` - si vous utilisez Fish | |||||
| * `foo-venv\bin\activate.bat` - sous Windows | |||||
| Une fois le virtualenv activé, taper `python`, `python3` ou `pip` utilisera les binaires correspondants dans le virtualenv automatiquement, | |||||
| et ce, tant que la session du shell sera ouverte. | |||||
| Le script d'activation ne fait en réalité pas grand-chose à part modifier la variable `PATH` et rajouter le nom du virtualenv au début de l'invite de commandes : | |||||
| ``` | |||||
| # Avant | |||||
| user@host:~/src $ source foo-env/bin/activate | |||||
| # Après | |||||
| (foo-env) user@host:~/src $ | |||||
| ``` | |||||
| Pour sortir du virtualenv, entrez la commande `deactivate`. | |||||
| ## Conclusion | |||||
| Le système de gestions des dépendances de Python peut paraître compliqué et bizarre, surtout venant d'autres langages. | |||||
| Mon conseil est de toujours suivre ces deux règles : | |||||
| * Un virtualenv par projet et par version de Python | |||||
| * Toujours utiliser `pip` *depuis* un virtualenv | |||||
| Certes, cela peut paraître fastidieux, mais c'est une méthode qui vous évitera probablement de vous arracher les cheveux (croyez-en mon expérience). | |||||
| @@ -0,0 +1,4 @@ | |||||
| +++ | |||||
| title = "Chapitre 14 - Bibliothèques" | |||||
| weight = 14 | |||||
| +++ | |||||
| @@ -0,0 +1,37 @@ | |||||
| import argparse | |||||
| import subprocess | |||||
| import sys | |||||
| def build(): | |||||
| process = subprocess.run(["hugo"]) | |||||
| if process.returncode != 0: | |||||
| sys.exit("build failed") | |||||
| def deploy(*, dry_run): | |||||
| cmd = [ | |||||
| "rsync", | |||||
| "--itemize-changes", | |||||
| "--recursive", | |||||
| "--delete", | |||||
| "public/", | |||||
| "dedi3:/srv/nginx/html/books/python/", | |||||
| ] | |||||
| if dry_run: | |||||
| cmd += ["--dry-run"] | |||||
| process = subprocess.run(cmd) | |||||
| if process.returncode != 0: | |||||
| sys.exit("deployment failed") | |||||
| def main(): | |||||
| parser = argparse.ArgumentParser() | |||||
| parser.add_argument("-n", "--dry-run", action="store_true") | |||||
| args = parser.parse_args() | |||||
| build() | |||||
| deploy(dry_run=args.dry_run) | |||||
| if __name__ == "__main__": | |||||
| main() | |||||