不少人在给纯 Python 包上 PyPI(Python 官方包仓库)时,像给一封家书订远洋货轮:集装箱刷得锃亮,收件地址却没写。我判断:纯 Python 包缺的不是 WASM(浏览器里跑的 WebAssembly 格式),是 py3-none-any(Python 3 通用、与平台无关的轮子包标记)。

这话听上去像抬杠,其实是在把问题拨回原位。大家一听 Pyodide(把 Python 搬进浏览器的一套运行环境),脑子里就敲起“新平台、新架构、新轮子”的锣,好像不给它专门造一份浏览器版安装包,就显得不专业。可纯 Python 的要害,恰恰在“不认平台”。只要里面没有 C 扩展(需要本地编译的那部分铁件),它就是写给解释器看的,不是写给芯片看的。对这种包,真正值钱的不是“为浏览器再编一次”,而是“明确告诉安装器:这东西本来就哪儿都能装”。

给纯 Python 包发 WASM,像给家书订货轮。

晚上十一点,一个前端工程师把团队的日期解析小库塞进浏览器里的练习环境。代码不到五百行,清一色 .py 文件,在自己电脑上装得稳稳当当。到了 Pyodide,安装失败,报错一路指向构建流程。他第一反应是:得补一个 WASM 轮子包。折腾两天,才发现仓库只上传了源码包(只给原材料,不给成品),没有上传通用 wheel(预打包安装文件)。浏览器不是不会跑这五百行 Python;它只是不该在一个页面里临时搭脚手架,替你现场造轮子。

浏览器装不上,不是因为它不会读 Python,而是因为它不该在页面里开一家包工厂。

第二个场景更像技术圈的集体幻觉。一个维护者看见别人的 PyPI 页面下面挂着 Linux、macOS、Windows 一排“勋章”,就认定到了 Pyodide,也该再挂一个浏览器平台的勋章才算跟上时代。于是她把自动打包流程改成一张铺天盖地的表,研究怎么替浏览器单独打包,补环境变量,修缓存,忙得像在造船厂开工。最后生成出来的,仍然只是几份 .py 文件。她不是在解决问题,她是在给纸箱申请护航舰队。

很多工程问题不是性能问题,是身份问题。

py3-none-any 不是贴纸,它是包作者给所有平台签的一纸通行证。py3 说明这是给 Python 3 的,none 说明它不依赖某个平台专属的编译产物,any 说明不认操作系统和处理器。对纯 Python 包,这不是“额外优化”,这是它应得的户口本。你不发这份通行证,安装器就只好把它当成“也许需要现场施工”的货物,于是去找构建工具、去拉打包后端、去碰浏览器里最不该碰的那堵墙。墙不是新长出来的,是你自己绕过去了。

第三个场景最有意思,因为它暴露的不是技术,而是习惯。一个做在线教学的平台,把 Python 练习环境全搬进了浏览器。学生点“安装依赖”,几十个包一路绿灯,偏偏卡在一个最不起眼的小工具库上:字符串处理、日期格式化、几百行代码,连个本地扩展都没有。老师以为是 Pyodide 支持不全,学生以为是浏览器不可靠,产品经理以为是“前沿技术还不成熟”。其实都错了。错的是那个包作者太习惯“源码也能装”,忘了浏览器不是你的开发机,不会替你兜底。

技术圈最爱把文书问题演成军备竞赛。

这就是反常识的地方。大家总以为,到了 WASM 时代,包分发的问题一定是“编译能力不够新”;恰好相反,纯 Python 包在 Pyodide 里最常见的阻塞,往往是“分发姿势不够旧”。不是缺炮舰,是缺门牌;不是缺引擎,是缺车票;不是缺跨平台技术,是缺承认自己本来就跨平台的诚实。

WASM 是引擎,py3-none-any 是车票;乘客没票,再新的引擎也开不出站。

我对这事的判断很明确:对纯 Python 包来说,急着发布 WASM wheel(面向浏览器平台的轮子包),多数时候是在给自己加戏。你真正该做的,是把通用 wheel 发出来,把依赖链里那些本来不需要编译的部分,老老实实标成 py3-none-any。这不是保守,这是把技术当物流,而不是当烟火。

一个时代的荒唐,往往不在它没有大机器,而在它把每一张通行证都办得暧昧。软件世界也一样。人们迷信 WASM,是因为“新大陆”听起来像事业;人们轻视 py3-none-any,是因为它看上去只是手续。可真正决定货能不能到、人能不能走、系统能不能顺滑转起来的,常常不是海上的巨轮,而是码头上那张写清楚了目的地的单子。

纯 Python 包缺的不是 WASM,是作者肯不肯把“这东西本来就通用”写成一个谁都认账的事实。