Python中的不透明类型

Hacker News Top 工具

摘要

文章解释了在Python中使用typing.NewType来隐藏内部实现细节的不透明数据类型模式,并通过ShippingOptions示例展示了如何提供最小化的公共接口。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/05/26 15:55

# Python 中的不透明类型 来源:https://blog.glyph.im/2026/05/opaque-types-in-python.html 假设你正在编写一个 Python 库。 在这个库中,你有一些状态集合,代表一系列操作的"选项"或"配置"。这样一组选项是一个可能变得越来越复杂的捆绑包。因此,你需要它拥有极其最小化的兼容性表面,并精心选择公共接口,要么很小,要么甚至什么都没有。这样的对象传递状态,并可能包含一些私有行为,但你希望用户只能以非常受限、特定的方式构建它,然后将其作为参数传递给你的 API。 举个例子,想象你正在封装一个处理实体包裹运输的库。 运输包裹的方式有无数种。有不同的承运商可以为你运送。有空运、陆运、海运。有隔夜快递。有需要签收的选项。还有包裹跟踪和挂号信。总之,东西很多。 如果你刚开始实现这样的库,你可能需要一个名为 `ShippingOptions` 的对象来封装其中一些内容。在你的库核心,你可能有一个这样的函数: ``` async def shipPackage( how: ShippingOptions, where: Address, ) -> ShippingStatus: ... ``` 如果你 *刚开始* 实现这样的库,你知道你对 `ShippingOptions` 的初始实现会是错的;或者,至少不能说"错",但"不完整"。在你对问题领域有足够深入的理解之前,你不应该承诺一个拥有大量不同属性的庞杂公共 API。 然而,`ShippingOptions` 对你库的其余部分至关重要。你需要构造它并传递给各种方法,如 `estimateShippingCost` 和 `shipPackage`。因此,你肯定不希望在你把它变得复杂的过程中出现大量的复杂性和变动。 更糟糕的是,这个对象必须持有大量状态。它有一些属性,甚至可能是相当复杂的内部属性,与不同的运输服务相关。 现在,*今天*,你需要添加 *某些东西*,以便你拥有"不急"、"标准"和"加急"选项。你不能无限期地推迟实现,直到想出一个完美的形状。怎么办? 你需要的工具是 *不透明数据类型* 设计模式。C 语言里充斥着这样的东西(`FILE`、`pthread_*_t`、`fd_set` 等)。头文件中的 `typedef` 很容易实现这一点。 但在 Python 中,如果你暴露一个 `dataclass`——实际上 *任何* 类——即使你保持所有字段私有,*构造函数* 本质上依然是公开的。你可以让它抛出异常之类的,但你的类型检查器仍然不会帮到你的用户;它看起来仍然像一个普通类。 幸运的是,Python 类型提示提供了一个工具:`typing.NewType` (https://docs.python.org/3.14/library/typing.html#newtype)。 让我们回顾一下需求: 1. 我们需要一个类型,客户端代码可以在其类型注解中使用;它需要是公开的。 2. 用户需要能够 *以某种方式* 构造它,即使他们不应该看到它的属性或其内部构造函数参数。 3. 表达高层次的概念(比如"快速运输"),这些概念在未来当我们添加更细致、更复杂的配置时(比如"用支持签名验证的最低成本承运商提供的最快选项运输")仍应得到支持。 为了分别解决这些问题,我们将使用: 1. 一个 *公开的 `NewType`*,它提供我们的公开名称…… 2. 它包装一个 *私有类*,该类具有完全私有的属性,以给我们一个实际的数据结构,同时不暴露构造函数, 3. 一组公开的构造函数,返回我们的 `NewType`。 将它们组合在一起,看起来像这样: ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ``` ``` from dataclasses import dataclass from typing import Literal, NewType @dataclass class _RealShipOpts: _speed: Literal["fast", "normal", "slow"] ShippingOptions = NewType("ShippingOptions", _RealShipOpts) def shipFast() -> ShippingOptions: return ShippingOptions(_RealShipOpts("fast")) def shipNormal() -> ShippingOptions: return ShippingOptions(_RealShipOpts("normal")) def shipSlow() -> ShippingOptions: return ShippingOptions(_RealShipOpts("slow")) ``` 从时间快照来看,这并不那么有趣;我们本可以直接将 `_RealShipOpts` 作为公共类暴露,从而节省一些时间。它暴露一个接受字符串的构造函数在当前时刻并不是什么大问题。对于一个初步的快速实现,我们可以在运输和估算代码中做类似 `if options._speed == "fast"` 这样的检查。 然而,我们在这里做的主要事情是保持灵活性,以便将来能够演化相关的 API,所以让我们看看如何做到这一点。例如,让运输选项包含具体的承运商和货运方式: ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 ``` ``` from dataclasses import dataclass from enum import Enum, auto from typing import NewType class Carrier(Enum): FedEx = auto() USPS = auto() DHL = auto() UPS = auto() class Conveyance(Enum): air = auto() truck = auto() train = auto() @dataclass class _RealShipOpts: _carrier: Carrier _freight: Conveyance ShippingOptions = NewType("ShippingOptions", _RealShipOpts) def shipFast() -> ShippingOptions: return ShippingOptions(_RealShipOpts(Carrier.FedEx, Conveyance.air)) def shipNormal() -> ShippingOptions: return ShippingOptions(_RealShipOpts(Carrier.UPS, Conveyance.truck)) def shipSlow() -> ShippingOptions: return ShippingOptions(_RealShipOpts(Carrier.USPS, Conveyance.train)) def shippingDetailed( carrier: Carrier, conveyance: Conveyance ) -> ShippingOptions: return ShippingOptions(_RealShipOpts(carrier, conveyance)) ``` 作为一个 `NewType`,我们的公开 `ShippingOptions` 类型没有构造函数。由于 `_RealShipOpts` 是私有的,并且它的所有属性都是私有的,我们可以完全移除旧版本。 任何 *在* 我们的运输库 *内部* 的代码仍然可以访问 `ShippingOptions` 上的私有变量;作为一个 `NewType`,它在运行时与它的基础类型是同一类型,因此它带来极小的开销¹ (https://blog.glyph.im/2026/05/opaque-types-in-python.html#fn:1:opaque-types-in-python-2026-5)。 *在* 我们运输库 *外部* 的客户端仍然可以调用我们所有的公共构造函数:`shipFast`、`shipNormal` 和 `shipSlow` 都以(就调用代码所知)相同的签名和行为工作。 如果你需要在公共 API 中构建和传递某些状态,同时避免与兼容性变动相关的破坏,希望这个技术能对你有所帮助! --- ## 致谢 感谢阅读,也感谢我的赞助人 (https://blog.glyph.im/pages/patrons.html) 支持我在这篇博客上的写作。如果你喜欢这里读到的内容,并且想阅读更多,或者你想支持我的各种开源项目 (https://github.com/glyph/),你可以作为赞助者支持我的工作 (https://blog.glyph.im/pages/patrons.html)。

相似文章

Data types à la carte (2008)

Lobsters Hottest

本文提出了一种从独立组件组合数据类型和函数的技术,并将该方法扩展到结合自由单子,从而实现了对Haskell的IO单子的模块化结构。

PyCon US 2026 类型峰会回顾

Lobsters Hottest

本文回顾了 PyCon US 2026 类型峰会,详细介绍了关于 Python 类型化进展的关键演讲,包括 PEP 提案、AI 辅助类型检查实验以及类型委员会问答环节。

纵深防御:Python供应链安全实用指南

Lobsters Hottest

一份关于通过分层防御保护Python供应链的实用指南,包括使用Ruff进行代码检查、使用哈希锁定依赖项、使用pip-audit进行漏洞扫描、生成SBOM以及使用OIDC证明的可信发布。

静态类型与铲子(2026)

Lobsters Hottest

作者认为,2010年代静态类型编程的复兴归功于改进的类型系统(例如 TypeScript、Haskell、Rust),这些系统提供了可空类型处理、和类型以及类型推断,与 Java 和 C++98 等早期语言中糟糕的静态类型形成对比。