You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 
 
 
 
 

9.6 KiB

% 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

>>> 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

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:

>>> print("Bonjour %i!" % name)
TypeError: %i format: a number is required, not str

On peut en mettre plusieurs

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)

Solution 2: format()

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.'
  • Pas de types!

format() - à plusieurs

On peut aussi nommer les remplacements:

template = "Bonjour, {nom}. La réponse est: {résultat}"
template.format(nom="Ford", résultat=42)

format() - à plusieurs

On peut les ordonner:

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:

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}"

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

>>> 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

Alignements et paddings

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 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

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

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

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

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

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

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()

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__

# 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

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

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

Exemple utile

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:

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:

>>> 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.

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