」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 鴨子類型與類型提示的結合:在 Python 中使用協議

鴨子類型與類型提示的結合:在 Python 中使用協議

發佈於2024-09-04
瀏覽:393

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 等高级静态类型检查工具。

祝你编码愉快,愿你的鸭子总是因类型安全而嘎嘎叫!

您可以在这里找到我的更多内容,包括我的时事通讯

版本聲明 本文轉載於:https://dev.to/samkeen/duck-typing-meets-type-hints-using-protocols-in-python-4d8l?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 為什麼sync.Once使用atomic.StoreUint32而不是標準分配?
    為什麼sync.Once使用atomic.StoreUint32而不是標準分配?
    sync.Once中的原子記憶體排序在探索sync.Once的原始程式碼時,我們偶然發現了使用atomic背後的原因。 StoreUint32 而不是像 o.done = 1 這樣的標準賦值。 Go 中的記憶體排序並發程式設計中的一個基本概念是記憶體排序,它確保共享內存在所有處理器上一致地觀察到存取...
    程式設計 發佈於2024-11-07
  • 為什麼無法在 PHP 中使用匿名函數初始化屬性?
    為什麼無法在 PHP 中使用匿名函數初始化屬性?
    使用匿名函數初始化屬性:為什麼以及如何? 如下面的程式碼片段所述,在類別聲明在 PHP 中觸發「解析錯誤:語法錯誤,意外的 T_FUNCTION」。然而,在建構函數中將函數指派給屬性是可能的,如第二個片段所示。 // Property initialization with anonymous fu...
    程式設計 發佈於2024-11-07
  • [Flatiron SE] 第 24 天
    [Flatiron SE] 第 24 天
    大家好,在這裡吃飯, 這將是我的第一篇部落格文章!我是應熨斗學校出色的老師的要求這樣做的。我將嘗試每天發表一篇部落格文章,記錄我在編碼過程中的發展。您好,歡迎,以及將來閱讀本文的雇主! 現在我需要說實話,這實際上不是我使用熨斗的「第一天」。我實際上是在兩天前的星期一開始的。但我相信今天絕對是我真正...
    程式設計 發佈於2024-11-07
  • 保護 JavaScript 應用程式的安全:常見漏洞以及如何避免它們
    保護 JavaScript 應用程式的安全:常見漏洞以及如何避免它們
    JavaScript 是最流行的 Web 开发语言之一,但由于其广泛使用,它也是攻击者的常见目标。保护 JavaScript 应用程序的安全对于避免可能导致数据被盗、用户帐户受损等安全漏洞至关重要。本文将探讨 JavaScript 应用程序中的一些常见漏洞并提供缓解这些漏洞的策略。 ...
    程式設計 發佈於2024-11-07
  • 為什麼我在 Go 中收到「語法錯誤:Else 之前出現意外的分號」?
    為什麼我在 Go 中收到「語法錯誤:Else 之前出現意外的分號」?
    Go 中Else 之前意外的分號:詳細解釋你的Go 碼在第21 行遇到異常錯誤:「syntax error:unexpected分號或換行符號之前。 這裡的核心問題源自於Go的自動分號插入規則。通常,Go 中分號是可選的;但是,編譯器會自動將它們插入到某些行的末尾以保持語法完整性。其中一個這樣的情況...
    程式設計 發佈於2024-11-07
  • 如何從 Flask 中的 URL 中提取命名參數?
    如何從 Flask 中的 URL 中提取命名參數?
    從Flask 中的URL 中提取命名參數假設您有一個類似http://10.1.1.1:5000/login? username 的URL =alex&password=pw1 您希望Flask 應用程式處理。若要存取問號後指定的參數,請使用 request.args,而不是 request.for...
    程式設計 發佈於2024-11-07
  • 一級抽象:簡潔函數的關鍵
    一級抽象:簡潔函數的關鍵
    曾經在看過一個函數後,感到迷失在它的複雜性中嗎?讓我們探討乾淨程式碼的基本原則:函數應該只維護一個抽象層級。 這是在 Web 應用程式中建立使用者的真實範例: // ❌ A function doing too many things at different abstraction levels...
    程式設計 發佈於2024-11-07
  • 在 JavaScript 中編寫簡潔且可維護的程式碼的最佳實踐
    在 JavaScript 中編寫簡潔且可維護的程式碼的最佳實踐
    干净且可维护的代码对于任何软件项目的长期成功和可扩展性至关重要。它改善了团队成员之间的协作,减少了错误的可能性,并使代码更易于理解、测试和维护。在这篇博文中,我们将探讨一些在 JavaScript 中编写干净且可维护的代码的最佳实践,以及说明每种实践的代码示例。 1. 一致的代码格式...
    程式設計 發佈於2024-11-07
  • 促進軟體開發成功:整合跨學科技能以獲得更好的結果
    促進軟體開發成功:整合跨學科技能以獲得更好的結果
    In today's world of software development, success is more than just technical know-how. Teams that work well together and mix different skills in vari...
    程式設計 發佈於2024-11-07
  • 如何修復Go模組導入過時的套件版本?
    如何修復Go模組導入過時的套件版本?
    Go 模組匯入過時的套件版本當嘗試使用Go 模組將新套件合併到專案中時,您可能會遇到以下問題:儘管該套件被標記為“最新”,但模組系統會檢索該套件的過時版本。這個過時的版本可能缺少程式碼所需的功能,從而導致編譯或執行時錯誤。 解決方案:在 go.mod 檔案中指定版本Go 模組系統可讓您指定要匯入的套...
    程式設計 發佈於2024-11-07
  • WatchYourLAN - 輕量級網路 IP 掃描器
    WatchYourLAN - 輕量級網路 IP 掃描器
    WatchYourLAN的主要特點 找到新主機時發送通知 監控主機線上/離線歷史記錄 保留網路中所有主機的清單 發送資料到InfluxDB2製作Grafana儀表板 v2 有什麼新功能? 基礎API 匯出到 InfluxDB2 SQLite 與 PostgreSQL...
    程式設計 發佈於2024-11-07
  • 掌握 React Native 中的深度連結和通用連結:OpenGraph Share 和 Node.js 集成
    掌握 React Native 中的深度連結和通用連結:OpenGraph Share 和 Node.js 集成
    设想 假设您有一个名为 ShopEasy 的电子商务应用程序,并且您希望点击电子邮件、消息或社交媒体中的产品链接的用户被直接重定向到应用程序中的相关产品页面,而不是网站。 第1步:在nodejs服务器中进行Opengraph配置以进行链接预览: Open Graph...
    程式設計 發佈於2024-11-07
  • 如何在不使用“eval”的情況下安全地解析“寬鬆”JSON?
    如何在不使用“eval”的情況下安全地解析“寬鬆”JSON?
    在沒有風險評估的情況下解析「輕鬆」的JSONJSON 是廣泛使用的資料交換格式,需要帶引號的鍵的嚴格語法。但是,某些應用程式可能會遇到帶有不帶引號的鍵的“寬鬆”JSON。由於安全風險,不鼓勵使用 eval 解析此類資料。 避免邪惡的 Evaleval 的替代方法是基於正則表達式的方法,該方法在解析之...
    程式設計 發佈於2024-11-07
  • 透過sponge+dtm快速輕鬆實現高性能的電商系統
    透過sponge+dtm快速輕鬆實現高性能的電商系統
    This article demonstrates how to use the Sponge framework to quickly build a simplified high-performance e-commerce system, implementing flash sale an...
    程式設計 發佈於2024-11-07
  • 什麼是 Java 中的關閉鉤子以及如何有效地使用它?
    什麼是 Java 中的關閉鉤子以及如何有效地使用它?
    1. 了解關閉鉤子 關閉鉤子是 Java 中的一種特殊構造,允許您註冊一個線程,該線程將在 Java 虛擬機 (JVM) 關閉時執行。這可以由各種事件觸發,例如使用者中斷 (Ctrl C)、系統關閉或編程終止。 1.1 關閉鉤子如何運作 當 JVM 啟動時,它會建...
    程式設計 發佈於2024-11-07

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3