资讯动态

workerd 中的 Pyodide:把 Python 运行时“编译进” JavaScript 引擎的构建与加载机制

发布时间:2026/9/16 19:37:34 来源:尧图企业网站定制
workerd 中的 Pyodide把 Python 运行时“编译进” JavaScript 引擎的构建与加载机制【免费下载链接】workerdThe JavaScript / Wasm runtime that powers Cloudflare Workers项目地址: https://gitcode.com/GitHub_Trending/wo/workerd本文围绕 src/pyodide/README.md 展开讲解 workerd驱动 Cloudflare Workers 的 JavaScript / Wasm 运行时是如何将 Pyodide 打包成 bundle 嵌入自身、如何裁剪 Pyodide 的包加载机制、以及“为什么要这么做、接下来要怎么做”的设计权衡并结合 src/pyodide/BUILD.bazel、src/workerd/api/pyodide/pyodide.h 与 samples/pyodide/ 的源码和示例说明从构建产物到运行时加载的完整链路以及如何在本地跑通一个 Python Worker。一、这个目录在做什么README 开篇给出的定位非常直接src/pyodide/存放“生成被构建进 workerd 的 Pyodide bundle”所需的全部代码。它由三部分协作完成src/pyodide/目录本身包含生成 bundle 的构建代码Bazel 规则、打包工具、内嵌的 TypeScript/Python 运行时模块。从源码结构看该目录还承担运行时层的角色——AGENTS.md 将其描述为 “Python Workers runtime layer”其中的模块会以pyodide-internal:*的 BUILTIN 模块形式注册。build/BUILD.pyodide目前只做一件事——exports_files(glob([pyodide/*]))即把 Pyodide 官方发布产物下载后落盘到build/pyodide/暴露给 workerd 的构建系统。src/workerd/api/pyodide/pyodide.hC 侧的接入点在开启相应 compatibility flag 时把 Pyodide bundle 加入 module registry让 V8 isolate 能够加载。README 同时说明了一个重要前提该功能需要experimental兼容性开关才能启用。而在当前仓库的示例 samples/pyodide/config.capnp 中实际使用的是专门的 Python Workers 开关const mainWorker :Workerd.Worker ( modules [ (name worker.py, pythonModule embed ./worker.py), ], compatibilityDate 2023-12-18, compatibilityFlags [python_workers, python_workers_20250116], );python_workers是功能总开关python_workers_20250116是绑定到特定 Python 快照发布release的开关。这一点与 src/pyodide/pyodide_extra.capnp 中的 schema 对应每个PythonSnapshotRelease常量携带pyodide版本号、packages包 bundle 日期、backport回退计数、baselineSnapshotHash基线内存快照的 sha256以及flagName/fieldName等字段——也就是说“开关 → 特定 Pyodide/Python 快照版本”的映射是在构建期生成进 capnp schema 的而不是运行时动态决定的。二、为什么要把 Pyodide 编译进 workerd设计权衡README 专门用一节标题提出疑问“Do we really want to be compiling this stuff into workerd?”我们真的想把这套东西编译进 workerd 吗并给出了明确的结论与理由。这一节值得完整理解因为它决定了当前架构的取舍升级主版本风险高。Pyodide 每升一个大版本都可能带来脚本层面的破坏每个 Pyodide 版本都绑定了一个特定的 Python 版本和一份特定的包 lockfile锁定所有包的版本与依赖信息。任何在新 Python 或新版包上会出问题的脚本都会在升级时一起出问题。因此必须允许用户固定pin旧版本。而在开始支持多个 Pyodide 版本之前合理的做法是在运行时动态获取用户所需的 Pyodide 版本而不是把一大堆“可能用不上”的代码都塞进二进制里撑大体积。同理用户使用的 Python 包将来也大概率需要动态获取。现状是“最快跑通”的方案。README 明确承认“The present approach is just the fastest way to get something working.”当前方案只是最快能跑起来的方式。换言之把 Pyodide 静态编入 workerd 是一个有意识的、暂时的权衡先保证功能可用再逐步演进到多版本 动态下载。仓库中的构建配置印证了这一演进方向src/pyodide/BUILD.bazel 顶部从 build/python_metadata.bzl 加载BUNDLE_VERSION_INFO并用BUNDLE_VERSION_INFO[development][real_pyodide_version]作为开发版本名通过alias生成pyodide.capnp.bin、pyodide等重定向目标——“多版本并存、按版本选择 bundle”的框架已经搭好只是当前还只有一个开发版本在生效。三、Pyodide bundle 到底装了什么README 的 “Whats happening here?” 一节说明了 Pyodide 官方发行物的组成以及 workerd 只取其中一部分Pyodide 的发行物包括主“emscripten 二进制”pyodide.asm.js与pyodide.asm.wasm加载器pyodide.jsPython Pyodide 标准库python_stdlib.zip包 lockfilepyodide-lock.json包含发布中构建的所有包的版本与依赖信息……其他东西。workerd 目前只取pyodide.asm.js、pyodide.asm.wasm和python_stdlib.zip并且通过不配置 lockfile 彻底禁用了包加载——因为 workerd 最终需要自己的包管理机制而且这个机制“很可能发生在配置阶段configuration time”。关于加载器的替换README 提到src/pyodide/python.js作为pyodide.js的简化替代它去掉了包加载器和大部分配置但增加了**内存快照memory snapshot**支持。团队希望把内存快照支持上游回 Pyodide但“维护一个自己的精简加载器未来大概率仍有价值”。需要指出的是README 中写的python.js在当前的仓库结构里已经演进为 src/pyodide/internal/python.ts——从 AGENTS.md 的组件表可以确认internal/python.ts是“核心桥Emscripten 初始化、Pyodide 引导、快照编排”并配套internal/snapshot.ts基线/专用快照的收集与恢复、internal/setupPackages.ts与internal/loadPackage.ts包挂载、sys.path、vendor 目录等模块。3.1 版本与 lockfile 在仓库中的落地“每个 Pyodide 版本绑定一份 lockfile”这一约束在仓库中体现得非常具体src/pyodide/python-lock/ 目录按“日期 修订号”命名提交了两份 checked-in 的包 lockfilepyodide-lock_20240829.4.json2024-08-29 发布第 4 次修订pyodide-lock_20250808.json2025-08-08 发布BUILD.bazel 通过exports_files(glob([python-lock/*.json]))将它们导出注释说明其用途是供pyodidemodule extensionbuild/deps/dep_pyodide.bzl读取以便下载 stdlib wheels——“同一 Pyodide 版本 不同包修订”的双坐标pyodide版本号 ×packages日期与上面PythonSnapshotReleaseschema 的字段一一对应也解释了为什么 lockfile 文件名要同时携带日期和修订号。3.2 构建产物如何被“嵌入”helpers.bzl 中的pyodide_extra()、pyodide_static()、python_bundles()三个宏是 bundle 构建的核心python_packages.capnp 定义 stdlib wheel 文件的嵌入 schemapack_python_packages.py 是一个构建期工具把 stdlib wheels 解包、编码进一个PythonPackagescapnp 消息最终嵌入 Pyodide bundle这一流程在 BUILD.bazel 的注释中被直接写明。输出的pyodide.capnp.bin是一个自描述的 capnp 消息对于无法本地构建它的 runner例如交叉编译仓库还提供了一个 alias pyodide.capnp.bin_cross在prebuilt_binaries_arm64配置下直接重定向到预编译产物bin.arm64/tmp/pyodide/pyodide.capnp.bin.aarch64-linux-gnu。C 侧的 pyodide.h 则定义了运行时读取这些嵌入数据的整套接口可以按职责分为几组组件职责PyodideBundleManager按版本存取 Pyodide bundle 数据setPyodideBundleData/getPyodideBundlebundle 以进程级kj::Directory/消息形式持有EmbeddedPackagesReader从 bundle 中定位嵌入的python_packages数据模块getFiles()一次性返回文件布局每个条目携带指向“已解压字节”的ReadOnlyBuffer读取器避免逐文件的 JS↔C 往返拷贝PyodideMetadataReader向 Python 运行时暴露 worker 元数据主模块、模块文件内容getNames/getSizes/read、Pyodide/包版本、lockfile、内存快照hasMemorySnapshot/readMemorySnapshot、compat flags 等ArtifactBundlerMemorySnapshotResult验证器validator在部署验证时收集“初始化完成后的 Python 解释器状态”快照用于加速冷启动区分“基线/通用快照”与“专用快照”DiskCache仅本地开发用把跨 workerd 重启获取的 wheel 等数据落到磁盘缓存SimplePythonLimiterPython 专属的启动期 CPU 时限beginStartup/finishStartup之间超限即抛 TypeError头文件注释中注明目前采用“启动结束后再检查”的事后策略WorkerFatalReporter把 fatal error 上报给 request observerRuntime Analytics此外pyodide.h 末尾声明了 bundle 完整性校验接口computePyodideBundleIntegrity计算 subresource-integrity 风格的sha256-base644校验和verifyPyodideBundleIntegrity在运行时校验下载的 Pyodide bundle 与 release 元数据中的integrity字段一致pyodide_extra.capnp中integrity字段的注释也印证了这一点只有本地构建的 “dev” bundle 跳过校验其他 bundle 若缺失预期校验和本身即为错误。这与第二节“将来支持多版本 Pyodide、动态下载”的规划直接衔接——动态下载的前提首先是可校验。四、从源码结构看运行时加载链路把上述组件串起来可以推断出当前 Python Worker 的加载链路以“从源码结构看”表述逐环节有文件可依入口Python 模块不能直接作为 ES6 入口因此 python-entrypoint.js 作为 ES6 入口被当作用户 bundle 的一部分嵌入。它只做两件事从pyodide:python-entrypoint-helper一个 BUILTIN 模块见 python-entrypoint-helper.ts引入initPython、createImportProxy等并在设置好“动态 import JS 模块”的代理后才调用initPython()。文件头注释解释了为何要这一层间接BUILTIN 模块不能导入 INTERNAL 模块、也不能直接看到用户模块入口文件恰好处于唯一能同时看到两者的位置。桥接internal/python.ts 负责 Emscripten 初始化与 Pyodide 引导从PyodideMetadataReader读取 worker 模块、从EmbeddedPackagesReader获取 stdlib 文件布局并构建只读文件系统由 internal/tarfs.ts 提供再执行用户模块。快照internal/snapshot.ts 完成内存快照的收集与恢复PyodideMetadataReader携带memorySnapshotArtifactBundler携带 validator 阶段产出的快照二者共同实现“验证期生成、运行期恢复”的冷启动加速。约束internal/pool/emscriptenSetup.ts 运行在“vanilla V8 isolate”中不能导入 C 扩展模块见 AGENTS.md 的醒目提示这是 Emscripten 池化与 workerd 自身 isolate 隔离边界之间的实现约束。测试方面AGENTS.md 说明 Python Worker 的测试位于src/workerd/server/tests/python/由py_wd_test.bzl宏展开它处理%PYTHON_FEATURE_FLAGS模板替换、多 Pyodide 版本变体、快照的生成/加载以及每个版本的 compat flag 隔离测试默认sizeenormous每个测试会按支持的每个 Pyodide 版本生成一个变体。五、动手运行 samples/pyodide 示例README 指引“Seesamples/pyodide/for an example using this in its current state.”示例只有两个文件非常小适合本地快速验证整条链路。samples/pyodide/config.capnp 声明了一个监听*:8080的 HTTP socket绑定到名为main的 serviceworker 配置如上节所示pythonModule embed ./worker.py表明 workerd 把 Python 源文件作为模块嵌入compatibilityFlags打开python_workers与python_workers_20250116。samples/pyodide/worker.py 是最小的 Python Workerfrom js import Response from workers import WorkerEntrypoint class Default(WorkerEntrypoint): def fetch(self, request): return Response.new(hello world) def test(): print(Hi there, this is a test)可以看到两个关键 APIfrom js import Response从 JS 侧导入 Web API与from workers import WorkerEntrypointPython SDK 的入口基类fetch是请求处理入口。workers这个 Python SDK 包对应 src/pyodide/internal/workers-api/ 下冻结的 SDK 源码src/workers/__init__.py、_workers.py等AGENTS.md同时说明该 SDK 现已迁至独立的 workers-py 项目并从 PyPI 安装仓库中保留的是向后兼容版本。运行方式需要本地已构建出 workerd 可执行文件例如通过 Bazel 构建//src/workerd/workerd:workerd目标后workerd serve samples/pyodide/config.capnp # 然后 curl http://localhost:8080 # 预期返回hello world注意示例配置没有显式写--experimental因为当前 Python Workers 的开关已由python_workers*系列 compat flag 表达README 中“需要experimental兼容性开关”的表述反映的是早期阶段的状态阅读时应以仓库中当前配置为准。六、下一步自定义链接 Emscripten 二进制README 最后一段给出了明确的性能优化方向同样值得完整保留为了降低内存占用和启动时间我们也希望链接我们自己版本的 Emscripten 二进制pyodide.asm.js和pyodide.asm.wasm。由于 Emscripten 动态链接的限制Pyodide 官方产物被静态链接进去的、很少用到的“垃圾”撑大了体积。希望下一个 Pyodide 版本的发布产物中包含所有编进 Pyodide 的静态库。那样我们就能在构建流程里做一个“修改版的最终链接步骤”丢掉我们不需要的内容。这段规划的工程含义是当前 workerd 消费的是 Pyodide 的成品动态链接 wasm无法裁剪一旦上游把静态库暴露为发布产物workerd 就能在 helpers.bzl 的构建规则里介入最终链接按 Python Worker 实际用到的符号集做静态裁剪。这与第二节“多版本 动态获取”的路径互为表里一个解决“体积/启动”一个解决“版本/依赖”最终让编译进二进制的只剩真正需要的部分。七、小结回到 src/pyodide/README.md 的全局信息它做了什么把 Pyodide 的 emscripten 二进制 stdlib 打成一个 capnp bundle 编入 workerd用自研精简加载器internal/python.ts一族模块替代官方pyodide.js砍掉包加载器并新增内存快照它为什么这么做升级 Pyodide 大版本有破坏性风险必须支持多版本 pin 与动态获取而静态编入是“最快跑通”的过渡方案它现在长什么样python-lock/双 lockfile、pyodide_extra.capnp的 release 元数据、pyodide.h的 bundle 管理/完整性校验/快照接口以及 samples/pyodide/ 可运行的最小示例它要去哪里动态下载多版本 Pyodide bundle、配置期包管理、自定义最终链接以裁剪 emscripten 产物。想继续深入时建议按顺序阅读src/pyodide/BUILD.bazel 与 src/pyodide/helpers.bzl构建与 bundle 装配、src/pyodide/pyodide_extra.capnprelease 元数据 schema、src/workerd/api/pyodide/pyodide.hC 运行时接口、src/pyodide/internal/python.ts运行时桥接实现以及src/workerd/server/tests/python/下的测试。【免费下载链接】workerdThe JavaScript / Wasm runtime that powers Cloudflare Workers项目地址: https://gitcode.com/GitHub_Trending/wo/workerd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

免费获取报价