Maison  >  Article  >  développement back-end  >  Duck Typing rencontre les indices de type : utilisation de protocoles en Python

Duck Typing rencontre les indices de type : utilisation de protocoles en Python

WBOY
WBOYoriginal
2024-07-31 10:40:341088parcourir

Duck Typing Meets Type Hints: Using Protocols in Python

La nature dynamique de Python et la prise en charge du typage canard ont longtemps été louées pour leur flexibilité. Cependant, à mesure que les bases de code deviennent plus volumineuses et plus complexes, les avantages de la vérification de type statique deviennent de plus en plus évidents. Mais comment concilier la flexibilité du typage canard et la sécurité de la vérification de type statique ? Entrez dans la classe Protocol de Python.

Dans ce tutoriel, vous apprendrez :

  1. Qu'est-ce que le typage canard et comment il est pris en charge en Python
  2. Les avantages et les inconvénients de la frappe au canard
  3. Comment les classes de base abstraites (ABC) tentent de résoudre les problèmes de frappe
  4. Comment utiliser le protocole pour obtenir le meilleur des deux mondes : flexibilité de frappe avec vérification de type statique

Comprendre la frappe du canard

Le typage Duck est un concept de programmation où le type ou la classe d'un objet est moins important que les méthodes qu'il définit. C'est basé sur l'idée que "S'il ressemble à un canard, nage comme un canard et cancane comme un canard, alors c'est probablement un canard."

En Python, la saisie en canard est entièrement prise en charge. Par exemple :

class Duck:
    def quack(self):
        print("Quack!")

class Person:
    def quack(self):
        print("I'm imitating a duck!")

def make_it_quack(thing):  # Note: No type hint here
    thing.quack()

duck = Duck()
person = Person()

make_it_quack(duck)    # Output: Quack!
make_it_quack(person)  # Output: I'm imitating a duck!

Dans cet exemple, make_it_quack ne se soucie pas du type de chose. Il s'en soucie seulement que cette chose ait une méthode de charlatan. Notez qu'il n'y a pas d'indice de type pour le paramètre thing, ce qui est typique dans le code typé canard mais peut entraîner des problèmes dans des bases de code plus volumineuses.

Avantages et inconvénients de la frappe au canard

Le typage canard offre plusieurs avantages :

  1. Flexibilité : cela permet un code plus flexible qui n'est pas lié à des types spécifiques.
  2. Réutilisation plus facile du code : vous pouvez utiliser les classes existantes dans de nouveaux contextes sans modification.
  3. L'accent est mis sur le comportement : il se concentre sur ce qu'un objet peut faire, plutôt que sur ce qu'il est.

Cependant, il présente également quelques inconvénients :

  1. Manque de clarté : il peut être difficile de savoir quelles méthodes un objet doit implémenter.
  2. Erreurs d'exécution : les erreurs liées au type ne sont détectées qu'au moment de l'exécution.
  3. Moins de prise en charge des IDE : les IDE ont du mal à fournir une saisie semi-automatique et une vérification des erreurs précises.

La solution ABC

Une approche pour résoudre ces problèmes consiste à utiliser des classes de base abstraites (ABC). Voici un exemple :

from abc import ABC, abstractmethod

class Quacker(ABC):
    @abstractmethod
    def quack(self):
        pass

class Duck(Quacker):
    def quack(self):
        print("Quack!")

class Person(Quacker):
    def quack(self):
        print("I'm imitating a duck!")

def make_it_quack(thing: Quacker):
    thing.quack()

duck = Duck()
person = Person()

make_it_quack(duck)
make_it_quack(person)

Bien que cette approche fournisse une meilleure vérification de type et des interfaces plus claires, elle présente des inconvénients :

  1. Cela nécessite un héritage, ce qui peut conduire à des hiérarchies inflexibles.
  2. Cela ne fonctionne pas avec les classes existantes que vous ne pouvez pas modifier.
  3. Cela va à l'encontre de la philosophie du "duck typing" de Python.

Protocoles : le meilleur des deux mondes

Python 3.8 a introduit la classe Protocol, qui nous permet de définir des interfaces sans nécessiter d'héritage. Voici comment nous pouvons l'utiliser :

from typing import Protocol

class Quacker(Protocol):
    def quack(self):...

class Duck:
    def quack(self):
        print("Quack!")

class Person:
    def quack(self):
        print("I'm imitating a duck!")

def make_it_quack(thing: Quacker):
    thing.quack()

duck = Duck()
person = Person()

make_it_quack(duck)
make_it_quack(person)

Décomposons cela :

  1. Nous définissons un protocole Quacker qui précise l'interface que nous attendons.
  2. Nos classes Duck et Person n'ont pas besoin d'hériter de quoi que ce soit.
  3. Nous pouvons utiliser des indices de type avec make_it_quack pour spécifier qu'il attend un Quacker.

Cette approche nous apporte plusieurs avantages :

  1. Vérification de type statique : les IDE et les vérificateurs de type peuvent détecter les erreurs avant l'exécution.
  2. Aucun héritage requis : les classes existantes fonctionnent tant qu'elles disposent des bonnes méthodes.
  3. Interfaces claires : le protocole définit clairement les méthodes attendues.

Voici un exemple plus complexe montrant comment les protocoles peuvent être aussi complexes que nécessaire (forme), en gardant vos classes de domaine (cercle, rectangle) plates :

from typing import Protocol, List

class Drawable(Protocol):
    def draw(self): ...

class Resizable(Protocol):
    def resize(self, factor: float): ...

class Shape(Drawable, Resizable, Protocol):
    pass

def process_shapes(shapes: List[Shape]):
    for shape in shapes:
        shape.draw()
        shape.resize(2.0)

# Example usage
class Circle:
    def draw(self):
        print("Drawing a circle")

    def resize(self, factor: float):
        print(f"Resizing circle by factor {factor}")

class Rectangle:
    def draw(self):
        print("Drawing a rectangle")

    def resize(self, factor: float):
        print(f"Resizing rectangle by factor {factor}")

# This works with any class that has draw and resize methods,
# regardless of its actual type or inheritance
shapes: List[Shape] = [Circle(), Rectangle()]
process_shapes(shapes)

Dans cet exemple, Circle et Rectangle n'héritent pas de Shape ou de toute autre classe. Ils implémentent simplement les méthodes requises (dessiner et redimensionner). La fonction process_shapes peut fonctionner avec tous les objets dotés de ces méthodes, grâce au protocole Shape.

Résumé

Les protocoles en Python fournissent un moyen puissant d'apporter le typage statique au code typé canard. Ils nous permettent de spécifier des interfaces dans le système de types sans nécessiter d'héritage, en conservant la flexibilité du typage duck tout en ajoutant les avantages de la vérification de type statique,

En utilisant les protocoles, vous pouvez :

  1. Définissez des interfaces claires pour votre code
  2. Obtenez un meilleur IDE (vérification de type statique), supportez et détectez les erreurs plus tôt
  3. Maintenir la flexibilité de la frappe au canard
  4. Exploitez la vérification de type pour les classes que vous ne pouvez pas modifier.

Si vous souhaitez en savoir plus sur les protocoles et les indications de type en Python, consultez la documentation officielle de Python sur le module de saisie ou explorez les outils avancés de vérification de type statique comme mypy.

Bon codage, et que vos canards cancanent toujours avec la sécurité des types !

Vous pouvez trouver plus de mon contenu, y compris ma newsletter ici

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn