测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载Hypothesis 通常被视为一个属性测试property-based testing库但从实现角度看它首先是一个数据构造与探索引擎生成数据、提出假设、证明或证伪假设测试库只是构建在其上的一层薄壳。本文基于 Hypothesis 官方技术博客《Exploring Voting Systems with Hypothesis》完整演示如何借助find函数把 Hypothesis 当作交互式数据探索工具用来在两个真实的投票算法Plurality 与 IRV之间自动寻找结果不一致的选举反例读完本文你将掌握st.composite、st.permutations、st.lists、st.integers的组合用法理解find的底层运行机制并能把这套策略 谓词 最小反例的方法迁移到自己的探索式开发场景中。从测试库到数据探索工具理解 Hypothesis 的双重身份博客作者 drmaciver 在文中开门见山地指出一个常被忽略的事实Hypothesis is, of course, a library for writing tests. But from animplementationpoint of view this is hardly noticeable. Really its a library for constructing and exploring data and using it to prove or disprove hypotheses about it. It then has a small testing library built on top of it.也就是说Hypothesis 的绝大多数用户把它当作测试库使用这也是项目开发的重心所在但借助find函数你完全可以把它当作一个交互式数据探索器给定一个数据生成策略strategy和一个判定条件condition它会自动找出满足该条件的最小数据示例。从当前仓库源码可以印证这一点。find的完整实现位于 hypothesis/src/hypothesis/core.py#L2428-L2476其核心语义在 docstring 中定义得非常明确Returns the minimal value from the given strategyspecifierthat matches the predicate functioncondition.即返回给定策略中满足谓词函数的最小值。它的默认配置也体现了探索而非严格测试的取向默认使用Settings(max_examples2000)最多尝试 2000 个示例并且自动suppress_health_checklist(HealthCheck)抑制健康检查、report_multiple_bugsFalse关闭多 bug 报告因为它只关心能否找到第一个符合条件的值。正是这种构造数据 → 验证谓词 → 收缩到最小反例的循环让find成为回答I wonder if...?这类探索性问题的利器。探索对象单一获胜者偏好投票制为了让探索有一个具体落点文章聚焦于单一获胜者偏好投票制single winner preferential voting systems有一组候选人candidates每位选民给出一个完整排序从最喜爱到最不喜爱投票系统根据全体选票选出一名获胜者。文中约定的 Python 接口非常简洁每个投票系统是一个函数接收一个 election选票列表每张选票是一个把候选人按偏好排序的列表返回无歧义获胜者或None表示平局def plurality_winner(election): counts Counter(vote[0] for vote in election) alternatives candidates_for_election(election) winning_score max(counts.values()) winners [c for c, v in counts.items() if v winning_score] if len(winners) 1: return None else: return winners[0]这是Plurality 投票制多数人理解的普通投票获得最多第一偏好票的候选人获胜若最高票数被多人并列则返回None表示平局。另一个重点考察的投票制是IRVInstant Runoff Voting即时决选投票在英国政治语境下常被称为 Alternative Vote替代投票。IRV 采用多轮淘汰机制直到剩下唯一候选人def irv_winner(election): candidates candidates_for_election(election) while len(candidates) 1: scores Counter() for vote in election: for c in vote: if c in candidates: scores[c] 1 break losing_score min(scores[c] for c in candidates) candidates [c for c in candidates if scores[c] losing_score] if not candidates: return None else: return candidates[0]IRV 的每轮逻辑是对每个仍在竞争的候选人统计有多少选民把他排在剩余候选人中的第一位联合最低分的候选人一起淘汰。循环结束时可能剩下 0 个或 1 个候选人0 个意味着在某轮所有候选人并列最低分判定为平局draw1 个即为获胜者victory。直觉上这两种制度不可能在所有选举中都给出相同答案——但如何手工构造一个让二者分道扬镳的例子并不直观。这正是 Hypothesis 登场的地方。用 Hypothesis 构造选举生成器st.composite首先需要定义随机生成一场选举的策略。文章使用st.composite装饰器把任意一个从数据流里取值的函数转化为策略import hypothesis.strategies as st st.composite def election(draw): candidates list(range(draw(st.integers(2, 10)))) return draw(st.lists(st.permutations(candidates), min_size1))逐行拆解这个策略st.integers(2, 10)先画出一个 2 到 10 之间的整数list(range(...))将其转成候选人的整数编号列表。注意range的右端点不包含因此实际候选人数是2 到 9 人。候选人本身用整数编号即可——只要彼此可区分具体是什么并不重要用整数最简洁。st.permutations(candidates)生成候选人的一种随机排列对应一张完整偏好排序选票。st.lists(..., min_size1)把多张这样的选票聚合成一场选举min_size1保证至少有一位选民。也就是说这个策略生成的选举是由 2~9 名候选人、1 张及以上随机偏好排列的选票构成的列表。从当前仓库源码可以进一步印证每个策略的语义st.integers定义于 hypothesis/src/hypothesis/strategies/_internal/numbers.py#L121-L125支持min_value/max_value参数约束取值区间st.lists定义于 hypothesis/src/hypothesis/strategies/_internal/core.py#L303-L360docstring 明确其行为列表长度落在[min_size, max_size]区间内任一方向为None则无边界并且示例收缩时会尝试移除列表元素、同时收缩每个元素st.permutations定义于 hypothesis/src/hypothesis/strategies/_internal/core.py#L2021-L2033docstring 说明其收缩方向示例收缩时会尝试更接近values的原始顺序——这保证了find找到反例后能高效地把它缩到更小、更易读st.composite装饰器定义于 hypothesis/src/hypothesis/strategies/_internal/core.py#L2177-L2186它把接收draw回调的函数包装成策略是编写复合数据生成器最惯用的方式。顺带一提election策略本身也有对应的真实测试覆盖例如仓库中的 hypothesis/tests/cover/test_find.py 对find的行为做了系统性验证。写出你感兴趣的谓词differing_without_ties有了数据生成器之后接下来定义我们要找什么——一个谓词函数接受一场选举并返回是否感兴趣def differing_without_ties(election): irv irv_winner(election) if irv is None: return False plurality plurality_winner(election) if plurality is None: return False return irv ! plurality这个谓词表达的语义是Plurality 与 IRV 都没有出现平局但两者选出的获胜者不同。这就是我们要让find去寻找的有趣数据——一个能证明两种投票制度会得出不同结果的选举反例。注意这里对平局的排除是有代价的如文章后续所说正是因为坚持无平局这个额外约束最终找到的例子才会偏大。如果允许任意打破平局例如偏好编号较小的候选人可以找到小得多的反例。在控制台运行 find自动收获一个反例把策略和谓词交给find在交互式控制台里即可运行 from hypothesis import find import voting as v distinct find(v.election(), v.differing_without_ties) distinct [[0, 1, 2], [0, 1, 2], [1, 0, 2], [2, 1, 0], [0, 1, 2], [0, 1, 2], [1, 0, 2], [1, 0, 2], [2, 1, 0]]示例中voting是包含前文plurality_winner、irv_winner等函数的一个模块原文命名为voting。Hypothesis 很快找到了这样一场选举3 名候选人0、1、29 张选票。接下来验证这场选举确实让两种制度产生了分歧 v.irv_winner(distinct) 1 v.plurality_winner(distinct) 0IRV 的获胜者是候选人 1Plurality 的获胜者是候选人 0——两种制度在同一场选举上给出了不同结果反例成立。文章的观察也值得记录例子偏大的主要原因是无平局的硬约束若允许任意打破平局能找到更小的例子不同次运行中Hypothesis 有时会找到一个略小、但包含 4 名候选人的选举——这说明find的搜索结果具有一定的运行间差异这也与它默认基于随机种子探索有关。find 的工作原理源码视角find之所以能自动找到最小反例其底层机制在 hypothesis/src/hypothesis/core.py#L2428-L2476 中一目了然def find( specifier: SearchStrategy[Ex], condition: Callable[[Any], bool], *, settings: Settings | None None, random: Random | None None, database_key: bytes | None None, ) - Ex:关键实现细节默认设置未传入settings时使用Settings(max_examples2000)并强制suppress_health_checklist(HealthCheck)、report_multiple_bugsFalse数据库键若未指定database_key会用function_digest(condition)对谓词函数求摘要作为数据库键源码注释提醒该键并不保证唯一基于 given 运行find内部把谓词包装进一个given(specifier)装饰的测试函数命中条件时抛出内部Found异常记录当前值并终止搜索test()结束后返回记录的值找不到时的行为若 2000 个示例或收缩后的整个探索过程内没有命中则抛出NoSuchExample异常并附上谓词的描述。这也是仓库 hypothesis/docs/changelog.rst 中记录的一条演进脉络find()曾在 4.28.0 版本2019-07-11被建议弃用——官方认为想随便拿一个例子时.example()更合适、想要最小例子时可以用given随后 4.47.5 版本2019-11-28又把find()重构为基于given实现以复用更多公共代码。直到当前仓库版本find仍保留在core.py中可直接使用本文介绍的用法依然成立。更多探索方向投票制度还有很多值得探索的性质原文为感兴趣的读者留了三个练习寻找 Condorcet 循环构造一场存在投票悖论的选举——例如选民整体上偏好 A 胜过 B、偏好 B 胜过 C、却又偏好 C 胜过 A导致多数偏好无法形成一个明确的传递顺序比较多数偏好与获胜者寻找多数选民更偏好 Plurality 获胜者胜过 IRV 获胜者以及相反方向的选举观察两种制度在尊重多数意愿上的差异用 given 替代 find 写性质测试把经典的选举制度评判准则如单峰性、单调性、独立性等形式化为不变量用given写成常规测试让 Hypothesis 大规模随机验证而非只找一个反例。这三条路线恰好勾勒出find与given的分工前者适合交互式、一次性的探索后者适合可重复、可回归的自动化验证。超越选举探索式开发中的 Hypothesis文章的收尾把视野拉回到工程实践开发过程往往是一连串小实验测试是执行这些实验的好方式但有时你只是想回答一个探索性的I wonder if...?问题——这两种算法会不会在某些输入上结果不一致这个约束会不会让某些合法输入无法生成——这种时候把 Hypothesis 的生成与收缩能力直接带到 REPL 里往往比先搭好一整套测试框架高效得多。本文演示的st.composite定义数据形状 → 谓词表达探索目标 →find返回最小反例三步法可以原样迁移到解析器、序列化器、状态机、数值计算等任何输入空间巨大、直觉难以穷举的领域。当你下一次产生我很好奇……的念头时不妨让 Hypothesis 替你去找答案。赞分享测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载相关推荐Python股票量化分析实战构建高效数据驱动投资系统在当今数据驱动的投资时代Python凭借其强大的数据分析能力成为量化投资的首选工具。本文将通过一个完整的股票分析系统实战案例详细介绍如何利用Python构建后端数据分析数据可视化金融科技探索Danswer解锁AI驱动数据问答的终极潜力探索Danswer解锁AI驱动数据问答的终极潜力 Danswer是一款开源AI平台它通过先进的AI聊天功能与各种LLM大型语言模型无缝协作为用户提供智AI 应用大模型RAGAI Agent后端前端上一篇PageView数据驱动开发ForEach风格API使用详解下一篇LightGlue完全指南如何实现4-10倍速度提升的局部特征匹配创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
