news 2026/9/25 3:31:52

Hypothesis 策略的类型提示(Type Hints)完整指南:SearchStrategy、composite 与协变语义

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hypothesis 策略的类型提示(Type Hints)完整指南:SearchStrategy、composite 与协变语义
  • 测试
  • 开发工具

【免费下载链接】hypothesis

The property-based testing library for Python

项目地址:https://gitcode.com/gh_mirrors/hy/hypothesis
点击查看免费下载

本指南以 Hypothesis 官方文档 type-strategies.rst 为核心,系统讲解如何为基于 Hypothesis 的属性测试(property-based testing)编写类型提示:从SearchStrategy[T]泛型的基础用法、st.composite装饰器下正确标注返回值类型,到SearchStrategy的协变(covariance)语义及其对类型检查器推断的影响。读完本文,你将掌握为自定义策略函数和组合策略编写准确、可被 mypy / Pyright / Pyre 等类型检查工具验证的类型签名,让策略代码享受完整的静态类型保障。所有结论均有当前仓库源码佐证。

为什么策略需要类型提示

Hypothesis 为所有策略以及所有返回策略的函数提供了类型提示(type hints)。这意味着从hypothesis.strategies(常简写为st)导入的每个策略构造器——如st.integers()、st.lists()——其返回值的类型都能被静态类型检查器精确识别。

from hypothesis import strategies as st reveal_type(st.integers()) # SearchStrategy[int] reveal_type(st.lists(st.integers())) # SearchStrategy[list[int]]

reveal_type是 mypy、Pyright、Pyre 等类型检查器提供的诊断函数:它会直接在错误输出中打印表达式的推断类型。上述两行揭示出:

  • st.integers()返回SearchStrategy[int],即生成整数值的策略;
  • st.lists(st.integers())返回SearchStrategy[list[int]],即生成整数列表的策略。

这些类型提示的实现位于仓库的 strategies/init.py(SearchStrategy等公共符号在此重新导出,源码注释明确写道“The implementation of all of these lives in_strategies.py,but we re-export them via this module to avoid exposing implementation details”),真正带类型参数的定义在 strategies/_internal/strategies.py 与 strategies/_internal/core.py 中。

SearchStrategy:策略的类型

SearchStrategy是策略的类型,定义于 strategies/_internal/strategies.py#L255-L261。它是泛型(generic)的:类型参数即为该策略所生成值的类型。该类在源码 docstring 中明确说明:

"ASearchStrategytells Hypothesis how to generate that kind of input. This class is only part of the public API for use in type annotations, so that you can write e.g.-> SearchStrategy[Foo]for your function which returnsbuilds(Foo, ...). Do not inherit from or directly instantiate this class."

即:SearchStrategy只用于类型标注,不要继承或直接实例化它。

一个典型用法是给“返回策略的函数”写返回类型注解:

from hypothesis import strategies as st from hypothesis.strategies import SearchStrategy # returns a strategy for "normal" numbers def numbers() -> SearchStrategy[int | float]: return st.integers() | st.floats(allow_nan=False, allow_infinity=False)

这里的int | float是 Python 3.10+ 的联合类型(union type)写法。st.integers() | st.floats(...)使用的是策略的|运算符(等价于st.one_of(...)),它把两个策略组合成一个能同时生成整数与有限浮点数的策略,因此返回类型标注为SearchStrategy[int | float]完全匹配。

策略(strategy)与返回策略的函数(function)的区别

官方文档特意强调了一个容易混淆的点:区分“策略”和“返回策略的函数”。

  • st.integers是一个函数,调用后返回一个策略;因此st.integers的类型是Callable[..., SearchStrategy[int]];
  • 而s = st.integers()中的s是值,其类型是SearchStrategy[int]。

这一区别在源码签名上体现得淋漓尽致。看 numbers.py#L123-L154 中integers的真实签名:

def integers( min_value: int | None = None, max_value: int | None = None, ) -> SearchStrategy[int]:

它接收可选的min_value/max_value边界参数,返回SearchStrategy[int]。再看floats的签名(numbers.py#L281-L291):

def floats( min_value: Real | None = None, max_value: Real | None = None, *, allow_nan: bool | None = None, allow_infinity: bool | None = None, allow_subnormal: bool | None = None, width: Literal[16, 32, 64] = 64, exclude_min: bool = False, exclude_max: bool = False, ) -> SearchStrategy[float]:

可见每个策略构造器的返回类型都被精确标注为SearchStrategy[具体值类型],这就是类型检查器能够精准推断st.integers()结果为SearchStrategy[int]的根本原因。

其他常见策略的返回类型同样精确,例如:

  • st.lists(element)→SearchStrategy[list[T]](见 core.py#L305)
  • st.builds(target, ...)→SearchStrategy[target类型](见 core.py#L1184)
  • st.from_type(thing)→SearchStrategy[T],其中T与传入的类型绑定(见 core.py#L1273)

st.composite 下的类型提示写法

当使用@st.composite装饰器定义自定义策略时,类型提示的写法有一个关键规则:标注返回值的类型,而不是SearchStrategy。

@st.composite def ordered_pairs(draw) -> tuple[int, int]: n1 = draw(st.integers()) n2 = draw(st.integers(min_value=n1)) return (n1, n2)

这里函数签名写的是-> tuple[int, int],即组合策略最终产出的值的类型。@st.composite装饰器会自动把它包装成一个返回SearchStrategy[tuple[int, int]]的函数——这一包装逻辑正是源码中composite的真实实现(core.py#L2174-L2189):

if typing.TYPE_CHECKING or ParamSpec is not None: P = ParamSpec("P") def composite( f: Callable[Concatenate[DrawFn, P], Ex], ) -> Callable[P, SearchStrategy[Ex]]: return _composite(f)

注意composite的类型签名:它接收一个Callable[Concatenate[DrawFn, P], Ex]——即第一个参数是DrawFn类型(draw),其余参数为P,返回值类型为Ex(也就是你标注的返回类型)——并返回Callable[P, SearchStrategy[Ex]]。所以当你在函数上写-> tuple[int, int]时,Ex = tuple[int, int],装饰器返回的新函数类型就是SearchStrategy[tuple[int, int]]的构造器。

DrawFn协议(Protocol)同样定义在源码中(core.py#L2053-L2079),其 docstring 明确写道:

"This type only exists so that you can write type hints for functions decorated with@composite."

它的签名是:

def __call__(self, strategy: SearchStrategy[Ex], label: object = None) -> Ex:

这意味着在@st.composite函数内部,draw(st.integers())会被推断为返回int,draw(st.text())被推断为返回str——draw的返回类型自动等于所传策略的值类型。源码 docstring 中给出了示例:

@composite def list_and_index(draw: DrawFn) -> tuple[int, str]: i = draw(integers()) # type of `i` inferred as 'int' s = draw(text()) # type of `s` inferred as 'str' return i, s

底层包装:CompositeStrategy

@st.composite的底层实现会创建一个CompositeStrategy(见 core.py#L2036-L2050):

class CompositeStrategy(SearchStrategy): def __init__(self, definition, args, kwargs): super().__init__() self.definition = definition self.args = args self.kwargs = kwargs def do_draw(self, data): return self.definition(data.draw, *self.args, **self.kwargs)

它在执行do_draw时把 ConjectureData 的draw方法作为第一个参数传给原函数,这正是你在@st.composite函数中接收的draw参数的来源。这一实现印证了:类型层面DrawFn协议对应运行时真正的data.draw调用接口,两者一一对应。

补充:给 draw 参数也加上类型

虽然官方示例中draw参数未加注解,但为了更好的类型检查体验,可以显式标注:

from hypothesis.strategies import DrawFn @st.composite def ordered_pairs(draw: DrawFn) -> tuple[int, int]: n1 = draw(st.integers()) n2 = draw(st.integers(min_value=n1)) return (n1, n2)

DrawFn可以从hypothesis.strategies导入(已在 strategies/init.py 的__all__中导出,见该文件第 68 行)。这样draw(...)的返回值类型就能被静态推断,n1、n2都是int,进而min_value=n1的传参也能通过类型检查。

SearchStrategy 的协变性(Covariance)

含义

SearchStrategy是协变(covariant)的,即:如果B < A(B 是 A 的子类型),那么SearchStrategy[B] < SearchStrategy[A](SearchStrategy[B]是SearchStrategy[A]的子类型)。

用官方文档的例子:策略st.from_type(Dog)是策略st.from_type(Animal)的子类型(其中Dog继承自Animal)。这符合直觉——凡是能生成Animal的地方,都能接受一个只生成Dog的策略。

源码证据

协变语义在 strategies/_internal/strategies.py#L64-L67 中通过TypeVar的covariant=True参数实现:

if TYPE_CHECKING: Ex = TypeVar("Ex", covariant=True, default=Any) else: Ex = TypeVar("Ex", covariant=True) class SearchStrategy(Generic[Ex]): # L255

Ex声明为covariant=True的TypeVar,SearchStrategy继承自Generic[Ex]。当类型检查器看到SearchStrategy[Dog]与SearchStrategy[Animal]时,就能依据Dog < Animal推导出前者是后者的子类型。

协变在实践中的价值

协变让策略可以在函数参数与返回位置灵活替换。例如,下面这个接受“任意动物策略”的函数:

def run_experiment(animals: SearchStrategy[Animal]) -> None: ... run_experiment(st.from_type(Dog)) # OK:协变,SearchStrategy[Dog] 可传给 SearchStrategy[Animal] run_experiment(st.from_type(Animal)) # OK

如果把SearchStrategy设计成不变(invariant)的,则run_experiment(st.from_type(Dog))会直接报类型错误,即使从语义上完全合理。正是协变设计让这种直观的用法得以通过类型检查。

需要说明的是:SearchStrategy的协变是在类型层面由TypeVar(covariant=True)声明的性质;类型检查器(mypy、Pyright、Pyre 等)在静态分析时依据该声明进行子类型推断,运行时并不存在子类型关系的强制检查。

类型提示的验证与测试保障

Hypothesis 仓库自身就用测试保障了这些类型提示的准确性。例如 tests/cover/test_annotations.py 中有如下断言(第 99 行附近):

assert sig_comp.return_annotation == st.SearchStrategy[int]

它验证组合策略函数签名的返回注解确实是st.SearchStrategy[int]。此外,仓库还维护了whole_repo_tests/types/目录下的类型测试套件(test_mypy.py、test_pyright.py、test_hypothesis.py),用真实类型检查器对整仓库代码进行验证,确保策略 API 的类型提示不会退化。

实践小结

场景正确的类型标注说明
函数返回一个策略-> SearchStrategy[int]返回的是“策略”本身
使用@st.composite定义策略-> tuple[int, int](标注值的类型)装饰器会包装为SearchStrategy[tuple[int, int]]构造器
@st.composite函数内部draw: DrawFn或省略draw(strategy)的返回值类型自动等于策略值类型
多个策略组合SearchStrategy[int \| float]用\|或st.one_of组合后类型取联合

核心要点回顾:

  1. SearchStrategy[T]是策略的泛型类型,T是策略生成值的类型;它是公共 API 中仅用于类型标注的类,不要继承或实例化;
  2. st.integers(函数)的类型是Callable[..., SearchStrategy[int]],而s = st.integers()(值)的类型是SearchStrategy[int],两者务必区分;
  3. @st.composite下标注返回值的类型,而不是SearchStrategy[...],装饰器负责自动包装;
  4. SearchStrategy是协变的:B < A蕴含SearchStrategy[B] < SearchStrategy[A],这让策略在参数与返回值位置可以灵活替换,其语义由源码中TypeVar("Ex", covariant=True)声明保证。

按照上述规则为策略代码添加类型提示,即可让 mypy、Pyright、Pyre 等工具在编写阶段发现策略类型不匹配的问题,让属性测试代码同样享受现代静态类型检查带来的安全性与可维护性。

  • 测试
  • 开发工具

【免费下载链接】hypothesis

The property-based testing library for Python

项目地址:https://gitcode.com/gh_mirrors/hy/hypothesis
点击查看免费下载
上一篇:Falco社区赞助商权益:套餐与激活
下一篇:如何使用kss-node创建自动化CSS文档?5分钟快速入门教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 3:29:57

TensorFlow中dtensor导入失败的根因分析与分版本修复方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 3:29:19

SQL思路比细节更重要:从结果集思维到慢查询优化

开头我直接这样写&#xff1a;“思路不要细节的sql&#xff0c;或者关键词”这句话&#xff0c;我第一次看见是贴在某需求文档的备注栏里&#xff0c;当时第一反应是&#xff1a;这是什么意思&#xff1f;SQL 不就是靠细节写出来的吗&#xff1f;后来做久了才明白&#xff0c;这…

作者头像 李华
网站建设 2026/9/25 3:29:01

三值网络让27B模型塞进2-bit:原理、显存算账与本地部署实战

上周刷HuggingFace模型榜的时候&#xff0c;我一度以为自己眼花了&#xff1a;一个27B参数的大模型&#xff0c;三值化之后权重文件连7GB都不到&#xff0c;挂在榜首下得飞快&#xff0c;评论区全是在老显卡上跑出20 tokens/s的截图。放在两年前&#xff0c;27B这种体量想本地部…

作者头像 李华
网站建设 2026/9/25 3:27:55

用 Link Seams 在 CommonJS 中 Stub 依赖:Sinon + Proxyquire 实战指南

测试开发工具 【免费下载链接】sinon Test spies, stubs and mocks for JavaScript. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/si/sinon 点击查看 免费下载 Sinon 是 JavaScript 测试中最常用的测试替身&#xff08;test double&#xff09;库&#xff0c;但它本…

作者头像 李华