TIL : les Protocol et le typage en Python

Bonjour,

J'apprends encore beaucoup de choses sur Python, et j'ai découvert les Protocol il y a quelques mois. Je voulais l'écrire pour ne pas oublier.

Les Protocol ont été introduits dans Python 3.8 avec la PEP 544. Il existe plusieurs protocoles en Python, mais celui dont je parle est une façon de définir du typage structurel. Certaines personnes appellent aussi ça une interface implicite. On peut utiliser la classe Protocol du module typing. Pour ne pas vous embrouiller davantage, je vais l'expliquer avec une image simple.

Imaginons que vous êtes journaliste et que vous écrivez des articles tech, des notes de la semaine et plein d'autres types de textes ; il y a toujours des choses que vous ferez pour vos publications, quel que soit leur type, par exemple :

Pour être sûr·e de ne pas les oublier, créons un modèle. Ce modèle sera votre protocole.

Avec du vrai code, ça ressemblerait à ceci :

from typing import Protocol

class Publishable(Protocol):

   def publish(self):
      pass

Et voilà, vous pouvez l'utiliser pour n'importe quel type de publication sans héritage. Cela signifie qu'un objet est conforme à un protocole s'il implémente les méthodes requises. Voici un exemple avec la méthode publish :

class Article:
   def __init__(self, title: str):
      self.title = title

   def publish(self) -> None:
      # do something to publish
      pass

La classe Article implémente maintenant le protocole Publishable grâce à l'ajout de la méthode requise publish.

Les avantages et inconvénients sont les suivants :

Avantages

Inconvénients

Je suppose que vous vous dites : super, mais comment j'utilise ce Protocol ? Bonne question !

Les Protocol sont principalement utilisés pour les annotations de type et la vérification statique des types. Cela signifie que vous pouvez utiliser un protocole comme annotation de type pour indiquer qu'un argument ou une variable doit être conforme à un protocole donné.

Si on reprend notre exemple, on obtient le code suivant :

def publish_article(article: Article) -> None:
   article.publish()
   ...

Ici, un seul Protocol est utilisé, mais vous pourriez en utiliser plusieurs.

class Draft(Protocol):

   def mark_as_draft(self):
      # do something to mark as draft
      pass

class DraftAndPublishable(Draft, Publishable):
   pass

Dans cet exemple, on définit un protocole nommé Draft avec une méthode mark_as_draft. Puis on définit un autre protocole nommé DraftAndPublishable qui combine les protocoles Draft et Publishable.

Si vous connaissez les méthodes spéciales __iter__ et __next__ : si vous implémentez les deux, votre objet utilisera le protocole Iterator ✨ Vous pouvez en apprendre plus dans l'article de Real Python sur le protocole Iterator.

Pour conclure, les Protocol permettent de définir du typage structurel en Python, et donc de créer des interfaces sans avoir besoin d'héritage explicite. Ils permettent aux vérificateurs de types de s'assurer que les objets respectent les protocoles définis, ce qui améliore la justesse et la maintenabilité du code.

C'est tout pour le moment ! J'espère que ça sera utile à quelqu'un.