资讯动态

mypyc 深入解析:将类型注解的 Python 编译为 CPython C 扩展(随 Flipper Zero 固件工具链捆绑)

发布时间:2026/9/14 14:25:47 来源:尧图企业网站定制
mypyc 深入解析将类型注解的 Python 编译为 CPython C 扩展随 Flipper Zero 固件工具链捆绑【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmwaremypyc 是 mypy 项目自带的Python 到 C 扩展编译器它读取带有标准类型注解PEP 484的 Python 模块直接生成 C 代码并编译为 CPython C 扩展从而消除解释器开销。本文以该包自带的 README.md 为主体结合包内完整文档与源码结构系统讲解 mypyc 的严格编译语义、环境要求、安装与编译流程、与 CPython 的语义差异、性能优化手段及其内部实现原理。读完你可以独立判断哪些模块值得用 mypyc 编译、如何编译、会遇到哪些限制并理解其 IR 与代码生成管线。在本文所在仓库中mypyc 以预编译形式捆绑在固件构建工具链的 Python 3.11 环境中toolchain/x86_64-linux/lib/python3.11/site-packages/mypyc/其目录内绝大多数模块如irbuild、codegen、analysis等子包均为*.cpython-311-x86_64-linux-gnu.so已编译扩展——可以推断工具链环境本身正是这类编译加速的 Python的受益者之一。mypyc 是什么核心定位与严格语义Mypyc 是一个将mypy 注解过的、静态类型的 Python 模块编译为CPython C 扩展的编译器。按 README.md 的说明其当前首要目标是让 mypy 自身跑得更快——mypy 官方 wheel 就是用 mypyc 编译的编译后的 mypy 比未编译时大约快 4 倍这是项目文档声明的数据。Mypyc 编译的是一种基于 Python 语言变体的严格语义代码这意味着除其他事项外大多数类型注解在运行时被强制执行类型不匹配时抛出TypeError类被编译为扩展类extension classes没有__dict__行为上非常接近但不等同于使用了__slots__monkey patching 不生效实例属性在未定义时不会回退到类属性此外仍存在不少 bug 与不支持的特性。编译后的模块可以 import 任意 Python 模块也可以被其他 Python 模块调用。通常的做法是只编译包含性能瓶颈的模块。由于 mypyc 针对的是合法的 Python 代码编译后的模块也可以直接作为普通解释型 Python 模块运行因此所有 Python 开发工具和调试器都可以照常使用。关于性能提升幅度包内 introduction.rst 给出了更细的量化声明已有类型注解的代码编译后通常快1.5 到 5 倍为 mypyc 调优过的代码可以快5 到 10 倍mypyc 当前的目标是加速非数值计算类代码例如服务器应用mypyc 也被用来编译自身和 mypy。支持平台与环境要求README.md 对各平台给出了明确要求平台要求macOSmacOS Sierra 或更高版本需要 Xcode 命令行工具建议使用 python.org 提供的 Python 3.5其他版本未测试Linux足够新的 C/C 构建环境Python 3.5Windows已在 Windows 10 MSVC 2017 上测试Python 3.5包内 getting_started.rst 补充了更具体的构建依赖安装方式macOSxcode-select --install安装 Xcode 命令行工具Linux以 Ubuntu 18.04 为例sudo apt install python3-dev即需要 C 编译器以及 CPython 头文件和库Windows从 Visual Studio 2022 Build Tools 安装对应架构的 MSVC C 构建工具和一个 Windows SDK。贡献者快速上手从源码跑通测试README.md 给出了贡献者工作流注意这是针对 mypy/mypyc 上游仓库的开发流程本文仅作流程说明不包含外部链接克隆 mypy 的 git 仓库并cd进入推荐创建虚拟环境$ python3 -m venv directory $ source directory/bin/activate安装依赖$ python3 -m pip install -r test-requirements.txt运行 mypyc 测试$ pytest -q mypycdev-intro.md 对测试体系做了更深入的说明大多数测试用例采用与 mypy 相同的.test格式存放在mypyc/test-data/目录本文档仓库中对应 test-data/其中包括 74 个.test文件与若干.py、.pyi夹具文件。CI 会额外用 mypyc 编译 mypy 并运行完整 mypy 测试套件以捕捉本地测试发现不了的问题。安装、编译与运行一个可复现的完整示例安装 mypycmypyc 随 mypy 发行版一起分发需要 Python 3.8 及以上$ python3 -m pip install -U mypy[mypyc]某些系统上使用python -m pip install -U mypy[mypyc]。示例程序 fib.py以经典的递归斐波那契微基准为例保存为fib.pyimport time def fib(n: int) - int: if n 1: return n else: return fib(n - 2) fib(n - 1) t0 time.time() fib(32) print(time.time() - t0)注意fib函数带有- int类型注解——没有注解的话编译后的性能提升不会这么明显。编译与对比运行先用 CPython 以普通解释方式运行$ python3 fib.py 0.4125328063964844然后用mypyc编译为二进制 C 扩展$ mypyc fib.py这会在当前工作目录生成fib的 C 扩展例如在 Linux 上可能叫fib.cpython-37m-x86_64-linux-gnu.so在本文仓库的 Python 3.11 环境下则形如fib.cpython-311-x86_64-linux-gnu.so。C 扩展不能直接作为程序运行因此用python3 -c导入来运行$ python3 -c import fib 0.04097270965576172文档示例中编译后大约快 10 倍。同时要注意编译后fib.py中的__name__会是fib而不是__main__。mypyc命令行还接受大部分 mypy 命令行选项。想恢复解释版直接删除生成的.so文件即可Linux 示例$ rm fib.*.so通过 setup.py 集成到项目也可以用setup.py的方式编译核心是mypycify(...)函数from setuptools import setup from mypyc.build import mypycify setup( namemylib, packages[mylib], ext_modulesmypycify([ mylib/__init__.py, mylib/mod.py, ]), )mypycify之外列出的 Python 文件不会被编译。随后可以打包 wheel$ python3 setup.py bdist_wheelwheel 生成在dist/下也可以原地编译类似直接使用mypyc命令$ python3 setup.py build_ext --inplacemypycify的参数列表中可以混入 mypy 命令行选项例如用--disallow-untyped-defs强制所有函数都带类型注解setup( namefrobnicate, packages[frobnicate], ext_modulesmypycify([ --disallow-untyped-defs, # 传入 mypy 标志 frobnicate.py, ]), )官方文档特别提示不要轻易使用--check-untyped-defs去检查没有类型注解的函数因为大量已检查/未检查代码之间的切换会显著拉低性能。推荐开发工作流解释模式为主、编译模式验证getting_started.rst 给出的官方推荐工作流mypy/mypyc 自身就是这样开发的开发阶段使用解释模式获得快速的编辑-运行循环大量使用类型注解并用 mypy 静态检查在注解覆盖良好的前提下mypy 与测试可以提前发现绝大多数会破坏编译代码的错误实现完一个功能或修复后编译项目并以编译模式再跑一遍测试通常在注解覆盖良好时不会出问题这一步可以在本地或 CI 中完成发布/部署编译版本可选地保留一个解释版回退用于 mypyc 不支持的平台。配合 mypy 增量运行尤其用 mypy daemon 时通常只要几百毫秒解释模式下的开发体验几乎和纯 Python 项目无异。编译代码与 CPython 的行为差异README.md 列出的严格语义在 differences_from_python.rst 中被展开为一份详细的差异清单运行方式不能用python3 module.py或python3 -m module运行编译模块要用python3 -c import module或写一个 wrapper 脚本因此if __name__ __main__:在编译代码中不可依赖。类型错误会阻止编译产生 mypy 类型错误的代码无法编译# type: ignore偶尔可以绕过但可能生成坏代码被视为危险用法。运行时类型检查非擦除类型non-erased types的注解会在运行时被检查。例如def twice(x: int) - int传twice(5)正常传twice(2.2)或twice(blah)会抛TypeError。带推断类型的值同样会被检查编译代码调用socket.gethostname()时mypyc 依据 typeshed 中的 stubdef gethostname() - str推断返回类型为str若实际返回不兼容值如None会抛TypeError。cast(str, x)在编译后等价于if not isinstance(y, str): raise TypeError(...)而解释模式下cast不做任何运行时检查。原生类行为与普通 Python 类不同详见下文。基本类型int使用非堆分配unboxed表示代价是精确运行时类型信息丢失——first_int([True])输出1而不是Truebool是int子类转成int时被转换整数仍保持任意精度。定长元组同样被 unboxed精确类型与同一性不保留不能可靠地用is比较。早绑定early binding同一编译单元内的函数、类型、多数属性和方法引用在编译期确定目标省略了运行时的命名空间查找而非常数non-final的模块级变量仍使用晚绑定性能关键代码应避免。pickle 与拷贝构造对象时会隐式调用__init__。若原生类不支持无参__init__则无法 pickle/copy可用mypy_extensions.mypyc_attr装饰器开启serializable标志。注意开启序列化可能拖慢属性访问且子类自动继承该标志。monkey patching编译函数的定义不可变不能任意替换函数/方法比如在测试里打 mock。每个编译模块的 Python 命名空间本身是普通 dict、可以被修改但编译代码一般不走这个命名空间改动只对非编译代码可见。栈溢出编译代码目前不检查栈溢出失控递归可能导致程序不可恢复地崩溃文档注明该限制未来会修复。Final 值对Final声明的属性引用会被替换为编译期计算的常量值例如MAX: Final 100中两处MAX引用等效于字面量100解释模式下这些引用反而更慢且可被修改。尚不支持的 Python 特性文档明确列出这些特性不能用于编译代码或存在限制嵌套类被 if 语句保护的条件函数或类定义原生类的部分dunder 方法__del__、__index__、__getattr__、__getattribute__、__setattr__、__delattr__生成器表达式会被隐式替换为列表推导语义并不总是等价可用生成器函数或显式列表推导绕过任意描述符property、staticmethod、classmethod 受支持多种内省手段实例__annotations__通常不保留、inspect无法检查编译函数帧、inspect.ismethod不识别编译方法、inspect.signature会在编译函数上出错性能分析与追踪钩子profile、cProfile、trace不会触发编译函数的钩子调试器无法在编译函数中设置断点或用pdb单步通常应改为在解释模式下调试。原生类Native Classes详解类在编译模块中默认为原生类被编译为 C 扩展类很多方面类似int、str、list等内建类型。native_classes.rst 说明了其关键约束不可变命名空间类型对象命名空间基本不可变——Cls.method1 Cls.method2或Cls.new_method ...都是错误只能给类定义体内或基类中声明过的属性赋值。未在类中声明的实例属性如o.extra 3会报错。继承只支持单继承trait 类型除外。可作为非原生基类的仅有object、dict/Dict[k, v]、BaseException、Exception、ValueError、IndexError、LookupError、UserWarning、typing.NamedTuple、enum.Enum。默认不允许非原生类继承原生类、也不允许在定义原生类的编译单元之外继承它可通过mypyc_attr(allow_interpreted_subclassesTrue)开启代价是非原生子类的属性/方法访问变慢走普通 Python 属性访问机制。类变量必须显式用ClassVar/ClassVar[type]声明不能通过实例给类变量赋值o.cv 3报错常量类变量可用Final声明。泛型原生类类型变量在运行时被擦除实例不记录类型参数值运行时类型检查无法核对类型变量值要等到以类型变量类型读取值时才检查。元类多数元类不支持可用abc.ABCMeta与typing.GenericMeta遇到不支持的元类时mypyc会把该类编译成普通 Python 类。类装饰器多数装饰器太动态而不支持可用mypy_extensions.trait、mypy_extensions.mypyc_attr、dataclasses.dataclass、attr.s(auto_attribsTrue)。dataclass 与 attrs 类只有部分原生支持效率不如纯原生类遇到不支持的类装饰器时同样回退为普通 Python 类。删除属性默认属性不可删除用类体内的__deletable__ [x, y]显式允许且必须用仅含字符串字面量的列表/元组表达式就地初始化用变量赋值是错误写在类体外也是错误。原生类实例通常没有__dict__。类型注解与性能哪些类型值钱using_type_annotations.rst 给出了决定编译收益的关键分类基本类型primitive types——这些类型有大量高效的原生实现是性能提升的核心int、i64、i32、i16、u8、float、bool、str、List[T]、Dict[K, V]、Set[T]、变长Tuple[T, ...]、None。所有 Python 操作都可用但其中的原生操作有定制的优化实现。原生类——构造、属性访问、方法调用等最常见操作都被优化。元组类型——定长元组如Tuple[int, str]作为变量、参数或返回值时是值类型分配在机器栈或 CPU 寄存器上存入 Python 容器或传入非原生代码时会被 boxed 成普通 Python tuple。联合类型 / Optional——含基本类型、原生类、trait 的联合类型同样高效Optional[int]相当高效但值总是被 boxed普通intunboxed 表示通常更快。trait 类型——用mypy_extensions.trait定义为原生类提供有限的多继承形式trait 必须放在基类列表末尾通过 trait 类型访问方法/属性略慢于原生类但远快于普通 Python 类。擦除类型erased types——普通 Python 类、非基本内建类型、Callable、类型变量、Any、协议类型等没有定制操作运行时等价于Any。但用好擦除类型仍有收益例如f: Callable[[], int]调用本身慢但返回值的推断类型是基本类型int后续n 1可以用快速操作。值类型与堆类型bool、float、None、原生整数类型和定长元组是值类型int是混合体——常规大小64 位平台 63 位以内走值表示更大时走堆表示同 CPython。由此产生的语义包括整数/浮点/元组的对象同一性不保留应使用而非isbool赋给int类型变量时隐式转换为对应整数这是 mypyc 中唯一的隐式类型转换编译代码中禁止把int赋给float变量即使初始化也不行必须显式float(n)。原生整数类型i64/i32/i16/u8从mypy_extensions导入固定位宽、无溢出检查比任意精度int更快、更省内存。int与原生整数之间可双向隐式转换不同原生整数类型之间必须显式转换窄化转换截断且无运行时溢出检查混用int与原生整数的二元运算会把int操作数隐式强制转为原生整数粘性。注意在解释模式下这些类型等价于int声明它们不产生任何效果。编译单元跨模块调用的代价compilation_units.rst 解释了编译单元概念一次mypyc调用编译的一组模块构成一个编译单元单元内部使用早绑定多次调用产生多个独立编译单元跨单元引用回退到晚绑定所有调用走较慢的 Python 调用约定参数与返回值都要 boxed。因此为获得最大性能应最小化跨编译单元交互——最简单的做法是把整个程序作为一个编译单元一次编译。性能优化实战技巧performance_tips_and_tricks.rst 总结了实战要点先剖析mypyc 只加速你编译的代码。若 40% 时间花在编译代码之外即使编译代码快 100 倍总体也只快 2.5 倍。可用time.time()粗测或cProfile细测对非编译代码有效。避开慢库若时间集中在少数库功能上考虑用带注解的 Python 重实现并编译或换用更高效的库。补注解至少给性能关键函数/类加注解给被调用但未编译的代码加注解有助于类型推断外部库应选用 stub 覆盖良好的。若不想给外部代码写 stub可对变量做显式注解例如items: List[Tuple[int, str]] acme.get_items()否则items会推断为Any后续操作全部变慢。避开慢特性未受支持的类装饰器/元类、对解释型库的重度依赖、普通 Python 类、非特殊处理的装饰函数、嵌套函数、跨编译单元调用、*args/**kwargs、生成器函数、可调用值未走早绑定等。嵌套函数可用模块级函数或原生类方法替代可调用值可用带单个call(...)方法的原生类实例替代。使用快速原生特性直接调用同编译单元内的编译函数/原生类方法、多数整数与float运算、布尔、原生 list 操作索引、append、列表推导、while 循环、对 range/list 的 for 循环含enumerate/zip、读 dict 项、针对原生类与基本类型及其联合的isinstance、局部变量访问、原生类属性访问、Final 模块级属性访问、字符串相等比较都非常快。注意在 CPython 里把方法缓存到局部变量如append a.append的优化在编译代码中会变慢因为它破坏了早绑定。调整 GC编译不加速循环垃圾回收可用gc.set_threshold(150000)让 GC 少跑在主要计算之前调用。快速解释器退出若进程清理耗时批量任务可调用os._exit(code)跳过清理但必须确保流已 flush、数据已落盘否则可能丢数据。聪明的取舍与其抠低级细节不如优先移除阻止关键类被编译成原生类的元类——一次改动就能让大量方法调用和属性访问整体提速。内部实现IR、代码生成与运行时支持库dev-intro.md 提供了源码级剖析且其描述的模块结构在本文仓库的 mypyc/ 中一一对应编译管线passes用 mypy 做类型检查与推断产出 mypy AST 和类型映射把 mypy AST 翻译为 mypyc 专属的中间表示IR——IR 定义在 ir/ops.py中的Op、BasicBlockrtypes.py中的RType以及func_ir.py、class_ir.py翻译逻辑在 irbuild/入口为main.py插入未初始化变量检查transform/插入异常处理插入显式引用计数增减操作把 IR 翻译成 Ccodegen/用 C 编译器编译生成的 C 代码mypyc.build。值表示int使用带标签指针CPyTagged见 lib-rt/CPy.h 与 lib-rt/int_ops.cbool用char元组用 C 结构体其余对象用PyObject *。生成 C 的结构每个函数编译为原生函数 wrapper 函数两个 C 函数——原生函数接收固定数量、类型正确的 C 参数不做类型检查wrapper 遵循 Python C API 调用约定负责处理参数、类型检查、拆箱并调用原生函数返回值再 box 回 Python 对象。编译函数之间的调用不走模块命名空间直接调用目标原生 C 函数。生成的 C 代码存放在build/__native.c原生函数以CPyDef_为前缀、供解释代码调用的 wrapper 以CPyPy_为前缀。运行时辅助实现集中在mypyc/lib-rt/下CPy.h及int_ops.c、list_ops.c、dict_ops.c、str_ops.c、tuple_ops.c等对应的 C 单元测试在 lib-rt/test_capi.cc基于仓库内捆绑的 googletest。原生操作的定义mypyc.primitives中以数据驱动方式声明针对list、int等基本类型的特化操作编译时通过 AST 匹配自动挑选最合适的实现新增基本类型需要更新mypyc.ir.rtypes、irbuild.mapper.Mapper.type_to_rtype()以及codegen.emit中的emit_box/emit_unbox/emit_inc_ref等系列函数。开发状态与路线图README.md 记录了里程碑式路线图大部分已标记完成支持小而有用的 Python 子集聚焦单模块编译程序其余部分保持解释执行已完成支持把多个模块作为单一编译单元编译/动态链接已完成mypyc 能够编译 mypy 自身已完成优化若干关键性能瓶颈已完成对不支持的 Python 特性生成有用的错误信息而非崩溃或生成坏代码部分完成发布包含编译版 mypy 的 mypy 版本已完成更多特性/兼容性工作与 Python 100% 兼容明确是反目标但比现状更多是好事支持编译 Black 已完成更多优化尤其是代码体积缩减后续待定。dev-intro.md 同时提醒mypyc 目前仍是 alpha 软件仅建议在充分测试、且愿意贡献修复或绕行已知问题时用于生产当前也不检测栈溢出、不在编译代码中处理 Ctrl-C两项都计划未来修复。在本文仓库中的位置与用途本文所述 mypyc 以完整包的形式捆绑在仓库工具链中toolchain/x86_64-linux/lib/python3.11/site-packages/mypyc/与固件构建工具链fbt、site_scons等使用同一套 Python 3.11 环境。包内除了 README.md 和 doc/ 下的全套 RST 文档introduction、getting_started、differences_from_python、native_classes、using_type_annotations、performance_tips_and_tricks、compilation_units、dev-intro、future等还包含已编译的*.cpython-311-x86_64-linux-gnu.so模块、test-data/测试数据与lib-rt/C 运行时源码。这意味着如果你在固件开发之外的工具链脚本中遇到性能瓶颈完全可以在该 Python 3.11 环境内按本文流程安装并使用 mypyc对带类型注解的工具脚本做同样的注解 → 编译 → 加速改造需要注意的是 mypyc 只加速被编译的模块且要遵循上文提到的严格语义约束。【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价