|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538 |
- % 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 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 (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`
|