% Programmation avec Python (chapitre 9) % Dimitri Merejkowsky
\center \huge Formats et chaînes de caractères
Problème:
\vfill
>>> 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
%
printf
en C par example)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
>>> print("Bonjour %i!" % name)
TypeError: %i format: a number is required, not str
Avec un tuple:
print("Bonjour %s, vous avez %i" % (name, age))
\vfill
Avec un dictionnaire:
data = {"name": "John", "age": 42}
print("Bonjour %(name)s, vous avez %(age)i ans" % data)
Des {}
comme “placeholders” et une méthode sur les strings:
>>> 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.'
On peut aussi nommer les remplacements:
template = "Bonjour, {nom}. La réponse est: {résultat}"
template.format(nom="Ford", résultat=42)
On peut les ordonner:
template = "Bonjour {1}. La réponse est {0}"
template.format(reponse, name)
%
!format()
%
et .format()
On peut mettre du code dans {}
avec la lettre f
devant la chaîne:
name = "Patrick"
score = 42
text = f"Bonjour {name}. Votre score est: {score}"
\vfill
Mais aussi:
a = 2
b = 3
text = f"résultat: {a + b}"
Je vous ai présenté %
et format()
parce que vous risquez d’en voir.
Mais si vous avez le choix, utilisez des f-strings
!
>>> 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 %
:
"pi vaut à peu près {:.2f}".format(pi)
"pi vaut à peu près %.2f" % pi
On peut aussi faire des alignements et du “padding”:
\vfill
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 1003
: rajouter jusqu'à 2 zéros à gauche pour que la taille fasse 3Documentation ici:
htps://docs.python.org/fr/3/library/string.html#format-specification-mini-language
\center \huge Rappels sur les classes
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")
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)
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.
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
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
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!'
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!'
# 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
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
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
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():
...
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:
Pareil avec get_description()
. La méthode doit:
On peut passer à Game.__init__()
n’importe qu’elle classe pourvu qu’elle ait
les bonnes méthodes!
On appelle ça “duck typing”
Définition traditionnelle (pas exacte à mon sens):
\vfill
Meilleure définition:
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!
Comment s’assurer que FakeClient
et MarvelClient
restent synchronisés?
Une classe abstraite:
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 :)
On ne peut pas instancier la classe abstraite directement:
>>> client = BaseClient()
# Cannot instantiate abstract class BaseClient
# with abstract methods
# get_all_characters, get_description
En revanche on peut en hériter:
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.
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
Résultat sur git.e2li.org
:
dmerejkowsky/cours-python/sources/marvel/marvel.py
dmerejkowsky/cours-python/sources/marvel/consignes.md