diff --git a/README.md b/README.md index 414c74c..7327cb9 100644 --- a/README.md +++ b/README.md @@ -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/') +* Enfin, un répertoire cours/ contenant les sources du cours en ligne. + # 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/). diff --git a/cours/TODO b/cours/TODO new file mode 100644 index 0000000..7ccfccb --- /dev/null +++ b/cours/TODO @@ -0,0 +1,2 @@ +explication des exercices +insérer exercices pour chaque épisode de la saison 2 diff --git a/cours/archetypes/default.md b/cours/archetypes/default.md new file mode 100644 index 0000000..00e77bd --- /dev/null +++ b/cours/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/cours/config.yaml b/cours/config.yaml new file mode 100644 index 0000000..c46e163 --- /dev/null +++ b/cours/config.yaml @@ -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 diff --git a/cours/content/_index.md b/cours/content/_index.md new file mode 100644 index 0000000..8d50cfe --- /dev/null +++ b/cours/content/_index.md @@ -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. + diff --git a/cours/content/docs/01-introduction/01-introduction.md b/cours/content/docs/01-introduction/01-introduction.md new file mode 100644 index 0000000..603cc05 --- /dev/null +++ b/cours/content/docs/01-introduction/01-introduction.md @@ -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) diff --git a/cours/content/docs/01-introduction/02-le-langage-python.md b/cours/content/docs/01-introduction/02-le-langage-python.md new file mode 100644 index 0000000..c358f3f --- /dev/null +++ b/cours/content/docs/01-introduction/02-le-langage-python.md @@ -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 :/ + + diff --git a/cours/content/docs/01-introduction/_index.md b/cours/content/docs/01-introduction/_index.md new file mode 100644 index 0000000..2cbafb5 --- /dev/null +++ b/cours/content/docs/01-introduction/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 1 - Introduction" +weight = 1 ++++ diff --git a/cours/content/docs/02-premiers-pas/01-bases-ligne-de-commandes.md b/cours/content/docs/02-premiers-pas/01-bases-ligne-de-commandes.md new file mode 100644 index 0000000..fb6dc53 --- /dev/null +++ b/cours/content/docs/02-premiers-pas/01-bases-ligne-de-commandes.md @@ -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* + diff --git a/cours/content/docs/02-premiers-pas/02-interpréteur.md b/cours/content/docs/02-premiers-pas/02-interpréteur.md new file mode 100644 index 0000000..f4ce3b9 --- /dev/null +++ b/cours/content/docs/02-premiers-pas/02-interpréteur.md @@ -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/). diff --git a/cours/content/docs/02-premiers-pas/03-maths-simples.md b/cours/content/docs/02-premiers-pas/03-maths-simples.md new file mode 100644 index 0000000..26c8b1c --- /dev/null +++ b/cours/content/docs/02-premiers-pas/03-maths-simples.md @@ -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* + diff --git a/cours/content/docs/02-premiers-pas/_index.md b/cours/content/docs/02-premiers-pas/_index.md new file mode 100644 index 0000000..b955a90 --- /dev/null +++ b/cours/content/docs/02-premiers-pas/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 2 - Premiers pas" +weight = 2 ++++ diff --git a/cours/content/docs/03-variables-et-types/01-variables.md b/cours/content/docs/03-variables-et-types/01-variables.md new file mode 100644 index 0000000..5e9a366 --- /dev/null +++ b/cours/content/docs/03-variables-et-types/01-variables.md @@ -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 +``` + diff --git a/cours/content/docs/03-variables-et-types/02-chaînes-de-caractères.md b/cours/content/docs/03-variables-et-types/02-chaînes-de-caractères.md new file mode 100644 index 0000000..71d54c4 --- /dev/null +++ b/cours/content/docs/03-variables-et-types/02-chaînes-de-caractères.md @@ -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 !" +``` + + diff --git a/cours/content/docs/03-variables-et-types/03-types.md b/cours/content/docs/03-variables-et-types/03-types.md new file mode 100644 index 0000000..fa599a3 --- /dev/null +++ b/cours/content/docs/03-variables-et-types/03-types.md @@ -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. + diff --git a/cours/content/docs/03-variables-et-types/04-booléens.md b/cours/content/docs/03-variables-et-types/04-booléens.md new file mode 100644 index 0000000..d28af7b --- /dev/null +++ b/cours/content/docs/03-variables-et-types/04-booléens.md @@ -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 +``` + diff --git a/cours/content/docs/03-variables-et-types/_index.md b/cours/content/docs/03-variables-et-types/_index.md new file mode 100644 index 0000000..72b5923 --- /dev/null +++ b/cours/content/docs/03-variables-et-types/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 3 - Variables et types" +weight = 3 ++++ diff --git a/cours/content/docs/04-code-source/01-code-source.md b/cours/content/docs/04-code-source/01-code-source.md new file mode 100644 index 0000000..1b9dd88 --- /dev/null +++ b/cours/content/docs/04-code-source/01-code-source.md @@ -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 "", line 1, in +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 diff --git a/cours/content/docs/04-code-source/_index.md b/cours/content/docs/04-code-source/_index.md new file mode 100644 index 0000000..bdb1f50 --- /dev/null +++ b/cours/content/docs/04-code-source/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 4 - code source" +weight = 4 ++++ diff --git a/cours/content/docs/05-flot-de-controle/01-flot-de-contrôle.md b/cours/content/docs/05-flot-de-controle/01-flot-de-contrôle.md new file mode 100644 index 0000000..3cb84f7 --- /dev/null +++ b/cours/content/docs/05-flot-de-controle/01-flot-de-contrôle.md @@ -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 +``` diff --git a/cours/content/docs/05-flot-de-controle/02-exercice.md b/cours/content/docs/05-flot-de-controle/02-exercice.md new file mode 100644 index 0000000..618ef84 --- /dev/null +++ b/cours/content/docs/05-flot-de-controle/02-exercice.md @@ -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 +``` diff --git a/cours/content/docs/05-flot-de-controle/_index.md b/cours/content/docs/05-flot-de-controle/_index.md new file mode 100644 index 0000000..f411f0c --- /dev/null +++ b/cours/content/docs/05-flot-de-controle/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 5 - Flot de contrôle" +weight = 5 ++++ diff --git a/cours/content/docs/06-fonctions/01-functions.md b/cours/content/docs/06-fonctions/01-functions.md new file mode 100644 index 0000000..3239ee6 --- /dev/null +++ b/cours/content/docs/06-fonctions/01-functions.md @@ -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") +``` diff --git a/cours/content/docs/06-fonctions/02-portée-des-variables.md b/cours/content/docs/06-fonctions/02-portée-des-variables.md new file mode 100644 index 0000000..fe883e2 --- /dev/null +++ b/cours/content/docs/06-fonctions/02-portée-des-variables.md @@ -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 +``` diff --git a/cours/content/docs/06-fonctions/03-plusieurs-arguments.md b/cours/content/docs/06-fonctions/03-plusieurs-arguments.md new file mode 100644 index 0000000..33b0439 --- /dev/null +++ b/cours/content/docs/06-fonctions/03-plusieurs-arguments.md @@ -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 + diff --git a/cours/content/docs/06-fonctions/04 b/cours/content/docs/06-fonctions/04 new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/cours/content/docs/06-fonctions/04 @@ -0,0 +1 @@ + diff --git a/cours/content/docs/06-fonctions/04-par-défaut.md b/cours/content/docs/06-fonctions/04-par-défaut.md new file mode 100644 index 0000000..e07195e --- /dev/null +++ b/cours/content/docs/06-fonctions/04-par-défaut.md @@ -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 +``` + diff --git a/cours/content/docs/06-fonctions/05-fonctions-natives.md b/cours/content/docs/06-fonctions/05-fonctions-natives.md new file mode 100644 index 0000000..1c32c53 --- /dev/null +++ b/cours/content/docs/06-fonctions/05-fonctions-natives.md @@ -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 +``` + + diff --git a/cours/content/docs/06-fonctions/06-return.md b/cours/content/docs/06-fonctions/06-return.md new file mode 100644 index 0000000..1c65cdf --- /dev/null +++ b/cours/content/docs/06-fonctions/06-return.md @@ -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 +``` diff --git a/cours/content/docs/06-fonctions/_index.md b/cours/content/docs/06-fonctions/_index.md new file mode 100644 index 0000000..659c5d0 --- /dev/null +++ b/cours/content/docs/06-fonctions/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 6 - Fonctions" +weight = 6 ++++ diff --git a/cours/content/docs/07-listes/01-listes.md b/cours/content/docs/07-listes/01-listes.md new file mode 100644 index 0000000..cef92d3 --- /dev/null +++ b/cours/content/docs/07-listes/01-listes.md @@ -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 +``` diff --git a/cours/content/docs/07-listes/_index.md b/cours/content/docs/07-listes/_index.md new file mode 100644 index 0000000..e8b0815 --- /dev/null +++ b/cours/content/docs/07-listes/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 7 - Listes" +weight = 7 ++++ diff --git a/cours/content/docs/08-none-et-pass/01-none.md b/cours/content/docs/08-none-et-pass/01-none.md new file mode 100644 index 0000000..b185290 --- /dev/null +++ b/cours/content/docs/08-none-et-pass/01-none.md @@ -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 + ... +``` diff --git a/cours/content/docs/08-none-et-pass/02-pass.md b/cours/content/docs/08-none-et-pass/02-pass.md new file mode 100644 index 0000000..8a396f8 --- /dev/null +++ b/cours/content/docs/08-none-et-pass/02-pass.md @@ -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() + +``` diff --git a/cours/content/docs/08-none-et-pass/_index.md b/cours/content/docs/08-none-et-pass/_index.md new file mode 100644 index 0000000..f91d7c7 --- /dev/null +++ b/cours/content/docs/08-none-et-pass/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 8 - None et pass" +weight = 8 ++++ diff --git a/cours/content/docs/09-dictionnaires/01-dictionnaires.md b/cours/content/docs/09-dictionnaires/01-dictionnaires.md new file mode 100644 index 0000000..91c46ae --- /dev/null +++ b/cours/content/docs/09-dictionnaires/01-dictionnaires.md @@ -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()` ... + diff --git a/cours/content/docs/09-dictionnaires/_index.md b/cours/content/docs/09-dictionnaires/_index.md new file mode 100644 index 0000000..ec5c156 --- /dev/null +++ b/cours/content/docs/09-dictionnaires/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 9 - Dictionnaires" +weight = 9 ++++ diff --git a/cours/content/docs/10-tuples/01-tuples.md b/cours/content/docs/10-tuples/01-tuples.md new file mode 100644 index 0000000..58fabea --- /dev/null +++ b/cours/content/docs/10-tuples/01-tuples.md @@ -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! diff --git a/cours/content/docs/10-tuples/_index.md b/cours/content/docs/10-tuples/_index.md new file mode 100644 index 0000000..aff8911 --- /dev/null +++ b/cours/content/docs/10-tuples/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 10 - tuples" +weight = 10 ++++ diff --git a/cours/content/docs/11-classes-01/01-classes.md b/cours/content/docs/11-classes-01/01-classes.md new file mode 100644 index 0000000..b50c94a --- /dev/null +++ b/cours/content/docs/11-classes-01/01-classes.md @@ -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 `.`: + +```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. diff --git a/cours/content/docs/11-classes-01/_index.md b/cours/content/docs/11-classes-01/_index.md new file mode 100644 index 0000000..a237f17 --- /dev/null +++ b/cours/content/docs/11-classes-01/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 11 - Classes (1ère partie)" +weight = 11 ++++ diff --git a/cours/content/docs/12-modules-01/01-modules.md b/cours/content/docs/12-modules-01/01-modules.md new file mode 100644 index 0000000..5ea7a96 --- /dev/null +++ b/cours/content/docs/12-modules-01/01-modules.md @@ -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 + +``` + +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 ... diff --git a/cours/content/docs/12-modules-01/_index.md b/cours/content/docs/12-modules-01/_index.md new file mode 100644 index 0000000..505e713 --- /dev/null +++ b/cours/content/docs/12-modules-01/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 12 - Modules - 1ère partie" +weight = 12 ++++ diff --git a/cours/content/docs/13-classes-02/01-rappels.md b/cours/content/docs/13-classes-02/01-rappels.md new file mode 100644 index 0000000..d2099e4 --- /dev/null +++ b/cours/content/docs/13-classes-02/01-rappels.md @@ -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 +``` diff --git a/cours/content/docs/13-classes-02/02-couplage.md b/cours/content/docs/13-classes-02/02-couplage.md new file mode 100644 index 0000000..379b89f --- /dev/null +++ b/cours/content/docs/13-classes-02/02-couplage.md @@ -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`. diff --git a/cours/content/docs/13-classes-02/03-composition.md b/cours/content/docs/13-classes-02/03-composition.md new file mode 100644 index 0000000..eed6bf4 --- /dev/null +++ b/cours/content/docs/13-classes-02/03-composition.md @@ -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. diff --git a/cours/content/docs/13-classes-02/_index.md b/cours/content/docs/13-classes-02/_index.md new file mode 100644 index 0000000..c840da7 --- /dev/null +++ b/cours/content/docs/13-classes-02/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 13 - Classes (2ème partie)" +weight = 13 ++++ diff --git a/cours/content/docs/14-bibliothèques-01/01-rappels.md b/cours/content/docs/14-bibliothèques-01/01-rappels.md new file mode 100644 index 0000000..7097aae --- /dev/null +++ b/cours/content/docs/14-bibliothèques-01/01-rappels.md @@ -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`. diff --git a/cours/content/docs/14-bibliothèques-01/02-sys.path.md b/cours/content/docs/14-bibliothèques-01/02-sys.path.md new file mode 100644 index 0000000..e004143 --- /dev/null +++ b/cours/content/docs/14-bibliothèques-01/02-sys.path.md @@ -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._ diff --git a/cours/content/docs/14-bibliothèques-01/03-bibliotheques-tierces.md b/cours/content/docs/14-bibliothèques-01/03-bibliotheques-tierces.md new file mode 100644 index 0000000..006f777 --- /dev/null +++ b/cours/content/docs/14-bibliothèques-01/03-bibliotheques-tierces.md @@ -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. + diff --git a/cours/content/docs/14-bibliothèques-01/04-dépendances.md b/cours/content/docs/14-bibliothèques-01/04-dépendances.md new file mode 100644 index 0000000..782c9a7 --- /dev/null +++ b/cours/content/docs/14-bibliothèques-01/04-dépendances.md @@ -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 ? diff --git a/cours/content/docs/14-bibliothèques-01/05-virtualenv.md b/cours/content/docs/14-bibliothèques-01/05-virtualenv.md new file mode 100644 index 0000000..4a2949d --- /dev/null +++ b/cours/content/docs/14-bibliothèques-01/05-virtualenv.md @@ -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). diff --git a/cours/content/docs/14-bibliothèques-01/_index.md b/cours/content/docs/14-bibliothèques-01/_index.md new file mode 100644 index 0000000..64ee138 --- /dev/null +++ b/cours/content/docs/14-bibliothèques-01/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Chapitre 14 - Bibliothèques" +weight = 14 ++++ diff --git a/cours/deploy.py b/cours/deploy.py new file mode 100755 index 0000000..4cfe3a9 --- /dev/null +++ b/cours/deploy.py @@ -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()