为 PyO3 项目自动生成 Python 类型存根:nautilus_trader 中的 pyo3-stub-gen 实战指南
发布时间:2026/9/12 2:56:06 锦皓数字建站

为 PyO3 项目自动生成 Python 类型存根nautilus_trader 中的 pyo3-stub-gen 实战指南【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader导读patches/pyo3-stub-gen/README.md记录了 Jij Inc. 开源的pyo3-stub-gencrate——一个面向 PyO3 与 maturin 项目的 Python 类型存根*.pyi生成器。它通过半自动化的方式把 Rust 侧的 PyO3 绑定自动翻译成 Python 类型存根并在默认翻译器无法覆盖的边缘场景提供手工覆写能力。在 nautilus_trader 仓库中这套机制被完整落地nautilus-pyo3crate 把全工作区各 crate 的 Python 绑定聚合到nautilus_trader._libnautilus单一扩展模块再由pyo3-stub-gen生成整套python/nautilus_trader/下的.pyi存根。读完本文你将掌握如何为 PyO3 项目接入 stub 生成、如何使用#[gen_stub_*]系列过程宏、如何通过python参数 /override_type/submit!手工修正复杂类型、如何用type_alias!定义语义化类型别名以及如何集成mypy stubtest校验与 Sphinx 文档生成最后还能看到 nautilus_trader 在生产规模下的工程化实践。设计动机为什么需要半自动化翻译Rust 与 Python 的类型系统存在根本差异加之过程宏的能力边界从 Rust 代码自动、完整地翻译出 Python 存根在原理上并不可行。因此pyo3-stub-gen采用半自动化策略对应 README Design 一节提供一套默认翻译器覆盖大多数most而非全部all场景同时提供手工指定翻译的途径让用户只对默认翻译不生效的边缘情况做局部修正手工翻译可以与默认翻译器的产物合并集成从而最大化默认翻译器的复用率。整个 crate 家族分为两个部分pyo3-stub-gen提供手工指定翻译的运行时机制StubInfo、inventory注册、type_alias!等pyo3-stub-gen-derive基于pyo3-stub-gen的机制实现默认翻译器——即#[gen_stub_pyfunction]、#[gen_stub_pyclass]、#[gen_stub_pymethods]等过程宏。从仓库内的 Cargo.toml 可以看到pyo3-stub-gen0.20.0 的默认 feature 为either、infer_signature、numpy、ordered-float另有可选的rust_decimal依赖上通过pyo3 0.26、inventory 0.3、numpy、serde等支撑类型翻译与信息收集并用toml读取pyproject.toml配置。[!NOTE] 最低支持 Python 版本为3.10不要在 PyO3 配置中启用 3.9 或更早版本。版本 0.15.0–0.17.1 曾无意中包含一个 LGPL 依赖该依赖在 0.17.2 中移除受影响版本已被 yank。快速上手给 PyO3 函数生成第一个.pyi用过程宏注解 Rust 代码pyo3-stub-gen提供#[gen_stub_pyfunction]等过程宏与 PyO3 的#[pyfunction]配合使用。一个最简单的 PyO3 项目原本如下use pyo3::prelude::*; #[pyfunction] fn sum_as_string(a: usize, b: usize) - PyResultString { Ok((a b).to_string()) } #[pymodule] fn your_module_name(m: BoundPyModule) - PyResult() { m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; Ok(()) }接入 stub 生成只需三处修改对应 README Usageuse pyo3::prelude::*; use pyo3_stub_gen::{derive::gen_stub_pyfunction, define_stub_info_gatherer}; #[gen_stub_pyfunction] // Proc-macro attribute to register a function to stub file generator. #[pyfunction] fn sum_as_string(a: usize, b: usize) - PyResultString { Ok((a b).to_string()) } #[pymodule] fn your_module_name(m: BoundPyModule) - PyResult() { m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; Ok(()) } // Define a function to gather stub information. define_stub_info_gatherer!(stub_info);其中define_stub_info_gatherer!(stub_info)会生成一个名为stub_info的函数用于在运行时收集所有被注册的 stub 信息。[!NOTE]#[gen_stub_pyfunction]宏必须放在#[pyfunction]宏之前。nautilus_trader 仓库里nautilus-pyo3cratecrates/pyo3/Cargo.toml正是这种模式的产品级实例它以lib.name nautilus_pyo3、crate-type [rlib, cdylib]聚合全工作区各 crate 的pythonfeature 绑定再通过#[pymodule] fn _libnautilus(...)见 crates/pyo3/src/lib.rs#L79-L307把 analysis、core、common、model、trading、backtest 以及 binance、bybit、okx 等 20 余个适配器子模块逐一wrap_pymodule!后挂载到nautilus_trader._libnautilus命名空间并写入sys.modules使它们可作为子模块导入。生成存根文件的可执行目标接着创建一个可执行目标src/bin/stub_gen.rs调用stub_info()并触发生成对应 README 对应小节use pyo3_stub_gen::Result; fn main() - Result() { // stub_info is a function defined by define_stub_info_gatherer! macro. let stub pure::stub_info()?; stub.generate()?; Ok(()) }在Cargo.toml的[lib]段同时声明rlib与cdylib[lib] crate-type [cdylib, rlib]然后运行cargo run --bin stub_gen该命令会在项目根生成存根文件如pure.pyi。maturin 会自动发现该存根并打包进 wheel——对应 maturin 的 adding python type information 项目布局约定。nautilus_trader 中的对应实现位于 crates/pyo3/bin/stub_gen.rs且[bin] name python-stub-gen在 crates/pyo3/Cargo.toml#L176-L181 中声明。nautilus_pyo3::stub_info()见 crates/pyo3/src/lib.rs#L326-L335与文档示例略有不同它不依赖 crate 内部的 pyproject而是向上定位工作区根目录并显式加载python/pyproject.toml再调用pyo3_stub_gen::StubInfo::from_pyproject_toml(pyproject_path)完成初始化——这正是多 crate 工作区聚合绑定的必然形态。#[gen_stub(skip)]从存根中排除目标对不想出现在.pyi文件中的函数或方法使用#[gen_stub(skip)]use pyo3::prelude::*; use pyo3_stub_gen::derive::*; #[gen_stub_pyclass] #[pyclass] struct MyClass; #[gen_stub_pymethods] #[pymethods] impl MyClass { #[gen_stub(skip)] fn internal_method(self) { // This method will not appear in the .pyi file } }#[gen_stub(default xx)]为 getter/setter/类属性指定默认值对 getter、setter 与类属性可以用default参数指定会出现在存根中的默认值use pyo3::prelude::*; use pyo3_stub_gen::derive::*; #[gen_stub_pyclass] #[pyclass] struct Config { #[pyo3(get, set)] #[gen_stub(default Config::default().timeout)] timeout: usize, } impl Default for Config { fn default() - Self { Config { timeout: 30 } } } #[gen_stub_pymethods] #[pymethods] impl Config { #[getter] #[gen_stub(default Config::default().timeout)] fn get_timeout(self) - usize { self.timeout } }手工覆写默认翻译器不够用时当自动类型翻译结果不理想时可用 Python 存根语法手工指定类型共有两条主线、三种方法对应 README Manual Overriding方法一python参数整体覆写签名当需要完整替换函数签名的 Python 表达时用#[gen_stub_pyfunction(python ...)]use pyo3::prelude::*; use pyo3_stub_gen::derive::*; #[gen_stub_pyfunction(python r# import collections.abc import typing def fn_with_callback(callback: collections.abc.Callable[[str], typing.Any]) - collections.abc.Callable[[str], typing.Any]: Example using python parameter for complete override. #)] #[pyfunction] pub fn fn_with_callbacka(callback: Bounda, PyAny) - PyResultBounda, PyAny { callback.call1((Hello!,))?; Ok(callback) }该方式的特点✅ 对生成的存根拥有完整控制权✅ 支持collections.abc.Callable等复杂类型✅ 允许附带自定义 docstring✅ import 语句会被自动抽取。方法二#[gen_stub(override_type(...))]局部覆写当大多数类型翻译正确、仅个别参数需要调整时用override_type覆写参数、用override_return_type覆写返回值use pyo3::prelude::*; use pyo3_stub_gen::derive::*; #[gen_stub_pyfunction] #[pyfunction] #[gen_stub(override_return_type(type_reprcollections.abc.Callable[[str], typing.Any], imports(collections.abc, typing)))] pub fn get_callbacka( #[gen_stub(override_type(type_reprcollections.abc.Callable[[str], typing.Any], imports(collections.abc, typing)))] cb: Bounda, PyAny, ) - PyResultBounda, PyAny { Ok(cb) }该方式的特点✅ 对单个类型进行细粒度控制✅ 其余参数仍保持自动生成✅ 明确标注哪些类型需要手工指定。方法三submit!宏独立提交定义#[gen_stub_pyfunction]与#[gen_stub_pyclass]内部会自动生成submit!块来注册类型信息你也可以手工追加submit!块补充或覆写自动注册。当同一函数/方法存在多个类型签名时生成器会自动在.pyi中输出overload装饰器让类型检查器正确识别多签名函数。实现函数重载有两种途径python_overload参数随函数内联定义重载submit!块独立维护 stub 定义——尤其适合过程宏/代码生成场景。函数重载内联方式use pyo3::prelude::*; use pyo3_stub_gen::derive::*; // Define overloads inline with python_overload parameter #[gen_stub_pyfunction( python_overload r# overload def process(x: int) - int: Process integer input # )] #[pyfunction] pub fn process(x: f64) - f64 { x 1.0 }生成的存根overload def process(x: int) - int: Process integer input overload def process(x: float) - float: ... # Auto-generated from Rust用no_default_overload true抑制自动生成当运行时做动态类型分发、Rust 签名不具代表性时use pyo3::prelude::*; use pyo3_stub_gen::derive::*; #[gen_stub_pyfunction( python_overload r# overload def func(x: int) - int: ... overload def func(x: str) - str: ... #, no_default_overload true // Dont generate from Rust signature )] #[pyfunction] pub fn func(ob: BoundPyAny) - PyResultPyObject { // Runtime type checking todo!() }类方法重载submit!方式use pyo3::prelude::*; use pyo3_stub_gen::{derive::*, inventory::submit}; #[gen_stub_pyclass] #[pyclass] pub struct Calculator {} #[gen_stub_pymethods] #[pymethods] impl Calculator { fn add(self, x: f64) - f64 { x 1.0 } } // Alternative: Use submit! for method overloads (useful for proc-macro/code generation) submit! { gen_methods_from_python! { r# class Calculator: overload def add(self, x: int) - int: Add integer (overload) # } }submit!体系的核心收益✅ 用python_overload参数内联定义重载✅ 自动生成overload装饰器✅ 基于索引排序保证确定性顺序✅ 为过程宏/代码生成场景提供submit!语法。更进阶的类方法模式完全手工提交方法签名、以及用#[gen_stub(skip)]混合过程宏与手工提交可参考 README 指向的examples/pure/src/manual_submit.rs与 Python Stub Syntax Support 文档。进阶RustType标记在 Python 语法中引用 Rust 类型在 Python stub 语法内部可以直接用pyo3_stub_gen.RustType[TypeName]引用 Rust 类型其展开依赖该 Rust 类型对PyStubTypetrait 的实现use pyo3::prelude::*; use pyo3_stub_gen::{derive::*, inventory::submit}; #[pyfunction] pub fn sum_list(values: Veci32) - i32 { values.iter().sum() } submit! { gen_function_from_python! { r# def sum_list(values: pyo3_stub_gen.RustType[Veci32]) - pyo3_stub_gen.RustType[i32]: Sum a list of integers # } }RustType标记会自动展开为合适的 Python 类型RustType[Veci32]→typing.Sequence[int]用于参数RustType[i32]→int用于返回值。它尤其适用于VecT、HashMapK, V等泛型实现了PyStubType的自定义类型以及需要保证 Rust 与 Python 类型映射一致性的场景。何时选用哪种方法场景推荐方法复杂类型如Callable、Protocol方法一python ...参数覆写一两个参数方法二#[gen_stub(override_type(...))]函数重载overloadpython_overload ...参数在 Python 语法中引用 Rust 类型RustType[...]标记完整替换函数签名方法一python ...参数类型别名type_alias!与gen_type_alias_from_python!类型别名可以为复杂或频繁使用的类型定义语义化名称提升存根可读性与可维护性对应 README Type Aliases。基础用法use pyo3_stub_gen::type_alias; use std::collections::HashMap; // Simple type alias type_alias!(your_module, SimpleAlias Optionusize); // Collection types type_alias!(your_module, StrIntMap HashMapString, i32); // Nested option types type_alias!(your_module, MaybeString OptionOptionString);直接联合类型语法type_alias!支持直接书写联合类型大多数情况下无需单独的impl_stub_type!声明use pyo3_stub_gen::type_alias; // Simple union types type_alias!(your_module, NumberOrStringAlias i32 | String); // Multiple types in a union type_alias!(your_module, TripleUnion i32 | String | bool); // Unions of generic types type_alias!(your_module, GenericUnion Optioni32 | VecString); // Complex nested unions type_alias!(your_module, ComplexUnion OptionVeci32 | OptionVecString);备选与impl_stub_type!组合复用若同一联合类型需要在多处引用仍可采用两步式impl_stub_type!type_alias!模式use pyo3_stub_gen::{impl_stub_type, type_alias}; // Define a reusable union type struct NumberOrString; impl_stub_type!(NumberOrString i32 | String); // Use it in multiple type aliases type_alias!(your_module, NumberOrStringAlias NumberOrString); type_alias!(your_module, AnotherAlias NumberOrString);生成输出与语法配置类型别名在 Python 存根中以TypeAlias注解渲染兼容 Python 3.11from typing import TypeAlias __all__ [ MaybeDecimal, NumberOrStringAlias, SimpleAlias, StrIntMap, StructUnion, ] MaybeDecimal: TypeAlias typing.Optional[DecimalHolder] NumberOrStringAlias: TypeAlias builtins.int | builtins.str SimpleAlias: TypeAlias typing.Optional[builtins.int] StrIntMap: TypeAlias builtins.dict[builtins.str, builtins.int] StructUnion: TypeAlias ComparableStruct | HashableStruct默认生成的是 Python 3.12 之前的TypeAlias语法若项目面向 Python 3.12可在pyproject.toml中开启type语句语法[tool.pyo3-stub-gen] use-type-statement true输出将变为type MyAlias int | str[!NOTE] 使用use-type-statement true时请确保项目最低 Python 版本为 3.12 或更高——type语句在更早版本中不可用。用 Python 语法定义复杂别名需要 Python 特定语法如特定 import、复杂泛型、难以用PyStubType表达的类型时可用gen_type_alias_from_python!use pyo3_stub_gen::derive::gen_type_alias_from_python; gen_type_alias_from_python!( your_module, r# import collections.abc from typing import TypeAlias CallbackType: TypeAlias collections.abc.Callable[[str], None] # );解析器同时接受 3.12 前后两种语法输出格式统一由use-type-statement配置控制。类型别名是仅存根stub-only构造运行时并不存在纯粹服务于静态类型检查与 IDE 支持同时它天然兼容自动__all__生成以及 mypy、pyright、ruff 等类型检查器。质量保障mypy stubtest 集成mypy stubtestuv run stubtest your_module_name --ignore-missing-stub --ignore-disjoint-basesPyO3/maturin 项目必需的标志--ignore-missing-stubmaturin 会创建内部原生模块.so文件并向__init__.py再导出stubtest 会为这些内部模块寻找存根但实际并不存在所有类型都在__init__.pyi中该标志用于消除此类误报。--ignore-disjoint-basesPyO3 类在运行时属于 disjoint bases而 pyo3-stub-gen 不会生成typing.disjoint_base装饰器。已知限制嵌套子模块stubtest 不适用于 PyO3 嵌套子模块。嵌套的#[pymodule]在运行时创建的是属性attribute而非可导入模块而存根文件使用目录结构组织。对于含嵌套子模块的项目需要对这些包禁用 stubtest。nautilus_trader 的 python/generate_stubs.py 在生成完成后执行了一整套与之配套的存根后处理post_process_stubs修正文件头# This file is automatically generated by pyo3_stub_gen、按rename_all SCREAMING_SNAKE_CASE重命名枚举变体、py_new - __init__等方法重命名、从#[pyo3(signature (...))]属性还原真实默认值并安全地抑制必选参数出现在带默认参数之后的非法签名、为缺失默认值的Optional参数补 None、对后向引用局部类的默认值替换为...以避免类体内非法运行时表达式、注入手写 Python 符号的再导出如ReportProvider、TearsheetChart等、以及剥离 docstring运行时__doc__才是唯一事实来源。这些正是 stubtest 能在 nautilus_trader 数千个绑定上通过的必要工程支撑。进阶用同一份元数据生成 Sphinx API 文档除了存根pyo3-stub-gen 还能用同一份 Rust 类型元数据生成 Sphinx。配置在pyproject.toml中添加[tool.pyo3-stub-gen.doc-gen]段[tool.pyo3-stub-gen.doc-gen] output-dir docs/api json-output api_reference.json index-title API Referencesrc/bin/stub_gen.rs无需任何改动——只要存在该配置段stub.generate()就会自动生成文档。可用选项选项类型默认值描述output-dirPathdocs/api生成文件输出目录相对pyproject.tomljson-outputStringapi_reference.jsonJSON 数据文件名separate-pagesBooleantrue每个模块生成独立.rst页面index-titleString{package} API Referenceindex.rst标题intro-messageString默认简介文本index.rst的简介文字空字符串则省略contents-tableBooleanfalse显示模块内容汇总表Sphinx 搭建在开发依赖中加入 Sphinx 相关包[dependency-groups] dev [myst-parser, sphinx, sphinx-rtd-theme]创建docs/conf.py加载生成的扩展import sys from pathlib import Path # Add the API docs directory so Sphinx can find the generated extension sys.path.insert(0, str(Path(__file__).parent / api)) project your_project extensions [ pyo3_stub_gen_ext, # Generated extension — reads api_reference.json sphinx.ext.intersphinx, # Enables cross-references to external projects ] intersphinx_mapping { python: (https://docs.python.org/3, None), } html_theme sphinx_rtd_theme生成文件与构建运行cargo run --bin stub_gen会在output-dir产出api_reference.json—— 结构化 API 数据JSON 中间表示pyo3_stub_gen_ext.py—— 把 JSON 渲染为文档的 Sphinx 扩展index.rst—— 含 toctree 的目录页separate-pages true时module.rst—— 每个模块一页separate-pages true时。每个模块.rst只含一个由扩展展开的指令pure .. pyo3-api:: pure构建命令cargo run --bin stub_gen # Generate API data Sphinx files uv run --with sphinx sphinx-build -W -b html docs docs/_build # Build HTML生成的扩展提供两个 RST 指令.. pyo3-api:: module_name—— 渲染单个模块的 API 参考.. pyo3-api-package:: package_name—— 渲染包内所有模块。Rust 文档注释/// ...会渲染为 MyST Markdown支持交叉引用:class:ClassName、代码块、admonition 等 Sphinx/MyST 特性因此myst-parser是必需的。README 还提示了单模块项目examples/pure/docs/与含子模块的多模块项目examples/mixed/docs/两套完整参考实现。生产级实践nautilus_trader 如何落地 stub 生成nautilus_trader 将上述机制规模化形成了一条可复现的存根生产线聚合绑定nautilus-pyo3crates/pyo3/src/lib.rs把所有 crate 的 Python 绑定收拢到单一_libnautilus扩展模块并在 crates/pyo3/Cargo.toml#L20-L21 声明crate-type [rlib, cdylib]同时满足 stub 生成rlib与 wheel 构建cdylib两种用途。生成入口cargo run --bin python-stub-gencrates/pyo3/bin/stub_gen.rs调用nautilus_pyo3::stub_info()后者显式读取 python/pyproject.toml 并构造StubInfocrates/pyo3/src/lib.rs#L326-L335。构建集成python/pyproject.toml的[tool.maturin]指向../crates/pyo3/Cargo.toml、module-name nautilus_trader._libnautilus并通过include [**/*.pyi]把生成的存根打进 wheel[build-system]使用maturin1.15.0。脚本驱动与后处理python/generate_stubs.py 既可作为独立脚本python generate_stubs.py也可作为自定义构建脚本python generate_stubs.py build它解析[tool.maturin]的 feature 列表剔除extension-module并补入nautilus-interactive-brokers/gateway后调用 stub 生成器再执行类重定位如把_libnautilus中的AmmType、Dex、Chain等迁回model模块、__all__同步、配置门面存根生成、格式化与陈旧存根清理。兼容性补丁由于上游pyo3-stub-gen 0.20.0之后版本拒绝pymodule根之外的模块路径而本仓库的gen_stub_*注解指向nautilus_trader包路径位于nautilus_trader._libnautilus根之外patches/README.md 明确说明该 crate 被固定在 0.20.0并通过本地补丁适配pyo3 0.29.0src/util.rs中用cast::T()替换三处已移除的BoundPyAny::downcast::T()调用src/exception.rs移除PyEnvironmentError/PyIOError的 stub 类型实现PyO3 0.29 将两者别名到PyOSError保留会造成重复 trait impl。补丁刻意不改变存根布局、类重定位、模块命名与签名规范化等行为——这些仍由python/generate_stubs.py与固定的 0.20.0 代码控制。这套链路回答了一个工作区、几十个 crate、上千个绑定类如何产出可信的.pyi这一问题过程宏负责自动化翻译python/override_type/submit!/type_alias!负责边缘修正stubtest 负责校验Sphinx doc-gen 负责文档化而generate_stubs.py负责把这一切编排进构建管线。结语与进一步阅读pyo3-stub-gen的价值在于把Python 类型存根从手写维护的负担转化为从 Rust 绑定半自动生成 边缘手工修正的工程流程。其设计默认翻译器 手工覆写 元数据合并与 nautilus_trader 的规模化实践聚合 crate、脚本化后处理、版本锁定与兼容性补丁共同证明即便面对 Rust/Python 类型系统差异与过程宏局限仍然可以获得类型完整、IDE 友好、经 stubtest 校验的 Python API 层。可以继续深入阅读的仓库文件patches/pyo3-stub-gen/README.md —— 本文的直接来源用法、重载、类型别名、stubtest、Sphinx 文档生成patches/README.md —— 本仓库为何固定 0.20.0 并打补丁的决策记录patches/pyo3-stub-gen/src/lib.rs 与 patches/pyo3-stub-gen/src/stub_type.rs —— 核心StubInfo与类型翻译实现crates/pyo3/src/lib.rs 与 crates/pyo3/bin/stub_gen.rs —— 聚合绑定与生成入口python/generate_stubs.py —— 存根后处理与构建编排python/pyproject.toml —— maturin 与 stub 打包配置。【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。