| @@ -1,7 +1,182 @@ | |||||
| % Programmation avec Python (chapitre 9) | % Programmation avec Python (chapitre 9) | ||||
| % Dimitri Merejkowsky | % Dimitri Merejkowsky | ||||
| \center \huge Rappels | |||||
| \center \huge Formats et chaînes de caractères | |||||
| # 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 ... | |||||
| 3 solutions différentes en Python | |||||
| # Solution 1: À l'ancienne | |||||
| * Avec `%` | |||||
| * Compatible avec les très vieux Pythons | |||||
| * Compatibles avec d'autres langage (`printf` en C par example) | |||||
| # Example 1 | |||||
| ```python | |||||
| name = "John" | |||||
| print("Bonjour %s!" % name) # 's' comme string | |||||
| age = 42 | |||||
| print("Vous avez %i ans" % 42) # 'i' comme integer | |||||
| ``` | |||||
| # Attention aux types: | |||||
| ```python | |||||
| >>> print("Bonjour %i!" % name) | |||||
| TypeError: %i format: a number is required, not str | |||||
| ``` | |||||
| # On peut en mettre plusieurs | |||||
| Avec un tuple: | |||||
| ```python | |||||
| print("Bonjour %s, vous avez %i" % (name, age)) | |||||
| ``` | |||||
| \vfill | |||||
| Avec un dictionnaire: | |||||
| ```python | |||||
| data = {"name": "John", "age": 42} | |||||
| print("Bonjour %(name)s, vous avez %(age)i ans" % data) | |||||
| ``` | |||||
| # Solution 2: format() | |||||
| Des `{}` comme "placeholders" et une *méthode* sur les strings: | |||||
| ```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.' | |||||
| ``` | |||||
| * Pas de types! | |||||
| # format() - à plusieurs | |||||
| On peut aussi nommer les remplacements: | |||||
| ```python | |||||
| template = "Bonjour, {nom}. La réponse est: {résultat}" | |||||
| template.format(nom="Ford", résultat=42) | |||||
| ``` | |||||
| # format() - à plusieurs | |||||
| On peut les ordonner: | |||||
| ```python | |||||
| template = "Bonjour {1}. La réponse est {0}" | |||||
| template.format(reponse, name) | |||||
| ``` | |||||
| * Pas possible avec `%`! | |||||
| # Solution 3: f-strings | |||||
| * La meilleure de toutes :) | |||||
| * Plus succint que `format()` | |||||
| * Plus performant que `%` et `.format()` | |||||
| * Mais pas avant Python 3.6 | |||||
| # Principe | |||||
| On peut mettre du *code* dans `{}` avec la lettre `f` devant la chaîne: | |||||
| ```python | |||||
| name = "Patrick" | |||||
| score = 42 | |||||
| text = f"Bonjour {name}. Votre score est: {score}" | |||||
| ``` | |||||
| \vfill | |||||
| Mais aussi: | |||||
| ```python | |||||
| text = f"2 + 2 égale { 2 + 2 }" | |||||
| ``` | |||||
| # Conclusion | |||||
| Je vous ai présenté `%` et `format()` parce que vous risquez d'en voir. | |||||
| Mais si vous avez le choix, utilisez des `f-strings`! | |||||
| # Spécifications de formats | |||||
| * Permet des opérations pendant la conversion en texte | |||||
| * Fonctionne avec les 3 solutions | |||||
| # Tronquer | |||||
| ```python | |||||
| >>> pi = 3.14159265359 | |||||
| >>> truncated = "%.2f" % pi | |||||
| 3.14 | |||||
| ``` | |||||
| Le texte dans les accolades après le `:` est un mini-langage de spécification de format. | |||||
| `.2` veut dire: 2 chiffres après la virgule maximum. | |||||
| # Alignements et paddings | |||||
| 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 | |||||
| ``` | |||||
| * `>10` : aligné à gauche, taille minimum 10 | |||||
| * `03`: rajouter jusqu'à 2 zéros à gauche pour que la taille fasse 3 | |||||
| # Documentation | |||||
| Documentation ici: | |||||
| htps://docs.python.org/fr/3/library/string.html#format-specification-mini-language | |||||
| # | |||||
| \center \huge Rappels sur les classes | |||||
| # Classes | # Classes | ||||
| @@ -37,7 +212,7 @@ class Client: | |||||
| self.auth = auth | self.auth = auth | ||||
| def make_request(self): | def make_request(self): | ||||
| password = self.auth.password | |||||
| password = self.auth.get_password() | |||||
| requests.get(url, password=password) | requests.get(url, password=password) | ||||
| ``` | ``` | ||||
| @@ -156,3 +331,116 @@ class Dog(Pet): | |||||
| def __init__(self, name): | def __init__(self, name): | ||||
| super().init("dog", name) | super().init("dog", name) | ||||
| ``` | ``` | ||||
| # | |||||
| \center \huge Interfaces et classes abstraites | |||||
| # Example | |||||
| Imaginons un jeu où il faut retrouver le nom d'un super-héros à partir | |||||
| de sa description. | |||||
| On a une classe `MarvelClient` qui permet de lister les personnages et leurs | |||||
| descriptions et une class `Game` pour la logique du jeu | |||||
| # Implémentation - MarvelClient | |||||
| ```python | |||||
| class MarvelClient: | |||||
| url = "https://marvel.com/api" | |||||
| def __init__(self, credentials_file): | |||||
| # réupére les clés depuis un fichier | |||||
| def get_all_characters(self): | |||||
| # appelle l'api marvel pour récupérer | |||||
| # tous les personnages | |||||
| def get_description(self, character_name) | |||||
| # appelle l'api marvel pour récupérer | |||||
| # une description | |||||
| ``` | |||||
| # Implémentation - Game | |||||
| ```python | |||||
| import random | |||||
| class Game: | |||||
| def __init__(self, marvel_client) | |||||
| self.auth = marvel_client | |||||
| def play(self): | |||||
| characters = self.marvel_client.get_all_characters() | |||||
| name_to_guess = random.choice(characters) | |||||
| description = self.marvel_client.get_description( | |||||
| name_to_guess) | |||||
| while not self.won(): | |||||
| ... | |||||
| ``` | |||||
| # Contrats implicites - 1 | |||||
| Il y a un *contrat implicite* entre `Game` et `MarvelClient`. | |||||
| Dans `play` on appelle `self.marvel_client.get_all_characters()` donc la méthode | |||||
| `get_all_characters()` doit: | |||||
| * exister | |||||
| * ne prendre aucun agrument | |||||
| * retourner une liste de noms | |||||
| # Contrats implicites - 2 | |||||
| Pareil avec `get_description()`. La méthode doit: | |||||
| * exister | |||||
| * prendre un nom en unique argument | |||||
| * retourner une description | |||||
| # Une force et une faiblesse | |||||
| On peut passer à `Client.__init__()` n'importe qu'elle classe pourvu qu'elle ait | |||||
| les bonnes méthodes! | |||||
| On appelle ça "duck typing" | |||||
| # duck typing | |||||
| Définition traditionnelle (pas exacte à mon sens): | |||||
| * Si ça marche comme un canard et que ça fait coin-coin comme un canard alors c'est un canard. | |||||
| \vfill | |||||
| Meilleure définition: | |||||
| * Tu peux passer remplacer le canard par une vache. Tant que la vache a un bec et fait coin-coin, c'est bon! | |||||
| # En image | |||||
|  | |||||
| # Exmaple utile | |||||
| ```python | |||||
| class FakeCLient(): | |||||
| def get_all_characters(self): | |||||
| return ["Spider-Man", "Batman", "Superman"] | |||||
| def get_description(self, name): | |||||
| if name == "Spider-Man": | |||||
| ... | |||||
| ... | |||||
| fake_client = FakeMarvelClient() | |||||
| game = Game(fake_client) | |||||
| game.play() | |||||
| # Tout marche! | |||||
| ``` | |||||