Python 运行时

Python 运行时指的是 使 Python 程序能够执行的一组核心文件。从结构上看,主要包含三部分:

  1. 解释器(interpreter)
  2. Python 标准库(standard library)
  3. 内建模块(built-in modules)
  4. 扩展模块(extension modules)

第三方库(如 numpy、requests)通常安装在 site-packages 中,不属于 Python 自带运行时,但在执行视角下可以视为运行时的一部分。

抽象结构:

1
2
3
4
5
6
Python Runtime
│
├─ interpreter
├─ standard library
├─ built-in modules
└─ extension modules

Python 运行时的目录结构

在 Linux / macOS 的典型结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
python-3.12/
├── bin/
│ └── python3.12
│
├── lib/
│ └── python3.12/
│ ├── os.py
│ ├── pathlib.py
│ ├── importlib/
│ ├── encodings/
│ │
│ ├── lib-dynload/
│ │ ├── _ssl.cpython-312-x86_64-linux-gnu.so
│ │ └── _sqlite3.cpython-312-x86_64-linux-gnu.so
│ │
│ └── site-packages/
│
└── include/
└── python3.12/
└── Python.h

说明:

目录 作用
bin Python 解释器
lib/pythonX.Y Python 标准库
lib-dynload Python 自带的动态扩展模块
site-packages 第三方库安装目录
include C 扩展开发所需头文件

在 Windows 的结构与 Linux 略有不同,但逻辑一致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Python312/
├── python.exe
├── python312.dll
│
├── Lib/
│ ├── os.py
│ ├── importlib/
│ ├── encodings/
│ │
│ ├── site-packages/
│ │
│ └── lib-dynload/
│
├── Scripts/
│ └── pip.exe
│
└── Include/

Python 启动时如何找到库

Python 启动后会构造 sys.path,用于查找模块:

1
2
3
4
5
6
7
[
script_dir_or_cwd,
PYTHONPATH,
stdlib,
lib-dynload,
site-packages,
]

含义:

路径 内容
script_dir / cwd 当前脚本目录或工作目录
PYTHONPATH 用户通过环境变量 PYTHONPATH 指定的额外路径
stdlib Python 标准库
lib-dynload 内置扩展模块
site-packages 第三方库

Python 会根据 解释器所在目录自动推导 最后的三种路径,使其保持固定的相对路径关系。

在导入模块时,例如

1
import requests

Python 会按顺序在这些路径中查找。

设置 PYTHONPATH 环境变量用于添加自定义搜索路径,常用于未安装脚本的临时调试,例如

1
PYTHONPATH=/my/path/to/sth python script.py

pip 安装库时发生了什么

pip 在安装库时,实际就是 将包文件安装到 site-packages,并写入安装元数据。

例如执行:

1
pip install requests

site-packages 会加上如下两个文件夹,分别存储实际的代码和安装元数据。

1
2
3
4
5
6
7
8
9
10
11
site-packages/
├── requests/
│ ├── __init__.py
│ ├── api.py
│ └── ...
│
├── requests-2.32.3.dist-info/
│ ├── METADATA
│ ├── RECORD
│ ├── entry_points.txt
│ └── ...

其中 .dist-info 目录专门记录包的安装信息,例如:

1
2
3
4
METADATA
RECORD
entry_points.txt
WHEEL

作用分别为:

文件 用途
METADATA 包信息
RECORD 安装文件列表
entry_points.txt CLI 工具入口
WHEEL 构建信息

如果一个包定义了 CLI 入口:

1
2
[project.scripts]
tool = "mypkg.cli:main"

安装时 pip 会生成对应的启动脚本/启动程序

1
2
3
4
5
# Linux / macOS
<python>/bin/tool

# Windows
Scripts/tool.exe

它们的作用只是按照安装时提供的说明,主动调用库中的对应函数,简化后的代码类似:

1
2
from mypkg.cli import main
main()

小结

Python 运行时结构:

1
2
3
4
5
6
7
8
Python Runtime
│
├─ interpreter
├─ built-in modules
├─ extension modules
└─ site-packages
├─ packages
└─ .dist-info

关键结论:

  • Python 运行时 = 解释器 + 标准库 + 内建模块和扩展模块
  • site-packages 存放第三方库
  • pip 只是在 site-packages 中 写入包代码和元数据
  • Python 通过 sys.path 查找模块

Python 环境和虚拟环境

Python 环境

系统中可以同时安装多个 Python 运行时,通常是不同版本的 Python 或不同的 Python 实现,但是由于 PATH 的优先级顺序,默认只会使用找到的第一个 Python 运行时。

所谓 切换 Python 环境,本质上是调整查找顺序,让系统使用不同的 Python 运行时。

实现的核心步骤就是修改 PATH,从而改变用户使用 python 或 python3 命令所调用的 Python 解释器。

在使用和安装库时,对应的 site-packages 位置是完全基于 Python 解释器位置进行相对路径推导的,因此切换了 Python 解释器也就附带切换了对应的环境。

环境 = 一个 Python 解释器 + 与其绑定的一组路径(sys.path)

Python 虚拟环境

虚拟环境是 Python 提供的一种隔离机制,用于创建彼此隔离的执行环境。其核心目标是:

为不同项目提供独立的第三方依赖(site-packages)环境。

虚拟环境不是独立的,必须依赖于一个已有的基础环境产生,也就是在一个基础的 Python 运行时中创建,下文中将其简称为 base。

在执行层面,可以将虚拟环境表示为:

1
2
3
4
5
6
7
Virtual Environment
│
├─ interpreter(指向或派生自基础 Python)
├─ stdlib(共享)
├─ built-in modules(共享)
├─ extension modules(共享)
└─ site-packages(独立)

需要强调的是:虚拟环境不等于环境,不会复制完整的 Python 运行时,虚拟环境仅对第三方库(site-packages)进行隔离,共享 base 的标准库和解释器。

由此带来的直接效果是:

  • 如果创建时使用的 Python 运行时被卸载或破坏,由其产生的虚拟环境也会失效。
  • 不能通过创建虚拟环境来切换 Python 版本,虚拟环境中的 Python 版本必然和 base 的 Python 版本是一样的。

虚拟环境的目录结构如下:(习惯上命名为 .venv 或 venv)

Linux / macOS:

1
2
3
4
5
6
7
8
9
10
11
12
.venv/
├── bin/
│ ├── python
│ ├── pip
│ └── activate
│
├── lib/
│ └── python3.12/
│ └── site-packages/
│
├── include/
└── pyvenv.cfg

Windows:

1
2
3
4
5
6
7
8
9
10
11
.venv/
├── Scripts/
│ ├── python.exe
│ ├── pip.exe
│ └── activate.bat
│
├── Lib/
│ └── site-packages/
│
├── Include/
└── pyvenv.cfg

说明:

目录 作用
bin / Scripts 解释器与命令入口
site-packages 第三方库安装位置
pyvenv.cfg 虚拟环境配置核心
include C 扩展编译头文件

虚拟环境的行为由 pyvenv.cfg 控制,核心信息例如

1
2
3
home = /usr/bin
include-system-site-packages = false
version = 3.12.2

作用:

字段 含义
home base 的 Python 解释器位置
include-system-site-packages 是否继承 base 的 site-packages,通常都是 false
version Python 版本

实际的内容可能有所区别,例如 venv 创建的和 uv 创建的虚拟环境,记录的信息不完全一样。

用下面的命令可以激活虚拟环境(对于bash/fish/pwsh有各自的激活脚本)

1
source .venv/bin/activate

激活虚拟环境的实质是下面几件事:

  • 修改环境变量 PATH,使得用户优先找到虚拟环境中的 Python 解释器并使用,可以通过 which 命令查看
1
which python
  • 改变解释器前缀(prefix)

在虚拟环境中运行 Python,sys.prefix 和 sys.base_prefix 会被改变,例如

1
2
sys.prefix       = .venv
sys.base_prefix = path to base Python
  • 改变 sys.path 模块查找路径,尤其是 site-packages 路径,这是虚拟环境隔离第三方依赖的核心机制

Python 启动后构造:

1
sys.path

在虚拟环境中典型为:

1
2
3
4
5
6
7
[
script_dir,
...
stdlib(来自 base Python)
lib-dynload(来自 base Python)
site-packages(来自虚拟环境)
]

因此在虚拟环境中使用的第三方模块是与 base 隔离的。

  • 与上一点相匹配的是安装机制,在使用虚拟环境中的 pip 下载的第三方库时,第三方依赖自然也会被安装到虚拟环境的 site-packages 中

  • 改变 shell 的 prompt,用来提示用户位于哪个虚拟环境

退出虚拟环境的实质就是撤销这部分改动,尤其是对 PATH 和 shell prompt 的改动。

小结

环境和虚拟环境的区别可以概况为:

  • 环境 = 一个 Python 解释器 + 与其绑定的一组路径(sys.path)
  • 虚拟环境 = 一个虚拟的 Python 解释器(指向 base Python) + 与其绑定的一组路径(sys.path),其中 site-packages 是虚拟环境独有的