Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
Tento repozitář je archivovaný. Můžete prohlížet soubory, klonovat, ale nemůžete nahrávat a vytvářet nové úkoly a požadavky na natažení.
 
 
 
 
 
 

9.1 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" % 42)  # 'i' comme integer

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

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:

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

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

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

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 method(self):
        print("A!")

class B(A):
    def method(self):
        print("B!")

>>> a = A()
>>> a.method() # ok
'A!'

>>> b = B()
>>> b.method()
'B!'

Héritage - super()

class A:
    def method(self):
        print("A!")

class B(A):
    def method(self):
        super().method()
        print("B!")

>>> a = A()
>>> a.method() # ok
'A!'
>>> b = B()
>>> b.method()
'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.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

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


>>> client = BaseClient()

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

Pistes de réflexion

  • Que se passe-t-il si on rajoute une méthode dans BaseClient sans toucher à MarvelClient ni à FakeClient ?
  • Que se passe-t-il si la signature de get_description() change?

Conclusion

Plein de languages ont un concept d’interface. C’est bien utile de savoir, les définir en Python, même si ça ne résout pas tous les problèmes.

\center \huge Atelier

Encore un refactoring

Pour la prochaine fois:

  • Créer un compte dévelopeur sur le site de Marvel
  • Implémenter le jeu!