Update first chapters and new questions

Updated first chapter slides.
Added new questions in the new training section.
This commit is contained in:
2025-07-07 21:18:04 +02:00
parent bea28eca14
commit 77aa231f5b
11 changed files with 416 additions and 250 deletions

View File

@ -1,30 +1,39 @@
---
title: Découvrir le langage - types de données avancés
title: Découvrir le langage - collections
author: Steve Kossouho
---
# Collections de données
Ce chapitre va présenter 4 types de données de base, qui permettent de manipuler plusieurs informations avec une seule
variable :
- Listes
- Tuples
- Ensembles
- Dictionnaires
----
## Les listes
## Listes
Type de collection de données : `list`{.python}. Une liste peut contenir une **séquence** d'éléments
de n'importe quel type pris en charge par Python, y compris d'autres listes. Le contenu d'une liste est modifiable (ajouter, retirer des éléments…)
Type de collection de données le plus utile : `list`{.python}. Un objet de type liste peut contenir une **séquence** d'éléments
de n'importe quel type en Python, y compris d'autres listes. Le contenu d'une liste est modifiable (ajouter, retirer des éléments…)
```python {.numberLines}
liste1 = [1, 2, 3, 4, 5, 6] # types cohérents
liste2 = [1, 2, 3, "a", "b", "c"] # types divers
liste3 = [None, None, True, False]
liste1 = [1, 2, 3, 4, 5, 6] # éléments de types cohérents
liste2 = [1, 2, 3, "a", "b", "c"] # pas de restriction sur les types
liste3 = [None, None, True, False] # None n'est pas une valeur spéciale
liste4 = [[1, 2, 3], [4, 5, 6]]
liste5 = [] # liste vide, équivalent à list()
liste5 = [] # liste vide. Équivalent à list()
liste6 = [liste1] # Liste contenant 1 élément, qui est lui-même une liste
```
----
Il est possible de manipuler une liste facilement. On peut ajouter des éléments à la fin, à une
position précise, retirer un élément ou encore récupérer un élément seul.
Il est possible d'exploiter une liste facilement; on peut ajouter de nouveaux éléments à la fin, les insérer à une
position précise, retirer un élément ou encore récupérer un élément seul. Notez que les positions ([index]{.naming})
des éléments démarrent à zéro (`0`) et qu'une liste n'a jamais d'emplacement vide.
```python {.numberLines}
liste1 = [1, 2, 3, 4, 5, 6]
@ -34,8 +43,10 @@ liste2 = list() # Créer une nouvelle liste vide
length = len(liste1) # Récupérer le nombre d'éléments
position = liste1.index(3) # Renvoie l'index de la valeur 3, ou erreur si introuvable
nine_trouvable = 9 in liste1 # Renvoie si l'élément 9 existe dans la liste
# Récupérer des éléments
print(liste1[0]) # Affiche le premier élément si la liste est non vide
# Manipuler le contenu
liste1.append(7) # Ajoute 7 comme nouvel élément à la fin
liste1.insert(0, 99) # Insère 99 comme nouvel élément au tout début
@ -64,31 +75,33 @@ if len(liste1) >= 4:
### Erreurs de méthodes
La méthode `.remove(valeur)`{.python} provoque également une erreur (`ValueError`{.python}) si
l'élément en argument n'existe pas dans la liste. De la même façon, il peut être utile de tester
Les méthodes `list.remove()`{.python} et `list.index()`{.python} provoquent également une erreur (`ValueError`{.python}) si
l'élément en argument est introuvable dans la liste. De la même façon, il peut être utile de tester
qu'un élément est présent dans la liste avant d'essayer de l'en supprimer :
```python {.numberLines}
liste1 = [1, 2, 3]
if 8 in liste1:
liste1 = ["fred", "denis", 14, 23, 32]
if 8 in liste1: # Tester que 8 a été trouvé dans la liste
liste1.remove(8)
```
**Question** : Qu'écririez-vous pour supprimer toutes les occurrences d'une valeur en utilisant `list.remove()`{.python} ?
----
### Indices de liste négatifs
Il est également possible d'accéder à des éléments de liste en utilisant des indices négatifs.
Il est également possible d'accéder à des éléments de liste **non vide** en utilisant des indices négatifs.
Si la liste n'est pas vide, l'élément d'indice `-1` est le dernier élément de la liste (équivalent
à `len(liste) - 1`{.python}), et l'élément à l'indice `-len(liste)`{.python} est le premier (
équivalent à `0`).
Tout nombre inférieur génère une erreur de type `IndexError`{.python}.
Dans ce cas, l'élément à l'index `-1` d'une liste `l` est celui à l'index `len(l) - 1`{.python}, à savoir
le dernier élément. L'élément à l'index `-len(l)`{.python} sera celui à l'index `0`, à savoir le premier
élément. Tout index hors de la plage disponible provoquera une erreur de type `IndexError`{.python}.
```python {.numberLines}
liste1 = [1, 2, 4, 6, 9, 11]
print(liste1[-1])
animals = ["dog", "cat", "rabbit", "hamster", "parrot"]
print(animals[-1]) # affiche "parrot"
print(animals[-2]) # affiche "hamster"
print(animals[-5]) # affiche "dog"
```
----
@ -96,7 +109,7 @@ print(liste1[-1])
## Parcourir une liste
Comme on l'a vu avec la boucle `for`{.python}, utilisée avec `range()`{.python}, on peut utiliser la
boucle `for`{.python} sur une liste :
boucle `for`{.python} sur une liste :
```python {.numberLines}
prime_numbers = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
@ -110,37 +123,28 @@ les valeurs `2`{.python}, puis `3`{.python}, puis `5`{.python}, etc.
----
## Opérations sur listes
### Opérations sur listes
Récupérer des portions ou partitions de listes (slicing) :
Récupérer des portions ou partitions de listes est possible en Python avec la syntaxe suivante (slicing).
Le comportement de cette syntaxe est basé sur le fonctionnement de la fonction `range`{.python}.
```python {.numberLines}
a = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
b = a[0:5] # Index 0 à 5 non inclus. Marche aussi sur les chaînes.
c = a[5:0] # L'index de fin est inférieur au départ, renvoie une liste vide
d = a[:] # Renvoie une copie de toute la liste
e = a[::-1] # Tout parcourir à l'envers
a = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55] # liste de base
b = a[0:5] # copier dans une nouvelle liste les éléments aux index 0 à 4.
c = a[5:0] # créer une nouvelle liste, mais ne rien y copier
d = a[:] # copier de l'extrêmité gauche à droite incluses
e = a[::-1] # copier tous les éléments à l'envers
f = a[::2] # Tout parcourir 2 par 2
g = a[5:2:-1] # L'indice de début est supérieur à l'indice de fin
h = a[2:5:-1] # Le pas est négatif, start < end, renvoie une liste vide
```
Pourquoi créer des portions de listes ? Cela peut-être utile, par exemple, si
Pourquoi créer des portions de listes ? Cela peut-être utile, par exemple, si
vous souhaitez appliquer un calcul sur une petite partition d'un jeu de données.
----
## Autres collections
Outre les listes, il existe 3 types de base pour collectionner des éléments :
- Tuple : `tuple()`{.python}
- Set (jeu d'éléments uniques) : `set()`{.python}
- Dictionnaires (association) : `dict()`{.python}
----
### Tuples
## Tuples
**Tuple** : Un `tuple`{.python} fonctionne quasiment trait pour trait comme une liste, à la différence qu'une
fois que vous avez défini ses éléments, vous ne pouvez plus en changer. On parle de « collection immuable ».
@ -157,7 +161,7 @@ ce type : [opérations des tuples](https://docs.python.org/3/library/stdtypes.h
----
### Ensembles (données uniques)
## Ensembles (données uniques)
**Set** (ensemble) : Un `set`{.python} peut contenir plusieurs éléments et est modifiable au même titre
qu'une liste, mais n'est pas une séquence; aucune notion d'ordre ou d'index.
@ -168,11 +172,14 @@ dans votre objet.
a = {1, 1, 2, 3, 3, 3} # équivaut à {1, 2, 3}, les valeurs étant uniques à la fin
b = {2, 4, 5, "hello", (1, 2, 3)}
c = set() # obligatoire pour déclarer un set vide, sinon considéré dict
print(len(b))
a.add(9.5) # ajoute la valeur 9.5 (float) au set
a.discard(9.5) # retire la valeur 9.5, ou ne fait rien
d = a.intersection(b) # renvoie un set avec les éléments communs à a et b
e = a.union(b) # avec les éléments présents dans a ou b
d = a.intersection(b) # récupérer un ensemble avec les éléments communs à a et b
e = a.union(b) # récupérer un ensemble avec les éléments présents dans a ou b
...
```
@ -184,18 +191,18 @@ e = a.union(b) # avec les éléments présents dans a ou b
----
#### Bonus: Particularités des ensembles
### Bonus: Particularités des ensembles
Les ensembles sont des structures de données très efficaces. Techniquement, vous pouvez attendre une
complexité en `O(1)` pour que Python sache si un élément est déjà présent ou pas; cela signifie qu'il
faut plus ou moins le même nombre d'opérations pour trouver un élément, que votre `set`{.python} en possède un
faut plus ou moins le même nombre d'opérations pour trouver un élément, que votre `set`{.python} en contienne un
seul ou un milliard.
L'algorithme et la structure de données interne à Python derrière cette efficacité se nomme **Table de hachage**.
L'algorithme et la structure de données interne à Python derrière cette efficacité se nomme [Table de hachage]{.naming}.
----
##### Tables de hachage
#### Tables de hachage
Une table de hachage fonctionne en trois temps:
@ -206,13 +213,13 @@ Une table de hachage fonctionne en trois temps:
----
##### Exemple de hachage
#### Exemple de hachage
![Table de hachage](assets/images/basics-sets-hashing.png)
![Table de hachage](assets/images/basics-sets-hash-table.png)
----
##### Hachage
#### Hachage
La signature est un nombre calculé qui est sensé dépendre du contenu d'un objet. Python propose une fonction
à cet effet, `hash()`{.python}, pour laquelle chaque classe propose sa propre implémentation.
@ -223,7 +230,7 @@ il n'y a pas de collision.
----
##### Hachage en général dans Python
#### Hachage en général dans Python
Entre deux lancements d'un interpréteur Python, le hash de la majorité des objets est différent;
l'algorithme utilise une valeur aléatoire pour générer les hashs, de façon qu'il soit impossible de créer un
@ -238,7 +245,7 @@ Le hachage est en général imprévisible pour les valeurs suivantes:
----
##### Hashes prévisibles
#### Hashes prévisibles
Le hash est par contre prévisible pour les valeurs suivantes:
@ -255,7 +262,7 @@ Les objets `int`{.python} ont généralement un `hash()`{.python} identique, sau
----
##### Hashes impossibles
#### Hashes impossibles
L'algorithme de la table de hachage nécessite qu'un objet stocké dans un bucket possède en
permanence un `hash` correspondant à son bucket. Cela pose un problème avec les objets modifiables
@ -263,50 +270,57 @@ permanence un `hash` correspondant à son bucket. Cela pose un problème avec le
La solution adoptée par Python consiste à interdire le calcul de signature desdits objets.
Les `tuple`{.python} demeurent des données valides, puisque nous avons la garantie de ne jamais pouvoir
changer leur contenu, et ainsi leur signature.
changer leur contenu, et par là-même leur signature.
```python {.numberLines}
# La fonction hash est disponible par défaut en Python
print(hash([1, 2, 3])) # Provoque une exception
print(hash({1, 2, 3})) # Même problème
```
----
### Dictionnaires : associations entre clés et valeurs
## Dictionnaires : associations entre clés et valeurs
**Dictionnaires** : (`dict`{.python}) C'est un **ensemble** d'associations où l'on définit clés et valeurs. C'est le même
fonctionnement qu'un dictionnaire lexicographique, où, lorsque vous avez le
mot (la clé), vous retrouvez la définition s'il y en a une (la valeur). D'autres langages ont des structures
similaires et appellent ça des `HashMap`{.java} ou des `object`{.javascript}.
**Dictionnaires** : (`dict`{.python}) C'est un **ensemble** d'associations où l'on définit [clés]{.naming} et [valeurs]{.naming}.
C'est le même fonctionnement qu'un dictionnaire lexicographique, où, lorsque vous avez le
mot (la clé), vous retrouvez la définition s'il y en a une (la valeur).
D'autres langages ont des structures similaires et appellent ça des `HashMap`{.java} ou des `object`{.javascript}.
```python {.numberLines}
a = {"server1": "192.168.1.2", "server2": "192.168.1.3", "server3": "192.168.1.5"} # serveurs et adresses IP
b = {8: "Mme Garnier", 10: "M. Dubois", 11: "Mlle Yousfi", 12: "Mme Préjean"} # rendez-vous horaires
d = {1.2: "flottant", True: "booléen", None: "rien", (1, 2, 3): "tuple"} # clés de plusieurs types
c = {} # ou dict(), ceci est un dictionnaire vide
print(a["server1"]) # affiche "192.168.1.2"
print(b[10]) # affiche "M. Dubois"
print(b[9]) # provoque une erreur
```
Les dictionnaires ont les mêmes contraintes que les ensembles uniquement au niveau de leurs clés; vous
pouvez uniquement utiliser des valeurs de types immuables, tels que `int`{.python}, `float`{.python}, `tuple`{.python}...
----
#### Parcourir un dictionnaire
Il est possible de parcourir un dictionnaire avec une boucle `for`{.python}. De base, ce sont les
clés du dictionnaire qui sont parcourues. Mais il existe des variantes assez pratiques pour
parcourir un dictionnaire :
Il est possible de parcourir un dictionnaire avec une boucle `for`{.python}. Dans ce cas, ce sont
uniquement les clés du dictionnaire qui sont parcourues. Mais il existe des variantes assez pratiques pour
parcourir un dictionnaire :
```python {.numberLines}
a = {
"Jérémy": (25, "M", "Lille"),
"Hélène": (30, "F", "Ambérieu-en-Bugey"),
"Gwladys": (35, "F", "Nyons"),
profils = {
"Jérémy": (25, "M", "Lille"),
"Hélène": (30, "F", "Ambérieu-en-Bugey"),
"Gwladys": (35, "F", "Nyons"),
"Thierry": (40, "M", "Montceau-les-Mines"),
"Carole": (45, "F", "Saint-Rémy-lès-Chevreuse"),
}
for prenom in a:
for prenom in profils:
print(prenom) # affiche uniquement une clé
print(a[prenom]) # retrouve la valeur associée à la clé
print(profils[prenom]) # retrouve la valeur associée à la clé
```
----
@ -316,11 +330,12 @@ de `n` éléments, vous pouvez les dépaqueter (**unpacking**) :
```python {.numberLines}
# Avancé, unpacking via la méthode `items`
profils = {...}
for item in a.items(): # a.items() renvoie ((clé1, valeur1), (clé2, valeur2), …)
for item in profils.items(): # a.items() renvoie ((clé1, valeur1), (clé2, valeur2), …)
print(item) # est un tuple
for key, value in a.items():
for key, value in profils.items():
# on peut utiliser 2 variables de boucle si chaque élément parcouru est
# une séquence de taille 2. Merci l'unpacking !
print(key, value)
@ -334,18 +349,22 @@ Pour changer la valeur associée à une clé existante, ou par la même occasion
une nouvelle clé, il suffit d'écrire :
```python {.numberLines}
a = {"Bordeaux": 250000}
a["Marseille"] = 800000
a["Bordeaux"] = 90
print(a["Bordeaux"]) # Affiche 90
populations = {"Bordeaux": 250000, "Cherbourg": 80000}
print(populations)
# Modifier le contenu
populations["Marseille"] = 800000
populations["Bordeaux"] = 90
print(populations["Bordeaux"]) # Affiche 90
# On peut supprimer du dictionnaire une association en écrivant
del a["Marseille"] # plus de clé pour Marseille !
print("Marseille" in a) # renvoie si la clé "Marseille" existe
del populations["Marseille"] # plus de clé pour Marseille !
print("Marseille" in populations) # renvoie si la clé "Marseille" existe
```
----
### Plus: méthodes de dictionnaires
### Extra: méthodes de dictionnaires
La méthode `get(key, default)`{.python} des dictionnaires renvoie la valeur associée à une clé. Si
l'on ne passe pas de valeur pour l'argument `default`{.python}, pour une clé introuvable, la méthode
@ -415,9 +434,9 @@ Nous n'avons pas vu grand chose sur les chaînes de caractères, mais certaines
- [Documentation officielle sur le format des interpolations](https://docs.python.org/3/library/string.html#format-string-syntax)
```python {.numberLines}
a = 19
b = f"Le serveur qui doit être vérifié aujourd'hui est le numéro {a}"
c = f"Formatage de la variable : {a:f}" # affiché comme flottant (6 chiffres après la virgule)
number = 2_643
print(f"Le serveur qui doit être vérifié aujourd'hui est le numéro {number}")
print(f"Formatage de la variable : {number:f}") # affiché comme flottant (6 chiffres après la virgule)
```
----
@ -430,7 +449,7 @@ print(chaine.upper(), chaine.lower())
print(chaine.center(50, " ")) # Centre la chaîne originale sur 50 caractères
```
Méthodes qui renvoient une copie modifiée d'une chaîne
Méthodes qui renvoient une **copie** modifiée d'une chaîne
----
@ -457,37 +476,4 @@ print("17 juillet" in chaine)
Exemple de `str.split()`{.python} et `str.join()`{.python}
----
## Bonus : Convertir des données d'un type à un autre
Nous avons vu, ici et là, quelques fonctions pour déclarer des valeurs de base, ou convertir des
valeurs. En voici une liste plus complète :
- `bool(val)`{.python}
- `int(val)`{.python}, `float(val)`{.python}
- `str(val)`{.python}
- `list(val)`{.python}, `tuple(val)`{.python}
- `set(val)`{.python}, `dict(val)`{.python}
Toutes ces fonctions renvoient un nouveau booléen, entier, flottant etc. correspondant à une
conversion de l'expression passée en argument. Cela fonctionne uniquement lorsque la conversion a du sens.
----
Appelées sans argument, ces fonctions vous renvoient `False`{.python}, `0`{.python}, une chaîne ou une
collection vide (des valeurs considérées neutres, qui renvoient toujours `False`{.python} lorsqu'on les convertit en booléen).
Lorsque vous passez un argument à ces fonctions, elles vous renvoient une nouvelle valeur,
qui est une conversion de l'argument passé vers le type
représenté par la fonction. Ce n'est pas toujours possible d'effectuer une conversion; par exemple, il est impossible de
convertir une liste vers un nombre flottant, ou encore de convertir la chaîne `"bonjour"`{.python} vers un nombre entier.
```python {.numberLines}
converted1 = int(3.14159) # tronque le flottant / retire la partie décimale
converted3 = float("3.14159") # comprend le texte et génère un flottant
converted2 = list(1) # erreur
converted4 = float("salut") # aucun caractère valide pour représenter un nombre, échoue
```

View File

@ -630,3 +630,36 @@ Avec ce type d'expression, on pourra plutôt écrire :
```python {.numberLines}
variable = a if condition else b
```
----
### Extra : Convertir des données d'un type à un autre
Nous avons vu, ici et là, quelques fonctions pour déclarer des valeurs de base, ou convertir des
valeurs. En voici une liste plus complète :
- `bool(val)`{.python}
- `int(val)`{.python}, `float(val)`{.python}
- `str(val)`{.python}
- `list(val)`{.python}, `tuple(val)`{.python}
- `set(val)`{.python}, `dict(val)`{.python}
Toutes ces fonctions renvoient un nouveau booléen, entier, flottant etc. correspondant à une
conversion de l'expression passée en argument. Cela fonctionne uniquement lorsque la conversion a du sens.
----
Appelées sans argument, ces fonctions vous renvoient `False`{.python}, `0`{.python}, une chaîne ou une
collection vide (des valeurs considérées neutres, qui renvoient toujours `False`{.python} lorsqu'on les convertit en booléen).
Lorsque vous passez un argument à ces fonctions, elles vous renvoient une nouvelle valeur,
qui est une conversion de l'argument passé vers le type
représenté par la fonction. Ce n'est pas toujours possible d'effectuer une conversion; par exemple, il est impossible de
convertir une liste vers un nombre flottant, ou encore de convertir la chaîne `"bonjour"`{.python} vers un nombre entier.
```python {.numberLines}
converted1 = int(3.14159) # tronque le flottant / retire la partie décimale
converted3 = float("3.14159") # comprend le texte et génère un flottant
converted2 = list(1) # erreur
converted4 = float("salut") # aucun caractère valide pour représenter un nombre, échoue
```

View File

@ -9,26 +9,36 @@ author: Steve Kossouho
## Exceptions
En Python, une exception est une erreur qui se produit pendant l'exécution de votre code. En tant que développeur, vous pouvez choisir ce que vous souhaitez faire quand une exception se produit, plutôt que de laisser le programme s'arrêter avec un message d'erreur. Les exceptions s'opposent entre autres aux erreurs de syntaxe, où l'interpréteur n'est pas même en mesure d'exécuter votre code.
En Python, une [exception]{.naming} est une erreur qui se produit pendant l'exécution de votre code.
En tant que développeur, vous pouvez **intercepter** une exception, et dire à Python ce que vous souhaitez faire
afin d'empêcher le programme de se terminer prématurément si ce n'est pas nécessaire.
----
### Jargon des exceptions
- `Lever une exception` : Ce que Python effectue lorsqu'on rencontre une erreur à l'exécution d'un script. Les exceptions générées par Python sont d'un type qui dépend de l'erreur rencontrée. Elles font toutes partie d'une hiérarchie dont le type le plus générique est nommé `Exception`. (voir programmation orientée objet et hiérarchie des classes d'exception)
- `Traceback` : <span style="color:red">Texte qui apparaît</span> dans la sortie d'erreur (en rouge) lorsque votre programme plante à l'exécution. C'est un récapitulatif des exécutions d'instructions qui ont mené directement à l'interruption de l'exécution, accompagné d'informations sur l'exception.
- [Lever une exception]{.naming} : Ce que Python effectue lorsqu'on rencontre une erreur à l'exécution d'un script.
- [Traceback]{.naming} : <span style="color:#F04">Texte qui apparaît</span> dans la sortie d'erreur (en rouge) lorsque votre programme plante à l'exécution. C'est un récapitulatif des exécutions d'instructions qui ont mené directement à l'interruption de l'exécution, accompagné d'informations sur l'exception.
Les exceptions levées par Python sont d'un type qui dépend de l'erreur rencontrée (ex. `TypeError`{.python}).
Tous ces types font partie d'une hiérarchie dont le type le plus générique est nommé `Exception`.
Théoriquement, un développeur peut créer son propre type d'exception lorsqu'il développe des bibliothèques.
----
### Exemple d'exception
### Exemple de gestion d'exception
Dans l'exemple ci-dessous, on effectue une division par zéro, mais on pourrait accéder à un mauvais indice de liste, une mauvaise clé de dictionnaire, un fichier verrouillé…
Dans l'exemple ci-dessous, on effectue une division par zéro, ce qui est toujours une erreur, mais on pourrait accéder
à un mauvais index de liste, à une clé de dictionnaire inexistante, ou encore à un fichier verrouillé…
```{.python .numberLines}
```python {.numberLines}
try:
15 / 0 # erreur évidente pour l'exemple
except ZeroDivisionError:
pass
resultat = 15 / 0 # erreur évidente pour l'exemple
print("Cette ligne ne sera jamais exécutée.")
except ZeroDivisionError: # Ici, on intercepte spécifiquement le type d'erreur produit par la division
print("Le résultat n'a pas pu être calculé.")
print("Fin du programme.")
```
Gestion des erreurs de division par zéro
@ -39,30 +49,32 @@ Gestion des erreurs de division par zéro
----
### À ne pas faire
### Mauvaise pratique sur les exceptions
Il est déconseillé d'utiliser directement la classe `Exception` (ou rien, d'ailleurs) avec la clause `except`;
ceci a pour effet de pousser votre code à réagir exactement de la même manière pour tous les cas de figure.
Si vous le tentez malgré tout, affichez ou conservez le traceback, vous me remercierez plus tard :
Il est **ardemment** déconseillé d'utiliser directement la classe `Exception`{.python} (ou ne rien préciser, d'ailleurs) avec la clause `except`{.python};
une clause trop générique intercepte **tous** les problèmes rencontrés dans le bloc `try`{.python}.
Dans certains cas, cela vous fera manquer des erreurs que vous ne souhaitez pas traiter avec le même bloc `except`{.python}.
```{.python .numberLines}
Une règle d'or de Python indique que dans le cas où vous masquez tous les types d'exception sans distinction, vous
ne devez jamais le faire silencieusement; affichez le problème ou consignez-le quelque part :
```python {.numberLines}
import traceback
try:
print(15 / 0)
except Exception: # ou except:
traceback.print_exc() # Affiche le traceback dans la console
traceback.print_exc() # Affiche le traceback dans la console sans planter
```
Voir [antipattern sur les exceptions pour plus d'info](https://realpython.com/the-most-diabolical-python-antipattern/)
----
### Un bloc `except`, plusieurs exceptions
On peut même avoir des blocs `except`{.python} prenant en charge plusieurs types d'exceptions, pour cela il suffit de les indiquer dans un tuple :
On peut même avoir des blocs `except`{.python} prenant en charge plusieurs types d'exceptions, pour cela il suffit d'indiquer
un tuple de classes d'exceptions à la clause `except`{.python} :
```{.python .numberLines}
```python {.numberLines}
try:
pass # ou faire autre chose
except (TypeError, ValueError):
@ -76,7 +88,7 @@ except (TypeError, ValueError):
On peut, à l'instar de la structure `if/elif/else`, faire suivre le bloc `try`{.python} de
plusieurs blocs `except`{.python} :
```{.python .numberLines}
```python {.numberLines}
try:
print("Code à essayer")
except ValueError:
@ -85,15 +97,20 @@ except TypeError:
print("Code pour TypeError")
```
Python évaluera la structure de haut en bas. Si une erreur se produit dans le bloc `try`{.python},
Python évaluera d'abord le premier bloc `except`{.python}, de la même manière que pour toutes les structures de code.
----
Si aucun des blocs `except`{.python} ne gère précisément l'erreur qui se produit dans le bloc `try`{.python},
alors l'interpréteur Python plante avec un traceback dans la console, comme attendu.
```{.python .numberLines}
```python {.numberLines}
try:
1 / 0
except NameError:
except TypeError: # pas la bonne exception
pass
except NameError: # pas la bonne exception non plus
pass
```
@ -104,9 +121,9 @@ except NameError:
La structure `try`{.python}...`except`{.python} peut être accompagnée d'un bloc `else`{.python}. Ce bloc
sera exécuté si aucun des blocs `except`{.python} précédents n'a été exécuté (comme dans une structure `if`{.python}) :
```{.python .numberLines}
```python {.numberLines}
try:
value = input("Saisissez un nombre entier :")
value = input("Saisissez un nombre entier : ")
value = int(value)
except ValueError:
value = None
@ -121,7 +138,7 @@ else:
Pour finir, il existe un bloc `finally`{.python}, dont le bloc est *toujours* exécuté, même si le programme doit planter :
```{.python .numberLines}
```python {.numberLines}
try:
15 / 0
except KeyError:
@ -134,10 +151,12 @@ En général, on imagine l'utiliser lorsqu'on souhaite penser à toujours libér
----
On peut souhaiter également lever soi-même une exception pour notifier un problème à gérer.
C'est généralement le cas lorsque l'on fournit une API utilisable par d'autres développeurs.
### Lever une exception
```{.python .numberLines}
On peut souhaiter également lever soi-même une exception pour notifier un problème à gérer.
C'est généralement le cas lorsque l'on développe une bibliothèque utilisable par d'autres développeurs.
```python {.numberLines}
def do_something(value):
if not isinstance(value, int): # si value n'est pas un entier
raise TypeError("Value must be an integer.")

View File

@ -9,7 +9,7 @@ author: Steve Kossouho
## L'utilité des fonctions
Une fonction, c'est :
Le principe d'une fonction, c'est d'être :
1. Minimaliste si possible,
2. Réutilisable,
@ -19,19 +19,23 @@ Une fonction, c'est :
## Syntaxe de déclaration de fonctions
```{.python .numberLines}
def my_first_function():
# Affiche un texte à chaque fois qu'on l'exécute
print("Voici le code de la fonction")
```python {.numberLines}
def my_simple_function():
# Affiche un texte à chaque fois qu'on l'exécute
print("Bonjour, vous m'avez appelé ?")
print("Je suis une fonction très simple.")
my_first_function()
my_simple_function()
```
La syntaxe permet de déclarer la fonction et dire à Python de lui associer du code.
----
## Typographie des fonctions
Les noms de fonctions se choisissent, par convention, comme toute autre variable :
Les noms de fonctions se choisissent, par **convention** (suivez les conventions), comme toute autre variable :
- Tout en minuscules (ex. `exponential`)
- Mots séparés par des underscores (ex. `get_warning_count`)
@ -41,9 +45,14 @@ Les noms de fonctions se choisissent, par convention, comme toute autre variable
## Référence _versus_ appel de fonction
Lorsque vous définissez une fonction appelée `action` :
Lorsque vous déclarez une fonction appelée `action` :
`action` est une variable de type fonction dont vous pouvez ensuite exécuter le code associé.
```python {.numberLines}
def action():
pass
```
`action` est une **variable** de type fonction dont vous pouvez ensuite exécuter le code associé.
- `action` est donc la référence de la fonction.
- `action()`{.python} exécute le code de `action`, et récupère son expression de retour.
@ -53,25 +62,26 @@ Lorsque vous définissez une fonction appelée `action` :
## Valeurs de retour d'une fonction
En mathématiques, une fonction renvoie toujours une valeur.
Par exemple, on peut déclarer `Pour tout x dans les nombres réels, f(x) = x × 15`.
Par exemple, on peut déclarer ` x  , f(x) = x × 15`{.latex}.
En Python, pour qu'une fonction qu'on déclare renvoie une valeur (transmette une valeur au code qui l'exécute)
lorsqu'on l'exécute, il faut utiliser le mot-clé `return`{.python}.
Lorsque le mot-clé est rencontré par l'interpréteur, il interrompt immédiatement l'exécution de la
fonction et l'instruction qui a appelé la fonction récupère la valeur de l'expression qui a été retournée.
fonction et la valeur est transmise comme résultat de l'appel de la fonction.
```{.python .numberLines}
def multiply_by_five(value): # Fonction qui prend un argument nommé value
return value * 5
```python {.numberLines}
def squared(value):
# Renvoyer la valeur élevée au carré
return value ** 2.0
result = multiply_by_five(10) # l'expression `10 * 5` est assignée à la variable
print(result) # affiche 50
result = squared(10) # on récupère 100
print(result)
```
----
### Valeurs de retour spécifiques
### Valeurs de retour et cas particuliers
Le mot-clé `return`{.python} a quelques comportements implicites :
@ -89,8 +99,8 @@ En clair, cela signifie qu'une fonction en Python renvoie **toujours** une valeu
## Passer quelques arguments aux fonctions
Déclarer une fonction simple, c'est déjà pas mal, mais en déclarer une qui dépend de un ou
plusieurs arguments reçus en entrée, c'est bien plus utile !
Déclarer une fonction simple, c'est déjà pas mal, mais en déclarer une qui dépend d'un ou de
plusieurs arguments reçus en entrée, c'est encore plus utile !
Python propose au moins 4 types d'arguments différents, dont les usages sont évidemment différents,
et parmi ceux-ci, deux sont absolument essentiels et nous allons les aborder :
@ -104,7 +114,7 @@ et parmi ceux-ci, deux sont absolument essentiels et nous allons les aborder :
Derrière cet adjectif un peu pompeux se cache le type d'argument le plus simple à déclarer et à utiliser :
```{.python .numberLines}
```python {.numberLines}
def f(x, y):
# Accepte deux arguments, affiche leur valeur
# Mais ne renvoie rien d'autre que `None`
@ -124,7 +134,7 @@ C'est de ce comportement que vient la notion de **positionnalité**.
## Arguments : Valeurs par défaut
```{.python .numberLines}
```python {.numberLines}
def f(x, alpha=15, beta=16):
# Accepte trois arguments, dont deux avec une valeur par défaut
print(x, alpha, beta)
@ -157,7 +167,7 @@ les arguments positionnels **doivent** être déclarés **en premier** dans la l
Tous les arguments, positionnels (obligatoires) et par défaut, peuvent cependant être passés par leur nom,
mais ne faites jamais ça, vous induirez vos relecteurs en erreur :
```{.python .numberLines}
```python {.numberLines}
def f(x, alpha=15, beta=16):
return (x, alpha, beta) # renvoyer un tuple avec x, alpha et beta
@ -167,7 +177,7 @@ print(f(beta=23, x=50))
----
## Bonus : Argument "étoile" (séquence)
## Extra : Argument "étoile" (séquence)
Il existe un type d'argument, qui n'apparaît qu'une seule fois **maximum**, et dont le nom est précédé d'une étoile dans la signature de la fonction.
@ -175,7 +185,7 @@ Cet argument apparaît après les arguments positionnels, et _de forte préfére
Lors de l'exécution de la fonction, cet argument contient toujours un tuple valide, même vide.
Également, comme cet argument contient toujours un tuple, il est généralement nommé avec un nom **au pluriel**.
```{.python .numberLines}
```python {.numberLines}
def stretchable(number, *words):
print(number, words) # words est toujours un tuple
@ -188,49 +198,84 @@ stretchable(1) # le tuple `words` sera vide
### Argument "étoile" : usage
Un argument de ce type accepte, lors de l'appel de la fonction, un nombre de valeurs arbitraire (0 ou plus), et ces valeurs sont passées comme des arguments positionnels (sans nom d'argument).
Un argument de ce type accepte, lors de l'appel de la fonction,
un nombre de valeurs arbitraire (aucune ou plus), et ces valeurs sont passées comme des arguments positionnels
(sans nom d'argument).
Cette technique est utilisée dans la fonction `print`{.python} pour pouvoir afficher à la suite plusieurs arguments.
```python {.numberLines}
from sys import stdout
def custom_print(*values):
for value in values:
stdout.write(value)
stdout.write(" ")
stdout.write("\n") # Échappement pour passer à la ligne
custom_print("Hello", "et", "bienvenue !")
```
----
On peut directement renseigner une liste pour ce type d'argument, en passant, lors de l'appel de fonction, une expression de liste (ou tuple) précédée par une étoile :
On peut directement renseigner une liste pour ce type d'argument, en passant, lors de l'appel de fonction,
une expression de _liste_ (ou _tuple_) précédée par une étoile :
```{.python .numberLines}
def star_function(number, *words):
```python {.numberLines}
def function_with_extensible_args(number, *words):
print(number, words) # words est toujours une liste
star_function(15, *["word 1", "word 2", "word 3"]) # raccourci
```
TODO: Si arg étoile suivi de défaut, passer *[], suivi d'une valeur positionnelle, cette dernière s'ajoute quand même à l'argument étoile, et donc obligation passer l'argument par défaut via son nom.
function_with_extensible_args(15, *["word 1", "word 2", "word 3"], "word 4") # raccourci
```
----
## Bonus : Argument "double-étoile" (dictionnaire)
## Extra : Argument "double-étoile" (dictionnaire)
Un dernier type d'argument, apparaît aussi une seule fois **maximum**, et généralement en tout dernier dans les arguments. Celui-ci s'utilise en passant des noms d'arguments qui n'existent pas ailleurs dans la signature de la fonction :
Un dernier type d'argument, apparaît aussi une seule fois **maximum**, et généralement en tout dernier dans les arguments.
Celui-ci s'utilise en passant des noms d'arguments qui n'existent pas ailleurs dans la signature de la fonction :
```{.python .numberLines}
def f_keywords(other=15, **kwargs):
print(kwargs) # toujours un dictionnaire
```python {.numberLines}
from typing import Any
f_keywords(plop="Hello", foo=19) # vont dans le dictionnaire `kwargs`.
def function_with_custom_named_args(other: Any = 15, **kwargs):
print(other, kwargs) # toujours un dictionnaire
function_with_custom_named_args(plop="Hello", foo=19) # vont dans le dictionnaire `kwargs`.
function_with_custom_named_args(other="Hello", tornado=19) # tornado ira dans le dictionnaire `kwargs`.
```
On appelle aussi cet argument l'argument "mots-clés", et s'appelle très fréquemment `kwargs`.
On appelle souvent cet argument l'argument [mots-clés]{.naming}, et s'appelle très fréquemment `kwargs` ou
`options`.
----
Si l'on appelle `f_keywords`{.python} avec un argument `plop`, Python regarde si un argument de ce nom existe. Si oui, la valeur de cet argument est modifiée. Si non, l'argument est ajouté comme association dans `kwargs`, de telle façon que `kwargs == {"plop": "Hello"}`{.python}.
### Fonctionnement de l'argument
Souvent utilisé par coquetterie, mais l'argument pourrait être généralement remplacé par le simple passage d'un dictionnaire dans un argument positionnel.
Si l'on appelle `function_with_custom_named_args`{.python} avec un argument `plop`, Python regarde si la fonction
accepte explicitement un argument de ce nom :
- Si oui, l'argument prendra cette valeur.
- Si non, l'argument est ajouté comme association dans `kwargs`, de telle façon que `kwargs == {"plop": "Hello"}`{.python}.
```python {.numberLines}
def function_with_custom_named_args(other=15, **kwargs):
print(other, kwargs) # toujours un dictionnaire
function_with_custom_named_args(plop="Hello")
```
L'argument est de plus en plus rarement utilisé, sauf par des bibliothèques qui ont besoin d'accepter des noms
calculés dynamiquement pour fonctionner.
----
## Arguments : Ordre de déclaration
Si vous deviez avoir dans vos fonctions tous les types d'arguments que nous avons vus, l'ordre de leur
Si vous deviez avoir dans la déclaration de vos fonctions tous les types d'arguments que nous avons vus, l'ordre de leur
apparition **devrait** être le suivant :
1. `positionnel` (toujours premier)
@ -252,7 +297,7 @@ simplement nommé `/`{.python}. Il ne peut exister qu'une fois au maximum.
Lorsque vous spécifiez cet argument dans votre signature, **tous les arguments qui le précèdent ne
peuvent pas être spécifiés autrement que positionnellement** :
```{.python .numberLines}
```python {.numberLines}
def my_function(a, b, /, c, d):
print(a, b, c, d)
@ -261,7 +306,7 @@ my_function(1, 2, c=3, d=4) # autorisé
my_function(1, b=2, c=3, d=4) # impossible, b est positionnel uniquement
```
Lorsqu'un argument `/`{.python} ou `*`{.python} est présent, les arguments `*args`{.python} sont interdits. La raison est que, si un tel
Lorsqu'un argument `/`{.python} est présent, les arguments `*args`{.python} sont interdits. La raison est que, si un tel
argument est présent, on est automatiquement tenu de passer les arguments qui suivent par leur nom.
----
@ -269,13 +314,13 @@ argument est présent, on est automatiquement tenu de passer les arguments qui s
### Arguments spéciaux : Étoile
Vous pouvez contrôler l'utilisation de vos fonctions en ajoutant dans votre signature un argument
simplement nommé `*`{.python}. Il ne peut exister qu'une fois au maximum, et se trouver après l'argument
simplement marqué `*`{.python}. Il ne peut exister qu'une fois au maximum, et se trouver après l'argument
`/`{.python}.
Lorsque vous spécifiez cet argument dans votre signature, **tous les arguments qui le suivent ne
peuvent être spécifiés autrement qu'en précisant leur nom** :
```{.python .numberLines}
```python {.numberLines}
def my_function(a, b, c=3, *, d=4):
print(a, b, c, d)

View File

@ -7,8 +7,9 @@ author: Steve Kossouho
----
En Python, on peut écrire des choses simples avec un seul script. Mais
souvent, et même pour des choses simples, on peut se retrouver à écrire du code, organisé de façon un peu plus complexe, ou même du code qui réutilise des fonctionnalités en dehors de votre script.
En Python, on peut écrire des choses simples qui tiennent dans un seul fichier de script.
Souvent, les projets plus ambitieux écrivent du code organisé de façon un peu plus complexe,
ou encore du code qui réemploie des fonctionnalités décrites dans des scripts externes.
----
@ -16,7 +17,9 @@ souvent, et même pour des choses simples, on peut se retrouver à écrire du co
Jusque là, tout ce que nous avons écrit, c'est des [modules]{.naming}. Ce sont des fichiers .py (vides ou non, ils peuvent contenir du code).
Il existe aussi les [packages]{.naming}. Ce sont des répertoires, pouvant contenir d'autres modules, et qui sont utilisables comme des modules (peuvent contenir du code). Pour pouvoir associer du code à ces répertoires et les considérer comme des packages, le langage Python impose que le code associé repose dans un fichier nommé `__init__.py` dans le répertoire. L'intérêt principal d'un package est d'y ranger d'autres packages et modules, pour organiser son code en arborescence cohérente.
Il existe aussi les [packages]{.naming}. Ce sont des répertoires, pouvant contenir d'autres modules, et qui sont utilisables comme des modules (peuvent contenir du code).
Pour pouvoir associer du code à ces répertoires et les considérer comme des packages, la spécification du langage Python impose que le code associé repose dans un
fichier nommé `__init__.py` dans le répertoire. L'intérêt principal d'un package est d'y ranger d'autres packages et modules, pour organiser son code en arborescence cohérente.
----
@ -24,9 +27,10 @@ Il existe aussi les [packages]{.naming}. Ce sont des répertoires, pouvant conte
Les modules et packages ont la même typographie que les variables (car Python les traite comme des variables de type `module`) :
- Tout en minuscules (ex. `mypackage`)
- <span style="color:red;">Sans espace ni tiret</span> (ex. `my_package`)
- etc.
- Tout en minuscules (ex. `my_package`)
- Sans espace ni tiret (ex. `super_package`). Un module contenant un espace n'est pas réutilisable.
- Aucun caractère accentué (ex. `derogations_generiques`)
- Peut contenir des chiffres, mais doit commencer par une lettre.
----
@ -37,87 +41,135 @@ Python est à la fois un langage et un exécutable interprétant des scripts éc
L'interpréteur est toujours livré avec ce qu'on appelle la bibliothèque standard.
Il s'agit d'une immense bibliothèque de fonctionnalités, que l'on peut réutiliser dans nos programmes.
La bibliothèque propose des outils pour manipuler du texte, du réseau, des bases de données, des fonctions mathématiques etc.
La bibliothèque propose des outils pour manipuler du texte, du réseau, des bases de données, des fonctions mathématiques, etc.
[Liste des modules et packages de la bibliothèque standard](https://docs.python.org/3/library/)
----
Parmi les très nombreux modules de la bibliothèque standard, assez peu vous serviront régulièrement. En voici une liste :
### Modules utiles
Parmi les très nombreux modules (environ `220`) de la bibliothèque standard, assez peu vous serviront régulièrement.
En voici une liste :
- `random` : génération de nombres aléatoires
- `math` : fonctions mathématiques et trigonométriques
- `statistics` : fonctions statistiques, comme l'écart type.
- `math` : fonctions arithmétiques, logarithmiques et trigonométriques
- `statistics` : fonctions statistiques de base, comme l'écart type.
- `pathlib` : outils pour gérer les chemins de fichier
- `datetime` : types pour gérer les dates
- `re` : pour gérer des expressions régulières (compatibles _Perl_)
D'autres peuvent servir ponctuellement, comme `csv` ou `sqlite3` pour nos exemples à venir.
----
Comment accéder à ces nombreuses fonctionnalités ?
Par défaut, elles ne sont pas toutes accessibles dans votre code, excepté celles documentées dans les sections "built-in", et pour pouvoir les utiliser, il faut en faire la demande explicite dans vos modules via la notion d'`import` :
### Importer des fonctionnalités
Variantes :
Par défaut, seules les fonctionnalités documentées dans les sections natives (`builtins`) sont directement utilisables,
et pour pouvoir utiliser les autres fonctionnalités, il faut en faire la demande explicite dans votre code via la notion d'`import` :
```{.python .numberLines}
# Les imports sont écrits au sommet d'un module
import math # vous avez maintenant une variable nommée `math`
from os import path # vous avez une variable `path`
```python {.numberLines}
import math # importe le module math via une variable
from os import path # importe le module path du package os
from os.path import basename # importe directement basename depuis os.path
from math import sin, cos # importer plusieurs fonctions depuis math
import datetime as dt # datetime est utilisable en tant que dt uniquement
from math import sin, cos # importer plusieurs fonctions de la même bibliothèque
math.cos(math.pi / 2.0) # possible grâce à import math
cos(0) # possible grâce à from math import cos
path.join(…)
print(math.cos(math.pi / 2.0)) # utilise uniquement l'import de math
print(cos(math.pi / 2.0), sin(math.pi / 2.0)) # utilise l'import de sin et cos
print(path.basename("/chemin/de/fichier/linux.txt")) # utilise l'import de path
print(basename("/chemin/de/fichier/linux.txt")) # utilise l'import de basename
print(dt.date.today())
```
Chaque script qui utilise directement un _symbole_ doit toujours l'importer.
Chaque script qui référence directement un objet provenant d'une bibliothèque doit toujours l'importer.
**Note** : Pour importer le contenu d'un module, l'interpréteur Python doit toujours exécuter le contenu dudit module, afin d'en connaître le contenu. Attention donc à la présence de `print` dans ledit module.
**Note** : Pour importer le contenu d'un module ou un élément d'un module, l'interpréteur Python doit toujours
exécuter le contenu dudit module, afin d'en connaître le contenu.
----
Note : Normalement, importer un package donne accès uniquement aux variables directement définies dans celui-ci, mais pas
aux sous-modules ou packages qui s'y trouvent. Si vous créez un package `package` contenant un sous-module `module1` :
```{.python .numberLines}
import package
print(package.module1) # ceci provoque une erreur
```
Cela ne fonctionne pas car Python ne charge pas directement les modules et packages présents à l'intérieur du package.
Pour y avoir accès, il faut que le package lui-même importe les éléments qui seront directement accessibles depuis celui-ci :
```{.python .numberLines}
from . import module1 # ajoute une variable module1 dans le package
```
`package/__init__.py`
----
## Faire un petit peu de calcul avec la bibliothèque standard
## Un peu de calcul avec la bibliothèque standard
Avec l'outil que sont les imports, on peut avoir accès à de nombreuses fonctions de calcul.
Par exemple, on pourrait générer un nombre aléatoire et calculer un cosinus :
```{.python .numberLines}
```python {.numberLines}
import random
import math
random_number = random.randint(0, 100) # nombre entre 0 et 100 inclus
cosinus_result = math.cos(random_number) # cosinus du nombre aléatoire
number = random.randint(0, 100) # nombre entre 0 et 100 inclus
cosinus_result = math.cos(number) # cosinus du nombre aléatoire
while True:
try:
guess = int(input("Saisissez un nombre entre 0 et 100 : "))
# Les lignes suivantes s'exécuteront si la ligne du dessus ne génère pas d'erreur
if guess < number:
print("C'est supérieur.")
elif guess > number:
print("C'est inférieur.")
else:
print("C'est gagné !")
break # Quitter la boucle
except ValueError: # si impossible de récupérer l'équivalent entier
print("Saisie incorrecte.")
```
- [Documentation du module `random`](https://docs.python.org/3/library/random.html)
- [Documentation du module `math`](https://docs.python.org/3/library/math.html)
----
## Bonus : Installer des paquets externes ([PyPI](https://pypi.org))
## Extra : Installer des paquets externes ([PyPI](https://pypi.org))
Avec PyCharm, on va installer simplement quelques paquets externes et utiliser leurs fonctionnalités :
Avec PyCharm, on peut installer simplement quelques paquets externes et utiliser leurs fonctionnalités :
1. `requests` : Faire des requêtes HTTP plus simplement
1. `requests` : Faire des requêtes HTTP et consommer des API REST
2. `unidecode` : Translittération et désaccentuation
3. `attrs` : Accélérer l'écriture de classes
3. `faker` : Anonymiser avec des données factices (noms, adresses, téléphone, code pin, etc.)
----
### Gérer un environnement virtuel pour installer des bibliothèques
Python est livré avec un outil en ligne de commande nommé `pip`, qui vous permet facilement de télécharger et
installer des bibliothèques externes publiées sur le site [PyPI](https://pypi.org).
Si pour votre projet, vous avez besoin de la dernière version de la bibliothèque `requests`, par exemple, vous pouvez l'installer
avec la commande suivante dans un terminal :
```sh {.numberLines}
pip install requests
```
La dernière version de la bibliothèque compatible avec votre version de Python sera installée dans un répertoire spécifique
avec le nom `requests`. Vous ne pouvez pas installer plusieurs versions simultanées de la même bibliothèque au même endroit.
----
### Les environnements virtuels pour gérer ses versions de paquets
Un environnement virtuel (`venv`) se présente sous la forme d'un répertoire avec quelques scripts, dont `python`. Lorsqu'un environnement
virtuel est "activé" via l'un de ses scripts, il se passe plusieurs choses :
- Les bibliothèques installées avec `pip` sont installées dans cet environnement virtuel
- L'exécutable `python` de l'environnement peut importer les bibliothèques installées dans ce dernier
L'intérêt est de pouvoir facilement gérer plusieurs projets qui nécessitent des versions différentes des mêmes
bibliothèques.
Les environnements virtuels peuvent être créés, sélectionnés, et utilisés automatiquement avec Visual Studio Code
et PyCharm.
----
#### PyCharm
TODO
----
#### Visual Studio Code
TODO

View File

@ -96,7 +96,7 @@ Tout ce qu'on a d'utile, c'est :
Une bibliothèque externe Python propose des outils pour effectuer certaines de ces actions :
```bash {.numberLines}
```sh {.numberLines}
pip install textfile
```

View File

@ -68,7 +68,7 @@ source ./activate # ou activate.csh ou activate.fish
Sous Windows, vous devez plutôt simplement exécuter une commande:
```bash {.numberLines}
```sh {.numberLines}
./scripts/activate.ps1 # Dans le powershell si vous êtes dans le répertoire du venv
```

View File

@ -57,11 +57,11 @@ pour les créer, en observant quelques précautions:
Si vous souhaitez installer des paquets qui sont (avec leurs dépendances) disponibles dans un répertoire local avec `pip`, que ce
soit via un fichier `requirements.txt` ou manuellement, vous devez utiliser l'une des commandes suivantes (les options sont les plus importantes):
```bash {.numberLines}
```sh {.numberLines}
pip install -r requirements.txt --no-index --find-links=<répertoire>
```
```bash {.numberLines}
```sh {.numberLines}
pip install <fichier .whl> --no-index --find-links=<répertoire>
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 369 KiB

View File

@ -2,6 +2,8 @@
----
## Reçu d'achat
Vous avez devant vous un adhérent de la librairie.
Il souhaite acheter 3 livres :
@ -10,13 +12,13 @@ Il souhaite acheter 3 livres :
- Guide de Sao Paulo (12,00€)
Le livre Python pour les débutants bénéficie d'une réduction de 20% sur son prix.
Affichez un récapitulatif du reçu d'achat en stockant dans des variables les titres
des livres et leurs prix.
Affichez un récapitulatif du reçu d'achat **en stockant dans des variables les titres
des livres et leurs prix**.
Le reçu peut ressembler à cela :
```
Bookie Dans SAS
Librairie des Recollets
Reçu
- La fable du marronnier 19.95
@ -28,3 +30,32 @@ Réduction
Total
<calculez la valeur>
```
----
## Minutes et secondes
> [!WARNING]
> Critical content comes here.
Vous lancez une simulation complexe, qui vous prend beaucoup de temps. Quand elle se termine,
vous récupérez une valeur indiquant le nombre de secondes nécessaires à son exécution.
Vous récupérez la valeur `867`{.python}.
Affichez le nombre de minutes et de secondes nécessaires à l'exécution de cette tâche,
à savoir `14` minutes et `27` secondes. Vous avez besoin de deux opérateurs arithmétiques pour obtenir
le résultat attendu.
----
## Heures, minutes et secondes
(_Voir exercice précédent_) Cette fois-ci, vous récupérez suite à la simulation un nombre encore
plus élevé, `4377`{.python}.
Affichez cette fois-ci le nombre d'heures, de minutes et de secondes nécessaires à l'exécution
de cette tâche. Vous devriez obtenir `1` heure, `12` minutes et `57` secondes.
----