首頁  >  文章  >  後端開發  >  鴨子類型遇到類型提示:在 Python 中使用協議

鴨子類型遇到類型提示:在 Python 中使用協議

WBOY
WBOY原創
2024-07-31 10:40:341088瀏覽

Duck Typing Meets Type Hints: Using Protocols in Python

Python 的動態特性和對鴨子類型的支持長期以來因其靈活性而受到稱讚。然而,隨著程式碼庫變得越來越大、越來越複雜,靜態類型檢查的好處變得越來越明顯。但是我們如何協調鴨子類型的靈活性和靜態類型檢查的安全性呢?進入Python的Protocol類別。

在本教程中,您將學習:

  1. 什麼是鴨子類型以及 Python 中如何支援它
  2. 鴨子打字的優點和缺點
  3. 抽象基底 (ABC) 如何嘗試解決打字問題
  4. 如何使用協議來獲得兩全其美的效果:透過靜態類型檢查實現鴨子類型靈活性

了解鴨子類型

鴨子類型是一種程式設計概念,其中物件的類型或類別不如它定義的方法重要。它基於這樣的想法:「如果它看起來像鴨子,像鴨子一樣游泳,像鴨子一樣嘎嘎叫,那麼它可能就是一隻鴨子。」

在 Python 中,完全支援鴨子類型。例如:

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!

在這個例子中,make_it_quack 不關心事物的類型。它只關心這個東西有一個江湖方法。請注意,thing 參數沒有類型提示,這在鴨子類型程式碼中很常見,但可能會導致較大程式碼庫中出現問題。

鴨子打字的優點和缺點

鴨子打字有幾個優點:

  1. 靈活性:它允許更靈活的程式碼,不依賴特定類型。
  2. 更輕鬆的程式碼重複使用:您可以在新上下文中使用現有的類別而無需修改。
  3. 強調行為:它專注於物件可以做什麼,而不是它是什麼。

但是,它也有一些缺點:

  1. 缺乏清晰度:可能不清楚物件需要實作哪些方法。
  2. 運行時錯誤:僅在運行時捕獲與類型相關的錯誤。
  3. IDE 支援較少:IDE 很難提供準確的自動完成和錯誤檢查。

ABC 解決方案

解決這些問題的一種方法是使用抽象基底類別(ABC)。這是一個例子:

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)

雖然這種方法提供了更好的類型檢查和更清晰的接口,但它也有缺點:

  1. 它需要繼承,這可能會導致不靈活的層次結構。
  2. 它不適用於您無法修改的現有類別。
  3. 這違背了 Python 的「鴨子類型」哲學。

協議:兩全其美

Python 3.8引入了Protocol類,它允許我們定義介面而不需要繼承。以下是我們如何使用它:

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)

讓我們來分解一下:

  1. 我們定義了一個Quacker協定來指定我們期望的介面。
  2. 我們的 Duck 和 Person 類別不需要繼承任何東西。
  3. 我們可以使用 make_it_quack 的類型提示來指定它需要 Quacker。

這個方法為我們帶來了幾個好處:

  1. 靜態類型檢查:IDE 和類型檢查器可以在運作前捕獲錯誤。
  2. 不需要繼承:現有的類別只要有正確的方法就可以工作。
  3. 清晰的介面:協定明確定義了期望的方法。

這是一個更複雜的範例,展示了協議如何根據需要(形狀)變得複雜,同時保持域類(圓形、矩形)平坦:

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)

在此範例中,Circle 和 Rectangle 不繼承自 Shape 或任何其他類別。他們只是實現所需的方法(繪製和調整大小)。由於 Shape 協議,process_shapes 函數可以與任何具有這些方法的物件一起使用。

概括

Python 中的協定提供了一種將靜態類型引入鴨子類型程式碼的強大方法。它們允許我們在類型系統中指定接口,而不需要繼承,保持鴨子類型的靈活性,同時添加靜態類型檢查的好處,

透過使用協議,您可以:

  1. 為您的程式碼定義清晰的介面
  2. 獲得更好的 IDE,(靜態類型檢查),支援並更早捕獲錯誤
  3. 保持鴨子打字的彈性
  4. 利用類型檢查來檢查您無法修改的類別。

如果您想了解有關 Python 中的協議和類型提示的更多信息,請查看有關打字模組的官方 Python 文檔,或探索 mypy 等高級靜態類型檢查工具。

祝你編碼愉快,願你的鴨子總是因為類型安全而嘎嘎叫!

您可以在這裡找到更多我的內容,包括我的電子報

以上是鴨子類型遇到類型提示:在 Python 中使用協議的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn