% Programmation avec Python (chapitre 9) % Dimitri Merejkowsky \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" % age) # 'i' comme integer poids = 68.5 print("Vous pesez %f kilos" % poids) # 'f' comme float ``` # 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 (2016) # 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 a = 2 b = 3 text = f"résultat: {a + b}" ``` # 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 >>> f"pi vaut à peu près {pi:.2f}" pi vaut à peu près 3.14 ``` Le texte dans les accolades après le `:` est un mini-langage de spécification de format. `.2f` veut dire: 2 chiffres après la virgule maximum. Fonctionne aussi avec `.format()` `et %`: ```python "pi vaut à peu près {:.2f}".format(pi) "pi vaut à peu près %.2f" % pi ``` # 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 ```python class Car: total_number_of_cars = 0 def __init__(self, color="black"): self.color = color Car.total_number_of_cars += 1 def drive(self): print("vroom") @classmethod def print_number_of_cars(cls): print(cls.total_number_of_cars, "cars have been made") ``` # Composition ```python class Authorization: def __init__(self, credentials_file): ... self.password = ... class Client: url = "https://exmple.com" def __init__(self, auth) self.auth = auth def make_request(self): password = self.auth.get_password() requests.get(url, password=password) ``` # Héritage - partage des attributs et méthodes ```python class A: def method_in_a(self): self.attribute_in_a = 42 class B(A): def method_in_b(self): self.method_in_a() # ok self.attribute_in_a # ok ``` On dit aussi que A est la classe *de base* et B la classe *dérivée*. # Héritage - ordre de résolution des méthodes ```python class A: def method_in_a(self): pass class B(A): def method_in_b(self): pass >>> a = A() >>> a.method_in_a() # ok >>> a.method_in_b() # error >>> b = B() >>> b.method_in_b() # ok >>> b.method_in_a() # ok ``` # Héritage - ordre de résolution des méthodes ```python class A: def method_in_a(self): pass class B(A): def method_in_b(self): pass >>> a = A() >>> a.method_in_a() # ok >>> a.method_in_b() # error >>> b = B() >>> b.method_in_b() # ok >>> b.method_in_a() # ok ``` # Héritage: surcharge ```python class A: def do_stuff(self): print("A!") class B(A): def do_stuff(self): print("B!") >>> a = A() >>> a.do_stuff() # ok 'A!' >>> b = B() >>> b.do_stuff() 'B!' ``` # Héritage - super() ```python class A: def do_stuff(self): print("A!") class B(A): def do_stuff(self): super().do_stuff() print("B!") >>> a = A() >>> a.do_stuff() # ok 'A!' >>> b = B() >>> b.do_stuff() 'A!' 'B!' ``` # Héritage - super() et \_\_init\_\_ ```python # All animals have a species class Animal: def __init__(self, species): self.species = species # Pets are animals with a name class Pet(Animal): def __init__(self, species, name): super().__init__(species) # <- à ne pas oublier self.name = name # All dogs are pets class Dog(Pet): def __init__(self, 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.marvel_client = 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 argument * 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 à `Game.__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 ![canard vache](img/canard-vache.jpg) # Exemple 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 = FakeClient() game = Game(fake_client) game.play() # Tout marche! ``` # Problème Comment s'assurer que `FakeClient` et `MarvelClient` restent synchronisés? # Solution Une classe *abstraite*: ```python import abc class BaseClient(metaclass=abc.ABCMeta): @abc.abstractmethod def get_all_characters(self): pass @abc.abstractmethod def get_description(self, name): pass ``` On retrouve le `@` au-dessus des méthodes. On reparlera des metaclasses plus tard :) # Utilisation On ne peut pas instancier la classe abstraite directement: ```python >>> client = BaseClient() # Cannot instantiate abstract class BaseClient # with abstract methods # get_all_characters, get_description ``` En revanche on peut en hériter: ```python class MarvelClient(BaseClient): def get_all_characters(self): ... def get_description(self, name): ... ``` À la construction, Python va vérifier que les méthodes abstraites sont bien surchargées. # Conclusion Plein de langages ont un concept d'interface. C'est utile de savoir que les interfaces existent en Python et ça peut rendre le code plus clair. Cela dit, dans le cas de Python c'est complètement *optionnel*. # \center \huge Atelier # Encore un refactoring Résultat sur `git.e2li.org`: `dmerejkowsky/cours-python/sources/marvel/marvel.py` # Pour la prochaine fois: * Créer un compte dévelopeur sur le site de Marvel * Implémenter le jeu! * Consignes: `dmerejkowsky/cours-python/sources/marvel/consignes.md`