You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 
 
 
 
 

11 KiB

% Programmation avec Python (chapitre 3) % Dimitri Merejkowsky

\center \huge None

Jouons avec les dictionnaires

>>> scores = { "Anne": 42, "Bernard": 5 }
>>> scores["Anne"]
42
>>> scores.get("Anne")
42

>>> scores["Sophie"]
KeyError
>>> scores.get("Sophie")
<rien>

Que se passe-t-il?

Exprimer l’absence

En réalité, get() retourne None quand la clé n’est pas présente

\vfill

>>> a = 42
>>> a
42
>>> b = None
>>> b
<rien>

None est ambigu

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

element = dictionnaire.get(clé)
if not element:
    ...

Mais ici, comment faire la différence entre:

  • la clé existe et vaut None
  • la clé existe et est falsy
  • la clé n’existe pas ?

Solution 1: tester l’appartenance

Avec in:

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

Solution 2: tester None

Avec is:

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

Retourner None

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

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

Retourner None au autre chose

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


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

\vfill

Les deux fonctions font la même chose : trouve_dans_liste2 est simplement plus explicite.

Types optionnels

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.

\centering \huge Retour sur les listes

Méthodes

fruits = ["pomme", "banane"]
fruits.append("orange")

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.

On appelle ce genre de fonction une méthode.

clear()

Pour vider une liste:

>>> fruits = ["pomme", "banane"]
>>> fruits.clear()
>>> fruits
[]

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

extend()

Pour concaténer des listes:

>>> fruits = ["pomme", "banane"]
>>> fruits.extend(["orange", "abricot"])
>>> fruits
['pomme', 'banane', 'orange', 'abricot']

\vfill

Peut aussi s'écrire +=

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

pop()

On a vu la méthode pop() pour les dictionnaires, mais elle existe aussi pour les listes:

\vfill

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

pop() sur liste vide

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

index()

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

>>> fruits = ["pomme", "banane"]
>>> fruits.index("banane")
>>> 1
>>> fruits.index("citron")
>> ValueError

count()

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

>>> fruits = ["pomme", "banane", "pomme", "poire"]
>>> fruits.count("pomme")
2
>>> fruits.count("poire")
1
>>> fruits.count("citron")
0

Indexer des listes

Rappel:

>>> lettres = ["a", "b", "c", "d", "e"]
>>> lettres[0]  # ça commence à zéro
"a"
>>> lettres[4]
"e"

Mais on peut aussi compter à l’envers:

>>> lettres[-1]
"e"
>>> lettres[-2]
"d"

Trancher des listes

Ou “faire des slices”, ou “slicer”.

>>> lettres = ["a", "b", "c", "d", "e"]
>>> lettres[1:3]  # début (inclus), fin (non-inclus)
['b', 'c']
>>> lettres[:3] # début implicite
['a', 'b', 'c']
>>> lettres[3:] # fin implicite
['d', 'e']
>>> lettres[1:-2]  # fin négative
['b', 'c']

\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

>>> message = "Bonjour, monde !"
>>> message.index("B")
0
>>> message.count("o")
3

Trancher des chaînes de caractères

On peu aussi slicer des strings:

\vfill

>>> message = "Bonjour, monde !"
>>> message[1:4]
'onj'
>>> message[:7]
'Bonjour'
>>> message[9:-2]
'monde'

Les strings sont immuables

Mais on ne peut pas modifier une string “sur place”.

>>> message = "Bonjour, monde !"
>>> message[-1] = "?"
TypeError
>>> message = "Bonjour, monde !"
>>> message.clear()
AttributeError

\centering \huge La ligne de commande

Schéma

programme

Entrée / sortie

3 canaux:

  • Une entrée qu’on peut lire (stdin)
  • Deux sorties:
    • Sortie normale (ou standard) (stdout)
    • Sortie d’erreur (stdout)

Accès en Python

  • Pour lire stdin: input()
  • Pour écrire dans stdout: print()
  • Pour écrire dans stderr: pas de fonction native

Accès en Python (2)

Rajouter import sys en haut du fichier, puis:

  • sys.stdin.read()
  • sys.stdout.write()
  • sys.stderr.write()

On peut aussi utiliser:

print("erreur", file=sys.stderr)

Accès depuis l’invite de commande

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

Rediriger stdout

Linux , macOS, Windows:

python3 fichier.py > output.txt

stdout sera écrit dans output.txt, et seul stderr sera visible.

Code de retour

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

\vfill

Toutes les familles heureuses se ressemblent, mais chaque famille malheureuse l’est à sa façon.

Tolstoï

Code de retour

Les valeurs d’erreur possibles sont en général présentes dans la documentation.

Note: ne pas retourner 0 en cas d’erreur, même minime, et même si un message a été affiché.

C’est important pour pourvoir composer plusieurs programmes (on y reviendra).

Afficher le code retour depuis le shell

# Linux, macOS
$ python3 code.py
$ echo $?
0
# Windows
> python3 code.py
> echo %ERRORLEVEL%
0

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:

import sys

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

Note: dans un vrai programme, veillez à construire et afficher un message utile!

Gestion du code de retour en Python (2)

sys.exit(0) pour terminer immédiatement et sans erreur.

import sys

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

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

    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:

if erreur:
    sys.exit("Une erreur est survenue")

Les arguments d’un programme

Pour lancer un programme, on tape son nom, suivi de mots séparés pas des espaces.

En Python, ça donne

python3 fichier.py arg1 arg2

Accès aux arguments en Python

  • import sys
  • sys.argv

sys.argv est une liste, et son premier argument est toujours le nom du fichier exécuté.

Exemple

# 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']

\center \huge Cas pratique

Squelette

Décodeur de noms d’aéroports

  • 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.

Différentes approches

  • Bottom-up (approche ascendante)
  • Top-Down (approche descendante)

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

def main():
    dico = fabrique_dico()
    code = lire_code()
    nom = trouve_code(code, dico)
    if nom:
        print(nom)
    else:
        affiche_erreur(code)

main()

On a fait comme si le code dont on avait besoin était déjà écrit :)

lire_code()

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:

CDG Paris-Charles de Gaulle
ORY Paris-Orly
NCE Nice-Côte d'Azur
...

Téléchargez-le ici:

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

Clique droit / Enregistrer sous / airports.txt

fabrique_dico() - 2

def fabrique_dico():
    dico = dict()
    fichier = open("airports.txt", "r")
    contenu = fichier.read()
    fichier.close()

    lignes = contenu.splitlines()
    for ligne in lignes:
        code = ligne[0:3]
        nom = ligne[4:]
        dico[code] = nom
    return dico

trouve_code()

def trouve_code(code, dico):
    if code in dico:
        return dico[code]

\vfill

Notez le return None implicite!

affiche_erreur()

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()

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

Place au débat

Quelle approche avez-vous préférée entre bottom-up et top-down?

Pourquoi?