”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 鸭子类型遇到类型提示:在 Python 中使用协议

鸭子类型遇到类型提示:在 Python 中使用协议

发布于2024-11-08
浏览:643

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]删除
最新教程 更多>
  • 解决Spring Security 4.1及以上版本CORS问题指南
    解决Spring Security 4.1及以上版本CORS问题指南
    弹簧安全性cors filter:故障排除常见问题 在将Spring Security集成到现有项目中时,您可能会遇到与CORS相关的错误,如果像“访问Control-allo-allow-Origin”之类的标头,则无法设置在响应中。为了解决此问题,您可以实现自定义过滤器,例如代码段中的MyFi...
    编程 发布于2025-05-06
  • 如何使用FormData()处理多个文件上传?
    如何使用FormData()处理多个文件上传?
    )处理多个文件输入时,通常需要处理多个文件上传时,通常是必要的。 The fd.append("fileToUpload[]", files[x]); method can be used for this purpose, allowing you to send multi...
    编程 发布于2025-05-06
  • 如何从PHP中的Unicode字符串中有效地产生对URL友好的sl。
    如何从PHP中的Unicode字符串中有效地产生对URL友好的sl。
    为有效的slug生成首先,该函数用指定的分隔符替换所有非字母或数字字符。此步骤可确保slug遵守URL惯例。随后,它采用ICONV函数将文本简化为us-ascii兼容格式,从而允许更广泛的字符集合兼容性。接下来,该函数使用正则表达式删除了不需要的字符,例如特殊字符和空格。此步骤可确保slug仅包含...
    编程 发布于2025-05-06
  • Go web应用何时关闭数据库连接?
    Go web应用何时关闭数据库连接?
    在GO Web Applications中管理数据库连接很少,考虑以下简化的web应用程序代码:出现的问题:何时应在DB连接上调用Close()方法?,该特定方案将自动关闭程序时,该程序将在EXITS EXITS EXITS出现时自动关闭。但是,其他考虑因素可能保证手动处理。选项1:隐式关闭终止数...
    编程 发布于2025-05-06
  • 为什么PHP的DateTime :: Modify('+1个月')会产生意外的结果?
    为什么PHP的DateTime :: Modify('+1个月')会产生意外的结果?
    使用php dateTime修改月份:发现预期的行为在使用PHP的DateTime类时,添加或减去几个月可能并不总是会产生预期的结果。正如文档所警告的那样,“当心”这些操作的“不像看起来那样直观。 ; $ date->修改('1个月'); //前进1个月 echo $ date->...
    编程 发布于2025-05-06
  • 如何使用不同数量列的联合数据库表?
    如何使用不同数量列的联合数据库表?
    合并列数不同的表 当尝试合并列数不同的数据库表时,可能会遇到挑战。一种直接的方法是在列数较少的表中,为缺失的列追加空值。 例如,考虑两个表,表 A 和表 B,其中表 A 的列数多于表 B。为了合并这些表,同时处理表 B 中缺失的列,请按照以下步骤操作: 确定表 B 中缺失的列,并将它们添加到表的末...
    编程 发布于2025-05-06
  • 版本5.6.5之前,使用current_timestamp与时间戳列的current_timestamp与时间戳列有什么限制?
    版本5.6.5之前,使用current_timestamp与时间戳列的current_timestamp与时间戳列有什么限制?
    在时间戳列上使用current_timestamp或MySQL版本中的current_timestamp或在5.6.5 此限制源于遗留实现的关注,这些限制需要对当前的_timestamp功能进行特定的实现。 创建表`foo`( `Productid` int(10)unsigned not n...
    编程 发布于2025-05-06
  • FastAPI自定义404页面创建指南
    FastAPI自定义404页面创建指南
    response = await call_next(request) if response.status_code == 404: return RedirectResponse("https://fastapi.tiangolo.com") else: ...
    编程 发布于2025-05-06
  • Java中假唤醒真的会发生吗?
    Java中假唤醒真的会发生吗?
    在Java中的浪费唤醒:真实性或神话?在Java同步中伪装唤醒的概念已经是讨论的主题。尽管存在这种行为的潜力,但问题仍然存在:它们实际上是在实践中发生的吗? Linux的唤醒机制根据Wikipedia关于伪造唤醒的文章,linux实现了pthread_cond_wait()功能的Linux实现,利用...
    编程 发布于2025-05-06
  • `console.log`显示修改后对象值异常的原因
    `console.log`显示修改后对象值异常的原因
    foo = [{id:1},{id:2},{id:3},{id:4},{id:id:5},],]; console.log('foo1',foo,foo.length); foo.splice(2,1); console.log('foo2', foo, foo....
    编程 发布于2025-05-06
  • Java的Map.Entry和SimpleEntry如何简化键值对管理?
    Java的Map.Entry和SimpleEntry如何简化键值对管理?
    A Comprehensive Collection for Value Pairs: Introducing Java's Map.Entry and SimpleEntryIn Java, when defining a collection where each element com...
    编程 发布于2025-05-06
  • 图片在Chrome中为何仍有边框?`border: none;`无效解决方案
    图片在Chrome中为何仍有边框?`border: none;`无效解决方案
    在chrome 中删除一个频繁的问题时,在与Chrome and IE9中的图像一起工作时,遇到了一个频繁的问题。和“边境:无;”在CSS中。要解决此问题,请考虑以下方法: Chrome具有忽略“ border:none; none;”的已知错误,风格。要解决此问题,请使用以下CSS ID块创建带...
    编程 发布于2025-05-06
  • 可以在纯CS中将多个粘性元素彼此堆叠在一起吗?
    可以在纯CS中将多个粘性元素彼此堆叠在一起吗?
    [2这里: https://webthemez.com/demo/sticky-multi-header-scroll/index.html </main> <section> { display:grid; grid-template-...
    编程 发布于2025-05-06
  • Go语言如何动态发现导出包类型?
    Go语言如何动态发现导出包类型?
    与反射软件包中的有限类型的发现能力相反,本文探索了替代方法,探索了在Runruntime。go import( “ FMT” “去/进口商” ) func main(){ pkg,err:= incorter.default()。导入(“ time”) 如果err...
    编程 发布于2025-05-06
  • Python中嵌套函数与闭包的区别是什么
    Python中嵌套函数与闭包的区别是什么
    嵌套函数与python 在python中的嵌套函数不被考虑闭合,因为它们不符合以下要求:不访问局部范围scliables to incling scliables在封装范围外执行范围的局部范围。 make_printer(msg): DEF打印机(): 打印(味精) ...
    编程 发布于2025-05-06

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3