前言
“如果当年我把import扔进垃圾桶,今天就不会有pip、也不会有PyPI。”
2003年PyCon的闭门圆桌上,Guido这句话一出口,全场瞬间安静下来。
Python 1.x的模块系统被无数人视作开创性的设计,但在它的缔造者眼里,这其实是一场险些失控的“工程事故”——嵌套包、相对导入、循环引用这些后来让无数开发者头疼的问题,早在1994年就已经埋下了祸根。

败笔的种子:1991年的简单设想如何走向失控
1991年的import foo,核心代码只有两行:
第一行,把foo.py读进来;
第二行,把名字foo绑定到当前命名空间。
看起来简洁优雅,但这套设计留下了三个“无法回头的漏洞”:
- 无命名空间隔离:所有模块平铺在同一张表里,
foo.bar与bar.foo很容易互相冲突。 - 无版本信息:
foo.py一升级,旧脚本直接静默崩溃,连个报错的机会都没有。 - 搜索路径黑箱:
PYTHONPATH的优先级硬编码在C代码里,用户只能靠“猜”才知道模块到底从哪里加载来的。
到了Python 1.2发布时,标准库已经有78个.py文件,平铺命名空间的冲突率首次突破了30%。
Guido在邮件列表里写下了一句话,现在看来几乎是某种预告。
差点被砍:1994年的重构之战
1994年5月,社区爆发了所谓的“包大爆炸”事件。
第三方开发者一股脑地把sound、image、crypto全部塞进了顶级命名空间,官方库在一夜之间就被挤到了用户的“第二页”。
Guido提交了史上最具争议的一份PEP——“PEP 0:废除import”。
核心提案只有一句话……
反对者在两周内回了400多封邮件,核心论点直击要害:
第一,失去“一次编写,到处运行”的易用性;
第二,破坏向后兼容,等于让所有Python 1.x用户重写代码。
最终投票结果:反对73%,支持27%。
import保住了,但留下了一堆妥协的补丁:
- 1995年的Python 1.4引入了包目录
__init__.py,首次允许嵌套命名空间。 - 搜索路径改为可配置的
site-packages,把第三方包与标准库隔离。
循环炼狱:相对导入的历史幽灵
包目录解决了命名冲突,却带来了新的地狱——相对导入。
1996年的Python 1.5允许from .foo import bar,初衷是“让子包自给自足”。
但早期的实现把相对路径写进了编译期常量,导致了一个严重的问题:
/* import.c */
static char *relative_base = NULL; /* 线程不安全 */
多线程环境下,两个包同时触发相对导入时,relative_base会被覆盖成野指针,解释器直接段错误。
一直到2000年的Python 2.0才用绝对导入修复了这个bug,但语法级别的歧义却留了下来:
from foo import bar # 这到底是指顶级的foo,还是当前包的foo?
为了向后兼容,Python 2默认优先使用相对导入,导致整整十年里,“绝对导入”必须写成一种“魔法”形式:
from __future__ import absolute_import
隐藏彩蛋:import钩子如何让PyPI成为可能
说来也讽刺,模块系统的“败笔”反而催生了生态上的奇迹。
1998年,Distutils的作者Greg Ward利用import钩子(PEP 302的前身),把.egg文件塞进了sys.meta_path,首次实现了“复制即安装”。
2005年,setuptools把这个钩子升级为easy_install;2008年,pip沿用同一套机制,最终让PyPI成长为全球最大的开源软件仓库。
如果没有1.x时代留下的“过度灵活”,Python不可能在2010年之前就拥有超过10万个第三方包。
尾声:今天的import,仍在偿还1.x的技术债
2020年,Python 3.9引入了“命名空间包”,彻底废除了__init__.py,看起来终于解决了目录层级的问题。
但核心开发者Brett Cannon在博客中坦言:这套系统的负担远比想象中要重。
从1991年的两行代码,到2024年2000行的C语言实现,Python的模块系统像一棵老树:根是败笔,枝桠却撑起了整片森林。
下次你在PyPI上一键pip install的时候,不妨想一想——
如果当年Guido真把import扔进了垃圾桶,今天的Python,还会是你认识的模样吗?
