% Programmation avec Python (chapitre 3) % Dimitri Merejkowsky \center \huge None # Jouons avec les dictionnaires ```python >>> scores = { "Anne": 42, "Bernard": 5 } >>> scores["Anne"] 42 >>> scores.get("Anne") 42 >>> scores["Sophie"] KeyError >>> scores.get("Sophie") ``` Que se passe-t-il? # Exprimer l'absence En réalité, `get()` retourne None quand la clé n'est pas présente \vfill ```python >>> a = 42 >>> a 42 >>> b = None >>> b ``` # None est ambigu None est `falsy`, tout comme 0, False et les listes vides: ```python 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`: ```python if clé in dictionnaire: # La clé existe, pas d'erreur valeur = dictionnaire[clé] ``` # Solution 2: tester None Avec `is`: ```python >>> 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: ```python >>> 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 ```python 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 ```python 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 ```python 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: ```python >>> 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: ```python >>> 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] ``` # pop() On a vu la méthode `pop()` pour les dictionnaires, mais elle existe aussi pour les listes: \vfill ```python >>> fruits = ["pomme", "banane"] >>> fruits.pop() # Retourne l'élément 'pomme' >>> fruits # Et modifie la liste ["banane"] ``` # pop() sur liste vide ```python >>> liste_vide = list() >>> liste_vide.pop() IndexError ``` # index() Pour récupérer la position d'un élément: ```python >>> fruits = ["pomme", "banane"] >>> fruits.index("banane") >>> 1 >>> fruits.index("citron") >> ValueError ``` # count() Pour compter le nombre d'occurrences d'un élément ```python >>> fruits = ["pomme", "banane", "pomme", "poire"] >>> fruits.count("pomme") 2 >>> fruits.count("poire") 1 >>> fruits.count("citron") 0 ``` # Indexer des listes Rappel: ```python >>> lettres = ["a", "b", "c", "d", "e"] >>> lettres[0] # ça commence à zéro "a" >>> lettres[4] "e" ``` Mais on peut aussi compter à l'envers: ```python >>> lettres[-1] "e" >>> lettres[-2] "d" ``` # Trancher des listes Ou "faire des slices", ou "slicer". ```python >>> 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 ```python >>> 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 ```python >>> 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". ```python >>> message = "Bonjour, monde !" >>> message[-1] = "?" TypeError ``` ```python >>> message = "Bonjour, monde !" >>> message.clear() AttributeError ``` # \centering \huge La ligne de commande # Schéma ![programme](img/programme.png) # 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 ```bash # Linux, macOS $ python3 code.py $ echo $? 0 ``` ```bash # 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: ```python 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. ```python 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: ```python 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 ```python # 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 ```python 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() ```python 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 ```python 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() ```python def trouve_code(code, dico): if code in dico: return dico[code] ``` \vfill Notez le `return None` implicite! # affiche_erreur() ```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()` ``` 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?