pycache 与 pip 缓存陈旧速查:改了代码行为不变的清缓存清单
更新时间:2026-10-24。FAQ 速查页,面向「明明改了代码,跑起来还是老样子」的同学。pycache 与 pip 缓存陈旧是 Python 日常开发里最败人品的一类问题:它不报错,只是静默地让你跑旧代码——你在源文件里加的 print 不出现、改的逻辑不生效、装的新版本行为还是旧的。根源在于 Python 生态到处都有缓存层:解释器把
.py编译成.pyc存进__pycache__,pip 把下载的 wheel 存进~/.cache/pip,pip install -e .用链接代替拷贝,打包工具在build/、*.egg-info里留中间产物。任何一层缓存「该失效没失效」,你看到的就是幽灵般的旧行为。本篇给六类缓存陈旧的识别与清理口径,核心原则:怀疑缓存时,清缓存永远比怀疑人生便宜。
本文要回答的问题
- 我改了源文件,为什么 print 没出来、行为也没变?
pip install明明显示装的新版,为什么import到的还是旧的?pip install -e .不是「改了立即生效」吗,为什么我这儿不生效?- 删了
.py文件,为什么它的代码还能被 import? - 一键清干净所有 Python 缓存的命令是什么?
一、报错原文与典型现象
缓存陈旧不报错,它表现为「行为与代码对不上」(工程路径以 /proj/demo 占位):
$ vim /proj/demo/app.py # 加了一行 print("new version")
$ python3 /proj/demo/app.py
# 输出里没有 new version——改动像不存在
$ find /proj/demo -name "__pycache__" -exec rm -rf {} +
$ python3 /proj/demo/app.py
new version # 清完缓存,改动立刻生效$ pip install requests==2.32.3
Successfully installed requests-2.32.3
$ python3 -c "import requests; print(requests.__version__)"
2.31.0
# 装的是新版,import 到的是旧版——site-packages 里有残留或多个环境串了$ pip uninstall requests
$ python3 -c "import requests; print(requests.__file__)"
/proj/demo/requests.py
# 卸载后还能 import——工程目录里有同名残留,或 editable install 链接没断认清机制一句话:Python 的每一层缓存都有自己的失效判据(.pyc 看源文件 mtime+大小,pip 缓存看 URL+hash,editable 看链接路径),判据失效判不出来时,陈旧内容就会被当成新鲜内容用。清缓存是让它重建,不是破坏。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| 改了源码行为不变 | __pycache__ 里 .pyc 陈旧 | 删 __pycache__ 后重跑对比 | find . -name __pycache__ -exec rm -rf {} + |
| 删了 .py 还能 import | 残留 .pyc 被直接当脚本跑 | 目录下找同名 .pyc | 连源带缓存一起删干净 |
| 装新版 import 旧版 | site-packages 残留 / 环境错位 | pip show 与 __file__ 对路径 | 卸干净重装,确认同一解释器 |
| pip 装的 wheel 是旧构建 | pip 缓存命中旧 wheel | pip cache list 包名 | pip cache remove 包名 或 --no-cache-dir |
| editable install 改动不生效 | 老式 setup.py develop 链接失效 | pip show -f 包名 看 Location | 重装 pip install -e .(pyproject 时代) |
| 打包发版内容缺新文件 | build/、*.egg-info 残留旧清单 | 看 sdist 里有没有新文件 | 删 build 产物再打包 |
三、排查决策图
这张图是缓存陈旧的标准动线:红是诡异现象与定罪根因,蓝是排查动作,绿是验证通过,橙是清理动作,紫是防复发沉淀。
读这张图抓住主线:缓存问题的排查不是「猜哪层缓存坏了」,而是先用 __file__/__version__ 确认实际加载的是哪份,再沿着「pyc → pip 缓存 → editable 链接 → 打包残留」四层逐层对。打印出来的路径和版本会自己招供,剩下的只是「清哪一层」的执行问题。
四、逐条排查展开
第一病因:__pycache__ 时间戳失效。 解释器 import 时若发现 __pycache__/xx.cpython-311.pyc 存在,会比对源文件的 mtime 和大小,一致就直接用旧字节码。绝大多数情况这个机制是对的,但跨机器拷贝文件、git checkout 切分支、解压归档这些操作会让 mtime 失真——源文件内容变了、mtime 却可能巧合一致(或时钟回拨),旧 pyc 就被继续用。处置一刀切:
$ find /proj/demo -type d -name "__pycache__" -exec rm -rf {} +
$ find /proj/demo -name "*.pyc" -delete
# 删光重建,无副作用第二病因:删了 .py 但 .pyc 还在。 删除了源文件却没清缓存,旧 .pyc 仍可能被执行(直接 python3 __pycache__/xx.pyc 或被打包工具收进去)。表现为「文件都没了,功能还在跑」,排查时极具迷惑性。处置:删源文件时连 __pycache__ 一起清;这也是改名/移动模块后的标准动作。
第三病因:pip 缓存命中旧 wheel。 pip 下载 wheel 后按 URL 缓存在本地,下次装同版本直接复用。绝大多数情况是加速,但两种场景会坑:一是私有源上同版本号被重新打包(1.0.0 覆盖了旧的 1.0.0),pip 还拿缓存里的旧构建;二是缓存条目损坏。处置:
$ pip cache list requests # 看缓存里有哪些版本
$ pip cache remove requests # 定向清
$ pip install --no-cache-dir requests==2.32.3 # 本次装不用缓存补一个高发的混合现场:在 Docker 镜像或 CI 缓存目录里,__pycache__ 被打进了镜像层,源码层更新了、缓存层没失效,容器里跑的就是上一版的字节码。判别方法是看 __pycache__ 目录的修改时间是否比源文件还新、容器内 print(模块.__file__) 路径是否与挂载的源码一致;处置是 Dockerfile 里加 ENV PYTHONDONTWRITEBYTECODE=1 禁止运行期写 pyc,并把 __pycache__ 写进 .dockerignore,构建镜像时不带入旧缓存。
第四病因:装了新版 import 旧版(环境错位)。 pip 和 python3 指向的不是同一个环境:系统 Python、venv、conda 各有一份 site-packages,pip install 装进了 A 环境,python3 跑的是 B 环境。验证一句口诀——装与跑用同一个解释器:
$ python3 -m pip install requests==2.32.3 # 用 python3 -m pip 而不是裸 pip
$ python3 -c "import requests; print(requests.__version__, requests.__file__)"处置:永远用 python3 -m pip 代替 pip,从根上消灭「装错环境」;残留旧版本先 pip uninstall 反复执行到提示不存在再装。
第五病因:editable install 失效。 老式 setup.py develop 用 .egg-link + easy-install.pth 链源码目录,目录被移动/重命名后链接即失效,之后 import 到的是悬空路径或残留拷贝,改源码不再生效。pyproject 时代(PEP 660)的 editable 靠 hook 注入,稳健得多但也怕「装完又换了构建后端」。处置:源码目录结构变动后,一律重装:
$ pip uninstall mypkg
$ pip install -e /proj/demo
$ python3 -c "import mypkg; print(mypkg.__file__)" # 必须指向 /proj/demo 里的源码第六病因:打包残留旧清单。 setup.py sdist/bdist_wheel 会在 build/、*.egg-info/ 里缓存文件清单(SOURCES.txt),新建的文件不打进包里——发版后用户发现「新版缺文件」。这是打包工具最著名的坑。处置:发版脚本里先 clean 再构建:
$ rm -rf build/ dist/ *.egg-info
$ python3 -m build现代 python -m build 默认在隔离环境构建,比老式 setup.py sdist 干净,但仍建议先删残留。
五、规范做法与预防
- 缓存目录进 .gitignore:
__pycache__/、*.pyc、build/、dist/、*.egg-info/五行标配,新仓库模板内置。 - 装包装卸一律
python3 -m pip:从命令形态上锁死「装与跑同一解释器」。 - 发版脚本先 clean 再 build:
rm -rf build dist *.egg-info && python3 -m build写进发布 checklist。 - 怀疑缓存先清再查:
__pycache__删了零成本,「清缓存后问题还在」才值得深入排查——这条顺序能省掉一半的幽灵排查。 - venv 一次性化:环境行为诡异时
rm -rf .venv && python3 -m venv .venv重建,比逐层修残留快得多。 - 团队共享一页清缓存清单:把本篇速查表的命令整理成 wiki 一页,新人遇到「改了不生效」先照单清一遍再提问,沟通成本立省一半。
这一篇在知识体系里的位置
本篇是 Python 排错速查簇的缓存层篇:装不上包看 pip install 失败速查 与 pip 依赖冲突装不上速查;同名遮蔽(另一类「import 到的不是你以为的那份」)看 Python 模块重名遮蔽标准库速查;模块找不到看 Python 模块找不到速查;版本语义问题看 Python 版本不兼容报错速查;行为飘忽的测试问题看 Python 测试偶发失败速查。「先确认加载的是哪份、再怀疑缓存」这条心法对所有带构建缓存的工具链(node_modules、Maven、Go build cache)通用。
一句话总结
缓存陈旧的本质是 pyc、pip 缓存、editable 链接、打包清单四层缓存各自的失效判据偶尔失灵,陈旧内容被静默当成新鲜内容用;六因处置——find . -name __pycache__ -exec rm -rf {} + 清字节码、删源文件连缓存一起删、pip cache remove 或 --no-cache-dir 清 wheel、python3 -m pip 锁死装跑同一解释器、目录变动后重装 editable、发版前 clean build 残留;定位口诀是「先 print(__file__) 确认加载的是哪份」,防复发主线是缓存目录 gitignore、发版脚本先 clean、环境诡异时重建 venv。