[Python] Protocol
Pour éviter de faire de l’héritage dans tous les sens, on peut parfois se contenter d’utiliser le Protocol.
« Si ça marche comme un canard et que ça fait coin-coin comme un canard, ça doit certainement être un canard«
Supposons qu’on ait une fonction calculate_total
qui permet de faire le total d’une liste de produits, où chaque produit a un nom, une quantité et un prix :
from typing import List
class Product:
def __init__(self, name: str, quantity: float, price: float):
self.name = name
self.quantity = quantity
self.price = price
def calculate_total(items: List[Product]) -> float:
return sum([item.quantity * item.price for item in items])
Dans cet exemple, la fonction calculate_total()
accepte une liste d’objets Product
et renvoie la valeur totale.
Lorsque vous écrivez cette fonction, vous souhaitez peut-être calculer le total d’une liste de produits. Mais vous voudrez probablement l’utiliser à l’avenir pour d’autres listes, comme les listes de stocks.
Si vous regardez de près la fonction calculate_total()
, elle utilise uniquement les attributs quantité et prix.
Pour rendre la fonction calculate_total()
plus dynamique tout en tirant parti des indications de type, vous pouvez utiliser le protocole du module de typage. La classe Protocol
est disponible depuis Python 3.8 et est décrite dans le PEP 544.
Tout d’abord, définissez une classe Item
qui hérite de Protocol
avec deux attributs : quantité et prix :
class Item(Protocol):
quantity: float
price: float
Ensuite, changez la fonction calculate_total()
afin qu’elle prenne une liste de Item
plutôt qu’une liste de Product
:
def calculate_total(items: List[Item]) -> float:
return sum([item.quantity * item.price for item in items])
En faisant ça, on peut passer à cette fonction n’importe quelle liste d’éléments, à partir du moment où ceux-ci respectent le protocole : il faut un attribut quantity
et un attribut price
.
Supposons que nous ayons une classe Stock
. A partir du moment où elle possède les attributs quantity
et price
, elle répond au protocole Item
et peut donc être utilisée dans notre fonction calculate_total()
:
class Stock:
def __init__(self, product_name, quantity, price):
self.product_name = product_name
self.quantity = quantity
self.price = price
# calculate total an inventory list
total = calculate_total([
Stock('Tablet', 5, 950),
Stock('Laptop', 10, 850)
])
print(total)
Dans cet exemple, les classes Product
et Stock
n’ont pas besoin de dériver la classe Item
mais peuvent tout de même être utilisées dans la fonction calculate_total()
.
C’est ce qu’on appelle le « duck typing » en Python. Dans le « duck typing », les comportements et les propriétés d’un objet déterminent le type de l’objet, et non le type explicite de l’objet.
Source : https://www.pythontutorial.net/python-oop/python-protocol/