Browse Source

Rewrite chapter 3

Move unused concepts into a `fragments` subdir
Add stuff about sys.stdout, stdin, stderr, error codes

bottom-up vs top-down

airport example
master
Dimitri Merejkowsky 5 years ago
parent
commit
e5eaae92cb
4 changed files with 779 additions and 291 deletions
  1. +73
    -0
      sessions/fragments/format.md
  2. +92
    -0
      sessions/fragments/sort.md
  3. +208
    -0
      sessions/fragments/tuples.md
  4. +406
    -291
      sessions/python-03.md

+ 73
- 0
sessions/fragments/format.md View File

@@ -0,0 +1,73 @@
% notions: string.format(), mini-language de specification de format

# Formater des chaînes de caractères

Problème:

\vfill

```python
>>> nom = "Ford"
>>> résultat = 42
>>> message = "Bonjour, " + nom + ". "
>>> message += "La réponse est: " + str(résultat) + "."
>>> message
'Bonjour, Ford. La réponse est: 42.'
```

\vfill

Ce n'est pas très lisible ...

# format()

Solution: utiliser un "template" et la méthode `format()`

\vfill

```python
>>> nom = "Ford"
>>> résultat = 42
>>> template = "Bonjour, {}. La réponse est: {}"
>>> message = template.format(nom, résultat)
>>> message
'Bonjour, Ford. La réponse est: 42.'
```

# format() avancé

On peut aussi nommer les remplacements:

```python
template = "Bonjour, {nom}. La réponse est: {résultat}"
template.format(nom="Ford", résultat=42)
```

# format() avancé

On peut aussi faire des alignements et du "padding":

\vfill

```python
template = "{name:>10}: {score:03}"
print(template.format(name="Alice", score=42))
print(template.format(name="Bob", score=5))
```

```
Alice: 042
Bob: 005
```

# Explications

Le texte dans les accolades après le `:` est un mini-langage de spécification de format:

* `>10` signifie: "aligner a droite, taille maximale 10"
* `03` signifie: "rajouter des zéros en début de nombre jusquà atteindre 3 chiffres".

Plus de précisions dans la documentation:


\url{https://docs.python.org/fr/3/library/string.html#format-specification-mini-language}.

+ 92
- 0
sessions/fragments/sort.md View File

@@ -0,0 +1,92 @@
% notions: sort, lambdas, sort avec une fonction key()

# Trier des listes

# sort() - ordre naturel

```python
>>> nombres = [2, 3, 1, 5]
>>> nombres.sort()
>>> nombres
[1, 2, 3, 5]
```

Notez que la liste est modifiée *sur place*.

# sort() - ordre alphabétique

```python
>>> mots = ["abeille", "faucon", "chat"]
>>> mots.sort()
>>> mots
['abeille', 'chat', 'faucon']
```

# sort() - ordre lexicographique

Pour chaque "liste-élément" on compare le premier élément.
S'il y a égalité, on regarde le deuxième élément, etc:

```python
>>> composite = [["chat", 1], ["abeille", 2], ["chat", 3]]
>>> composite.sort()
>>> composite
[['abeille', 2], ['chat', 1], ['chat', 3]]
```

L'ordre alphabétique est l'ordre lexicographique pour les chaînes de caractères :)

# Attention!

Tous les éléments de la liste doivent être comparables deux à deux:

\vfill

```python
>>> mauvaise_liste = ["un", 2]
>>> mauvaise_liste.sort()
TypeError
```


# Comparer autrement

Exemple: trier les mots par leur taille avec l'argument `key`

\vfill

```python
def taille(mot):
return len(mot)

mots = ["chat", "abeille", "faucon"]
mots.sort(key=taille)
>>> mots
["chat", "faucon", "abeille"]
```

# Lambda

Sert définir une fonction sans utiliser `def`

```python
>>> retourne_42 = lambda: 42 # pas d'argument
>>> retourne_42()
42
>>> ajoute_deux = lambda x: x + 2 # un seul argument
>>> ajoute_deux(3)
5
>>> multiplie = lambda x, y: x* y # deux arguments
>>> multiplie(2, 3)
6
```
Note: le corps de la fonction doit tenir en une seule ligne

# Utilisation avec sort

```python
>>> mots = ["chat", "abeille", "faucon"]
>>> mots.sort(key=lambda x: len(x))
>>> mots
["chat", "faucon", "abeille"]
```

+ 208
- 0
sessions/fragments/tuples.md View File

@@ -0,0 +1,208 @@
% notions: tuples, immuables & mutables, passage par référence,
% copie, retour multiples

#

\center Les tuples


# Création de tuples


```python
mon_tuple = tuple() # un tuple vide
mon_tuple = () # aussi un tuple vide
mon_tuple = (1, 2) # un tuple à deux éléments
```

# Note

C'est la virgule qui fait le tuple, pas les parenthèses
(on n'utilise les parenthèse que pour l'esthétique)

\vfill

```python
(1)
# pas un tuple, juste le nombre 1 (entre parenthèses)
(1,)
# un tuple à un élément
1,
# le *même* tuple
```

# Indexation, test d'appartenance

```python
>>> couple = ('Starsky', 'Hutch')
>>> couple[0]
'Starsky'
>>> couple[1]
'Hutch'
>>> couple[3]
IndexError


>>> 'Starsky' in couple
True
>>> 'Batman' in couple
False
```

Rien de nouveau en principe :p

# Déstructuration

Créer plusieurs variables en une seule ligne

```python
>>> couple = ("Batman", "Robin")
>>> héro, side_kick = couple
>>> héro
'Batman'
>>> side_kick
'Robin'
```

On dit aussi: unpacking

# Quelques erreurs

```python
>>> héro, side_kick, ennemi = couple
ValueError (3 != 2)

>>> (héro,) = couple
ValueError (1 != 2)

# Gare à la virgule:
>>> héro, = 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)
```

# On peut aussi déstructurer des listes

```python
>>> fruits = ["pomme", "banane", "orange"]
>>> premier, deuxième, troisième = fruits
```

# Retour multiple

Retourner plusieurs valeurs:

```python
def tire_carte(():
valeur = "10"
couleur = "trèfle"
return (valeur, couleur)

(v, c) = tire_carte()
print("{} de {}", v, c)
# 10 de trèfle
```

\vfill

En fait c'est juste une manipulation de tuples :)

# Les tuples sont immuables

```python
>>> couple = ('Starsky', 'Hutch')
>>> couple[0] = 'Batman'
TypeError
```

Les méthodes `count()` et `index()` existent
parce qu'elles ne modifient pas le tuple.

# Les tuples sont immuables (2)

Les méthodes qui modifieraient le tuple n'existent pas:

```python
>>> couple = ('Starsky', 'Hutch')
>>> couple.clear()
AttributeError
```



#

\center Mutables, immuables, et références

# Mutables et immuables

* Mutables: listes, ensembles, dictionnaires
* Immuables: entiers, flottants, booléens, strings

# Passage par référence

En Python, on ne manipule jamais les objets directement, on
ne manipule que des *références* sur ces objets.

```python
x = 3 # x est une référence vers l'objet 'entier 3'
x = "hello" # x référence une string
```

# Exemple 1

```python
def ajoute_trois(x):
x = x + 3 # crée une nouvelle référence

mon_entier = 42
ajoute_trois(mon_entier)
# 'mon_entier' vaut toujours 42
```

# Exemple 2

```python
def ajoute_trois(l):
l.append(3)
# Ne crée pas de nouvelle référence
# Appelle une méthode qui modifie 'l' sur place

ma_liste = [1, 2]
ajoute_trois(ma_liste)
# 'ma_liste' vaut maintenant [1, 2, 3]
```

# Exemple 3

```python
def get_max(liste):
autre_liste = liste.copy()
autre_liste.sort()
return autre_liste[-1]

ma_liste = [1, 3, 2]
x = get_max(ma_liste)
# x = 3
# `ma_liste` n'a pas changé
```

# Notes

* on peut aussi utiliser la fonction native "max()"
* on peut aussi utiliser une slice: `autre_liste = liste[:]`

# Conclusion

Une fonction ne peut modifier la valeur de ses arguments qui s'ils sont
mutables.

Toutes les copies doivent être explicites

+ 406
- 291
sessions/python-03.md View File

@@ -1,184 +1,225 @@
% Programmation avec Python (chapitre 3)
% Dimitri Merejkowsky

\centering
Retours sur le chapitre 2

#

\centering
Retour sur les listes
\center \huge None

# extend()
# Exprimer l'absence

Pour concaténer des listes:
Exemple: clé non présente dans un dictionnaire:

```python
>>> fruits = ["pomme", "banane"]
>>> fruits.extend(["orange", "abricot"])
>>> fruits
['pomme', 'banane', 'orange', 'abricot']
>>> scores = { "Anne": 42, "Bernard": 5 }
>>> score1 = scores.get("Anne")
>>> score1
42
>>> score2 = scores.get("Sophie")
>>> score2
<rien>
```
\vfill

Peut aussi s'écrire `+=`
En réalité, `score2` a bien une valeur: `None`.

```python
>>> nombres = [1, 2]
>>> nombres += [3, 4]
>>> nombres
[1, 2, 3, 4]
```
L'interpréteur n'affiche rien quand la valeur est `None`

# pop()

`pop()` existe aussi pour les listes:
# None est ambigu

\vfill
None est `falsy`, tout comme 0, False et les listes vides:

```python
>>> fruits = ["pomme", "banane"]
>>> fruits.pop() # Retourne l'élément
'pomme'
>>> fruits # Et modifie la liste
["banane"]

>>> vide = list()
>>> vide.pop()
IndexError
element = dictionnaire.get(clé)
if not element:
...
```

# clear()
Mais ici, comment faire la différence entre:

Pour vider une liste:
* la clé existe et vaut None
* la clé existe et est falsy
* la clé n'existe pas ?

# Solution 1: tester l'appartenance

Avec `in`:

```python
>>> fruits = ["pomme", "banane"]
>>> fruits.clear()
>>> fruits
[]
if clé in dictionnaire:
# La clé existe, pas d'erreur
valeur = dictionnaire[clé]
```

# index()
# Solution 2: tester None

Pour récupérer la position d'un élément:
Avec `is`:

```python
>>> fruits = ["pomme", "banane"]
>>> fruits.index("banane")
>>> 1
>>> fruits.index("citron")
>> ValueError
>>> scores = { "Anne": 42, "Bernard": 5 }
>>> score1 = scores.get("Anne")
>>> score1 is None
False
>>> score2 = scores.get("Sophie")
>>> score2 is None
True
```

# count()

Pour compter le nombre d'occurrences d'un élément
# Retourner None

`None` est aussi la valeur par défaut lorsqu'il n'y a pas de `return`
dans le corps de la fonction:

```python
>>> fruits = ["pomme", "banane", "pomme", "poire"]
>>> fruits.count("pomme")
2
>>> fruits.count("poire")
1
>>> fruits.count("citron")
0
>>> def ne_retourne_rien(a, b):
>>> c = a + b

>>> résultat = ne_retourne_rien(3, 2)
>>> résultat is None
True
```

# Retourner None (2)

On dit aussi que la fonction est une *procédure*.

On a déjà écrit des procédures: dans `pendu.py`, `display_hint()` et `main()`
ne retournaient rien.

# sort()
# Retourner None au autre chose

```python
def trouve_dans_liste1(liste, element):
if element in list:
return liste.index(element)

Pour trier une liste.

def trouve_dans_liste2(liste, element):
if element in list:
return liste.index(element)
else:
return None
```

\vfill

* Par ordre naturel
Les deux fonctions font la même chose : `trouve_dans_liste2` est simplement plus *explicite.*

# Types optionnels

```python
>>> nombres = [2, 3, 1, 5]
>>> nombres.sort()
>>> nombres
[1, 2, 3, 5]
def trouve_dans_liste(liste, element):
if element in list:
return liste.index(element)
else:
return None
```
On dit aussi que le type de retour de `trouve_dans_liste` est *optionnel*.

\vfill
#

\centering \huge Retour sur les listes

* Par ordre alphabétique
# Méthodes

```python
>>> mots = ["abeille", "faucon", "chat"]
>>> mots.sort()
['abeille', 'chat', 'faucon']
fruits = ["pomme", "banane"]
fruits.append("orange")
```

# Ordre lexicographique
Quand on écrit `fruits.append("orange")`, on peut voir `append()`
comme une "fonction" qui prendrait `fruits` en argument implicite
(avant le `.`), et `orange` en argument explicite.

Pour chaque "liste-élément" on compare le premier élément.
S'il y a égalité, on regarde le deuxième élément, etc:
On appelle ce genre de fonction une **méthode**.

# clear()

Pour vider une liste:

```python
>>> composite = [["chat", 1], ["abeille", 2], ["chat", 3]]
>>> composite.sort()
[['abeille', 2], ['chat', 1], ['chat', 3]]
>>> fruits = ["pomme", "banane"]
>>> fruits.clear()
>>> fruits
[]
```

\vfill
Notez que la méthode `clear()` ne renvoie rien!
La liste est modifiée *sur place*.

L'ordre alphabétique est l'ordre lexicographique pour les chaînes de caractères :)

# Attention!
# extend()

Pour concaténer des listes:

```python
>>> mauvaise_liste = ["un", 2]
>>> mauvaise_liste.sort()
TypeError
>>> fruits = ["pomme", "banane"]
>>> fruits.extend(["orange", "abricot"])
>>> fruits
['pomme', 'banane', 'orange', 'abricot']
```
\vfill

Peut aussi s'écrire `+=`

```python
>>> nombres = [1, 2]
>>> nombres += [3, 4]
>>> nombres
[1, 2, 3, 4]
```

# Comparer autrement

Trier les mots par leur taille
# pop()

* Avec l'argument `key`
On a vu la méthode `pop()` pour les dictionnaires, mais elle existe
aussi pour les listes:

\vfill

```python
def taille(mot):
return len(mot)
>>> fruits = ["pomme", "banane"]
>>> fruits.pop() # Retourne l'élément
'pomme'
>>> fruits # Et modifie la liste
["banane"]
```

mots = ["chat", "abeille", "faucon"]
mots.sort(key=taille)
>>> mots
["chat", "faucon", "abeille"]
# pop() sur liste vide

```python
>>> liste_vide = list()
>>> liste_vide.pop()
IndexError
```

# Lambda

Sert définir une fonction sans utiliser `def`
# index()

Pour récupérer la position d'un élément:

```python
>>> retourne_42 = lambda: 42 # pas d'argument
>>> retourne_42()
42
>>> ajoute_deux = lambda x: x + 2 # un seul argument
>>> ajoute_deux(3)
5
>>> multiplie = lambda x, y: x* y # deux arguments
>>> multiplie(2, 3)
6
>>> fruits = ["pomme", "banane"]
>>> fruits.index("banane")
>>> 1
>>> fruits.index("citron")
>> ValueError
```
Note: le corps de la fonction doit tenir en une seule ligne

# Utilisation avec sort
# count()

Pour compter le nombre d'occurrences d'un élément

```python
>>> mots = ["chat", "abeille", "faucon"]
>>> mots.sort(key=lambda x: len(x))
>>> mots
["chat", "faucon", "abeille"]
>>> fruits = ["pomme", "banane", "pomme", "poire"]
>>> fruits.count("pomme")
2
>>> fruits.count("poire")
1
>>> fruits.count("citron")
0
```


# Indexer des listes

Rappel:
@@ -214,14 +255,19 @@ Ou "faire des slices", ou "slicer".
['d', 'e']
>>> lettres[1:-2] # fin négative
['b', 'c']
>>> lettres[:] # une copie
['a', 'b', 'c', 'd', 'e']
```


#

\centering
Retour sur les strings
\centering \huge Retour sur les strings

# Rappel

On a vu que les strings étaient "presque" des liste de caractères.

Par exemple, on peut itérer sur les lettres d'un mot avec: `for lettre
in mot`.

# index() et count() marchent aussi

@@ -235,7 +281,7 @@ Retour sur les strings

# Trancher des chaînes de caractères

Ou slicer des strings:
On peu aussi slicer des strings:

\vfill

@@ -249,290 +295,359 @@ Ou slicer des strings:
'monde'
```

# Les strings sont immuables

# Formater des chaînes de caractères
Mais on ne **peut pas** modifier une string "sur place".

Problème:

\vfill
```python
>>> message = "Bonjour, monde !"
>>> message[-1] = "?"
TypeError
```

```python
>>> nom = "Ford"
>>> résultat = 42
>>> message = "Bonjour, " + nom + ". "
>>> message += "La réponse est: " + str(résultat) + "."
>>> message
'Bonjour, Ford. La réponse est: 42.'
>>> message = "Bonjour, monde !"
>>> message.clear()
AttributeError
```

\vfill
#

Ce n'est pas très lisible!
\centering \huge La ligne de commande

# format()
# Schéma

Solution: utiliser un "template" et la méthode `format()`
![programme](programme.png)

\vfill
# Entrée / sortie

```python
>>> nom = "Ford"
>>> résultat = 42
>>> template = "Bonjour, {}. La réponse est: {}"
>>> message = template.format(nom, résultat)
>>> message
'Bonjour, Ford. La réponse est: 42.'
```
3 canaux:

# format() avancé
* Une entrée qu'on peut lire (`stdin`)
* Deux sorties:
* Sortie normale (ou standard) (`stdout`)
* Sortie d'erreur (`stdout`)

On peut aussi nommer les remplacements:

```python
template = "Bonjour, {nom}. La réponse est: {résultat}"
template.format(nom="Ford", résultat=42)
```
# Accès en Python

# format() avancé
* Pour lire stdin: `input()`
* Pour écrire dans stdout: `print()`
* Pour écrire dans stderr: pas de fonction native

On peut aussi faire des alignements et du "padding":
# Accès en Python (2)

\vfill
Rajouter `import sys` en haut du fichier, puis:

```python
template = "{name:>10}: {score:03}"
print(template.format(name="Alice", score=42))
print(template.format(name="Bob", score=5))
```
* `sys.stdin.read()`
* `sys.stout.write()`
* `sys.stderr.write()`

```
Alice: 042
Bob: 005
```
On peut aussi utiliser:

# Explications
`print("erreur", file=sys.stderr)`

Le texte dans les accolades après le `:` est un mini-langage de spécification de format:

* `>10` signifie: "aligner a droite, taille maximale 10"
* `03` signifie: "rajouter des zéros en début de nombre jusquà atteindre 3 chiffres".
# Accès depuis l'invite de commande

Plus de précisions dans la documentation:
On dit aussi *shell*, ou *CLI* (pour *command line interface*).

* stdin: Taper quand le shell vous laisse la main, finir par 'entrée'
* stdout et stderr sont affichés en même temps pas défaut

\url{https://docs.python.org/fr/3/library/string.html#format-specification-mini-language}.
# Rediriger stdout

#
Linux , macOS, Windows:

\center
None
```
python3 fichier.py > output.txt
```

# Exprimer l'absence
stdout sera écrit dans `output.txt`, et seul `stderr` sera visible.

```python
>>> scores = { "Anne": 42, "Bernard": 5 }
>>> score1 = scores.get("Anne")
>>> score1
42
>>> score2 = scores.get("Sophie")
>>> score2
<rien>
```

En réalité, `score2` a bien une valeur: `None`.
# Code de retour

L'interpréteur n'affiche rien quand la valeur est `None`

* 0 quand tout va bien
* un autre entier quand quelque chose va mal

# None est falsy
\vfill

```python
element = dictionnaire.get(clé)
if element:
...
```
> Toutes les familles heureuses se ressemblent, mais chaque famille
> malheureuse l'est à sa façon.
>
> Tolstoï

Mais ici, comment faire la différence entre:
# Code de retour

* la clé existe et vaut 0, une chaîne vide, ou quoique ce soit de falsy
* la clé existe et vaut None
* la clé n'existe pas
Les valeurs d'erreur possibles sont en général présentes
dans la documentation.

# Tester l'appartenance
Note: **ne pas retourner 0** en cas d'erreur, même minime, et même si
un message a été affiché.

Avec `in`:
C'est important pour pourvoir composer plusieurs programmes (on y
reviendra).

```python
if clé in dictionnaire:
# La clé existe, pas d'erreur
valeur = dictionnaire[clé]

# Afficher le code retour depuis le shell

```bash
# Linux, macOS
$ python3 code.py
$ echo $?
0
```

# Tester None
```bash
# Windows
> python3 code.py
> echo %ERRORLEVEL%
0
```

Avec `is`:
# Gestion du code de retour en Python

Par défaut, le code de retour est 0.

On peut terminer le programme avec `sys.exit()` et un numéro:

```python
>>> scores = { "Anne": 42, "Bernard": 5 }
>>> score1 = scores.get("Anne")
>>> score1 is None
False
>>> score2 = scores.get("Sophie")
>>> score2 is None
True
import sys

def fait_quelque_chose():
if erreur:
sys.exit(1)
```

# Lever l'ambiguïté
Note: dans un vrai programme, veillez à construire et afficher un
message utile!

Notez qu'ici Sophie peut être dans le dictionnaire, mais avec une valeur 'None',
ou bien ne pas y être.
# Gestion du code de retour en Python (2)

Attention aux ambiguïtés, donc!
`sys.exit(0)` pour terminer immédiatement et sans erreur.

Pas de méthode magique : il faut être au courant du problème.
```python
import sys

def antivirus():
problèmes = cherche_problèmes()

# Retourner None
if not problèmes:
print("Aucun problème trouvé")
sys.exit(0)

`None` est aussi la valeur par défaut lorsqu'il n'y a pas de `return`
dans le corps de la fonction:
for problème in problèmes:
# traite chaque problème un à un
...
````

# Gestion du code de retour en Python - un raccourci

On peut passer le message d'erreur directement à `sys.exit()` avec
une string au lieu d'un numéro:

```python
>>> def ne_retourne_rien(a, b):
>>> c = a + b
if erreur:
sys.exit("Une erreur est survenue")

>>> résultat = ne_retourne_rien(3, 2)
>>> résultat is None
True
```

# Retourner None au autre chose
# Les arguments d'un programme

```python
def trouve_dans_liste1(liste, element):
if element in list:
return liste.index(element)
Pour lancer un programme, on tape son nom, suivi de mots séparés pas
des espaces.

En Python, ça donne

def trouve_dans_liste2(liste, element):
if element in list:
return liste.index(element)
else:
return None
```
python3 fichier.py arg1 arg2
```

\vfill
# Accès aux arguments en Python

Les deux fonctions font la même chose! `trouve_dans_liste2` est simplement plus *explicite.*
* `import sys`
* `sys.argv`

# Types optionnels
`sys.argv` est une liste, et son premier argument est
*toujours* le nom du fichier exécuté.

# Exemple

```python
def trouve_dans_liste(liste, element):
if element in list:
return liste.index(element)
else:
return None
# Dans foo.py
import sys
print("Fichier source:", sys.argv[0])
print("Reste des arguments:", sys.argv[1:])
```

\vfill

```
$ python3 foo.py un deux
Fichier source: foo.py
Reste des arguments: ['un', 'deux']
```
On dit aussi que le type de retour de `trouve_dans_liste` est *optionnel*.

#

\center Les tuples
\center \huge Cas pratique

# Création de tuples
# Squelette

Décodeur de noms d'aéroports

```python
mon_tuple = tuple() # un tuple vide
mon_tuple = () # aussi un tuple vide
mon_tuple = (1, 2) # un tuple à deux éléments
```
* Lire un fichier avec les codes et les noms associés
* En faire un dictionnaire
* Utiliser le premier argument comme nom de code
* Afficher le nom complet de l'aéeroport, ou une
erreur s'il est inconnu.

# Note
# Différentes approches

C'est la virgule qui fait le tuple, pas les parenthèses
(on n'utilise les parenthèse que pour l'esthétique)
* Bottom-up (approche *ascendante*)
* Top-Bottom (approche *descendante*)

\vfill
# Approches bottom-up

Utilisé pour le pendu:

* construire des blocs élémentaires (les petites fonctions `has_won`,
`display_hint`, etc...)
* les assembler pour faire un tout plus "haut niveau" (le corps de la
fonction `main()`

# Approche top-down

Essayons de partir du plus "haut niveau" et de "descendre" vers les
blocs élémentaires

# Code

```python
(1)
# pas un tuple, juste le nombre 1 (entre parenthèses)
(1,)
# un tuple à un élément
1,
# le *même* tuple
def main():
dico = fabrique_dico()
code = lire_code()
nom = trouve_code(code, dico)
if nom:
print(nom)
else:
affiche_erreur(code)

main()
```

# Indexation, test d'appartenance
On a fait comme si le code dont on avait besoin était déjà écrit :)

# lire_code()

```python
>>> couple = ('Starsky', 'Hutch')
>>> couple[0]
'Starsky'
>>> couple[1]
'Hutch'
>>> couple[3]
IndexError
import sys

def lire_code():
if len(sys.argv) < 2:
print(
"Pas assez d'arguments",
file=sys.stderr
)
sys.exit(1)
argument = sys.argv[1]
# On accepte `cdg` ou `CDG`.
code = argument.upper()
return code
```


# fabrique_dico()

Le fichier `airports.txt` ressemble à ça:

>>> 'Starsky' in couple
True
>>> 'Batman' in couple
False
```
CDG Paris-Charles de Gaulle
ORY Paris-Orly
NCE Nice-Côte d'Azur
...
```

Rien de nouveau en principe :p
Téléchargez-le ici:

# Déstructuration
\url{https://raw.githubusercontent.com/E2L/cours-python/master/sources/airports.txt}

Créer plusieurs variables en une seule ligne
Clique droit / Enregistrer sous / airports.txt

```python
>>> couple = ("Batman", "Robin")
>>> héro, side_kick = couple
>>> héro
'Batman'
>>> side_kick
'Robin'
# fabrique_dico() - 2

>>> héro, side_kick, ennemi = couple
ValueError 3 != 2
>>> héro, = couple
ValueError 1 != 2
ValueError
```python
def fabrique_dico():
dico = dict()
fichier = open("airports.txt", "r")
contenu = fichier.read()
fichier.close()

>>> héro = couple
# OK, mais la variable héro est maintenant un tuple ...
lignes = contenu.splitlines()
for ligne in lignes:
code = ligne[0:3]
nom = ligne[4:]
dico[code] = nom
return dico
```

# On peut aussi déstructurer des listes
# trouve_code()

```python
>>> fruits = ["pomme", "banane", "orange"]
>>> premier, deuxième, troisième = fruits
def trouve_code(code, dico):
if code in dico:
return dico[code]
```

# Retours multiple
\vfill

Retourner plusieurs valeurs:
Notez le `return None` implicite!

#TODO: better Exemple, pleaz

```python
def age_sexe_ville(pseudo):
age = ...
sexe = ..
ville = ...
return (age, sexe, ville)
# affiche_erreur()

(a, s, v) = age_sexe_ville('kevin')
# on dit aussi: unpacking
```python
def affiche_erreur(code):
print(
"Code:", code,
"non trouvé",
file=sys.stderr
)
sys.exit(2)
```

Notes:

* on affiche le code qu'on a pas trouvé (c'est utile de l'avoir dans
le message)
* valeur d'erreur différente du cas où il n'y avait pas assez
d'arguments

# Rappel du `main()`


```
a -> 14
s -> M
v -> Paris
def main():
dico = fabrique_dico()
code = lire_code()
nom = trouve_code(code, dico)
if nom:
print(nom)
else:
affiche_erreur(code)

main()
```

#

\centering \huge Démo


# Pour la prochaine fois

Prenez le temps de réfléchir :)

Quelle approche avez-vous préféré ? Pourquoi ?