跳到正文

01 运行机制与开发环境 ​

一句话:CPython 把源码编译成与解释器版本绑定的字节码,再由虚拟机逐条执行;Python 的跨平台来自源码和解释器,不来自 .pyc。环境问题的本质,是让解释器、包、版本锁三者对齐。

适用:CPython 3.11+。3.13/3.14 的可选构建(free-threading、实验性 JIT)见第 08 篇与官方文档。

为什么需要 ​

三个故障几乎都源于同一个原因:机器上有多个「Python」,而代码、pip、编辑器各认一个。

  • pip install requests 成功,运行却报 ModuleNotFoundError:包装进了另一个解释器的 site-packages。
  • 同一份代码在同事机器上能跑,自己这里报 ImportError:解释器版本或依赖版本不同。
  • 改了源码,行为却没变:加载到了旧的 .pyc,或运行的不是当前目录的文件。

Python 的动态类型和 C 扩展依赖,让「版本」不只是包版本,还包括解释器版本、ABI、编译选项。只靠全局 site-packages 无法同时满足多个项目,所以需要隔离环境;只靠 requirements.txt 的宽范围也无法复现构建,所以需要锁文件。

核心机制 ​

源码到执行 ​

python app.py 的主路径是:

几个可验证的结论:

  • 编译是自动的,但只编译到字节码,不生成机器码。CPython 的字节码由解释循环执行,不是 CPU 直接执行。
  • 主脚本不生成 .pyc。.pyc 只缓存被 import 的模块,缓存在模块旁的 __pycache__/。python app.py 重复启动仍会重新编译 app.py。
  • 可以用 dis.dis(func) 看字节码,用 python -X importtime app.py 看导入耗时。
  • 想禁止写缓存:python -B app.py 或环境变量 PYTHONDONTWRITEBYTECODE=1。

订正:.pyc 不是跨平台产物 ​

.pyc 的头部包含 magic number,绑定 CPython 版本与部分编译选项;不同大版本通常不能复用,解释器会忽略或重新编译。因此:

  • 跨平台的是 .py 源码;目标机器用自己的解释器重新编译,不保证能直接搬 .pyc。
  • 把 .pyc 当发布物会踩版本错配;分发用 wheel/sdist,不用 __pycache__。
  • 判断是否加载了旧缓存:比较源文件与 .pyc 的 mtime;清理用 find . -name __pycache__ -type d -prune -exec rm -rf {} +,或临时 python -B 验证。

同一份源码能在 Windows/macOS/Linux 跑,是因为各平台都装了兼容的 CPython,不是因为字节码跨平台。具体缓存策略以官方文档 importlib 章节为准。

模块、包与导入 ​

  • import x 按 sys.path 顺序查找:脚本目录、PYTHONPATH、标准库、site-packages。
  • 包是带 __init__.py 的目录(命名空间包可以没有);python -m pkg.mod 会把包根放进 sys.path,比 python pkg/mod.py 更可靠。
  • if __name__ == "__main__": 区分「被导入」和「被直接运行」,多进程在 Windows 上尤其需要它(见 08)。

虚拟环境解决什么 ​

python -m venv .venv 创建一个目录,里面有指向基础解释器的链接和独立的 site-packages。激活脚本主要改 PATH,不会改变已启动进程的解释器。因此最稳的安装方式是:

bash
python -m pip install requests

用 python -m pip 而不是裸 pip,保证包装进当前解释器。验证:

bash
python -c "import sys; print(sys.executable)"
python -m pip show requests

虚拟环境不是安全边界,也不解决跨 Python 版本兼容;它只隔离包目录。

依赖锁定解决什么 ​

requirements.txt 常写范围,适合表达「兼容哪些版本」;锁文件写精确版本(通常带哈希),适合复现构建。两者职责不同:

文件内容用途
requirements.in / pyproject.toml直接依赖 + 版本范围人维护的意图
requirements.lock / uv.lock / poetry.lock全量精确版本 + 哈希CI/部署复现

常见做法:pip-compile 从 .in 生成 .lock;uv lock + uv sync 一条链完成;pip freeze 只能导出当前环境,不能表达直接/间接依赖,适合临时快照。Python 版本也要锁:.python-version 或容器基础镜像。

从 Java / Go 转过来,先改这六处直觉 ​

旧语言直觉Python 的实际行为
大括号分块,块级作用域缩进分块;作用域是函数/类/模块级,if/for 里赋的变量在外面可见
b = a 复制一份对 list/dict/set 是共享引用;要独立副本用 .copy(),嵌套用 deepcopy
下标循环遍历for x in data 走迭代器协议;要下标用 enumerate
返回错误码,逐层判断抛异常,在能处理的边界 try/except;raise ... from e 保留根因
多线程等于并行计算CPython 有 GIL,纯 Python 计算要用多进程或 free-threading 构建
goroutine 与 asyncio 都是「协程」Go 是 M:N 调度、可在抢占点切换;Python asyncio 是单线程协作式,遇到同步阻塞会卡住整个循环

并发细节见 08;这里只保留会直接影响日常写法的差异。

最小上手 ​

在空目录执行,预期输出 requests 版本和一段字节码:

bash
python --version                       # 3.11 或更高
python -m venv .venv
source .venv/Scripts/activate          # Windows Git Bash
# macOS/Linux: source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install "requests==2.32.3"
python -c "import sys, requests; print(sys.executable); print(requests.__version__)"

python - <<'PY'
import dis
def add(a, b):
    return a + b
dis.dis(add)
PY

python -m pip freeze > requirements.lock

dis.dis 会打印 LOAD_FAST、BINARY_OP、RETURN_VALUE 等指令。看到这些说明当前解释器、包路径、字节码链路都正常。

生产动作 ​

  • 解释器固定:项目根放 .python-version;容器用固定 tag,如 python:3.12-slim,不要用 latest。
  • 安装入口固定:CI 与本地都用 python -m pip install -r requirements.lock;部署前跑 python -m pip check。
  • 缓存控制:PYTHONDONTWRITEBYTECODE=1(禁用写缓存)、PYTHONPYCACHEPREFIX=/tmp/pycache(集中缓存目录)。默认不设置,按需启用。
  • 排障三段:
    • 现象:ModuleNotFoundError → 命令:python -c "import sys; print(sys.executable)"、python -m pip show 包名、which python → 判断:三者指向的解释器是否一致。
    • 现象:改了代码不生效 → 命令:python -B app.py、检查 __pycache__ 的 mtime → 判断:是否加载了旧 .pyc 或错误路径的模块。
    • 现象:CI 与本地依赖不同 → 命令:pip freeze、pip check、对比锁文件 → 判断:是否有人绕过锁文件安装,或锁文件未提交。
  • 不要在系统 Python 里 pip install 项目依赖;不要提交 .venv/;不要把 __pycache__/ 当发布产物。

边界 ​

  • .pyc 不是分发格式:版本绑定,且不包含依赖;跨机器分发用 wheel,源码分发用 sdist。
  • venv 不是万能隔离:它不隔离解释器版本,也不隔离系统库;需要更强隔离时用容器。
  • 动态类型不等于不检查类型:边界处用类型注解 + 静态检查(09),运行时校验用 Pydantic v2(09)。
  • free-threading 不是默认行为:3.13 起是实验性可选构建,3.14 起为官方支持的独立构建;未适配的 C 扩展可能重新启用 GIL,详见 08 与官方文档。
  • 跨平台结论要带前提:路径、编码、文件锁、信号语义在 Windows 与 POSIX 上不同;示例默认 UTF-8 和 Git Bash。

讲清楚 ​

Python 的执行不是「边读源码边解释」,而是先编译成字节码,再由 CPython 虚拟机执行;.pyc 只是导入缓存,绑定解释器版本,不能当跨平台产物。跨平台的是源码和兼容的解释器。环境问题的根因通常是「解释器、包、版本锁」三者没有对齐:venv 隔离包目录,锁文件固定全量版本,python -m pip 保证装进当前解释器。排查时先打印 sys.executable,再看 pip show 和锁文件,最后才怀疑代码。GIL 和 free-threading 是另一层:它们影响并发模型,不影响「代码能否跑起来」。

适用 CPython 3.11+ | 初稿 2026-08,2026-10 重写

把一门技术讲透到既懂也会用