Python 基础体系 · 第 63/112 篇。示例统一以 Python 3.14 为语言基线;第三方库使用与其兼容的现代稳定版本,版本敏感行为会单独说明。
Python 虚拟环境:venv、解释器隔离、路径和常见污染
Python 虚拟环境解决的不是“如何安装一个包”,而是一个更基础的问题:
当多个项目依赖同一个包的不同版本时,如何让每个项目使用自己的一组解释器、包和命令,而不相互覆盖?
例如:
- 项目 A 需要
requests==2.31.*; - 项目 B 需要
requests>=2.32; - 系统环境中还可能存在其他项目安装的
requests; - 当前 shell 里可能设置了
PYTHONPATH; - 编辑器、测试工具和终端可能实际调用不同的 Python。
如果这些对象最终汇聚到同一个 site-packages 或同一条 sys.path 中,安装顺序就会影响运行结果。虚拟环境通过改变解释器的安装前缀和包搜索路径,把项目的运行时依赖拆分到不同目录中。
1. 先区分四个经常混用的概念
1.1 Python 解释器
解释器是实际执行 Python 字节码、导入模块和启动程序的可执行文件,例如:
/usr/bin/python3
/usr/local/bin/python3.14
/home/alice/.venvs/demo/bin/python
C:\Users\Alice\AppData\Local\Programs\Python\Python314\python.exe
运行下面的命令时,真正决定程序使用哪个 Python 的,是被操作系统找到并执行的那个文件:
python app.py
命令名 python 只是一个查找入口。它可能来自:
- 当前虚拟环境的
bin/python或Scripts\python.exe; - 系统安装目录;
- 用户级 Python 管理器;
- Conda、pyenv 或其他版本管理器;
- shell alias 或函数。
因此,看到终端提示符中出现了 (.venv),并不能单独证明后续命令一定调用了预期解释器。可靠判断应直接检查解释器路径:
python -c "import sys; print(sys.executable); print(sys.version)"
预期结果类似:
/home/alice/project/.venv/bin/python
3.14.0 (...)
在 Windows PowerShell 中:
python -c "import sys; print(sys.executable); print(sys.version)"
如果输出的是系统 Python,而不是项目目录下的 .venv,那么当前 shell 并未使用目标虚拟环境。
1.2 Python 包
包通常指可以被 import 的代码集合,例如:
import requests
但“导入名”和“安装名”不一定相同:
| 安装时的 distribution 名称 | 导入时的模块名 |
|---|---|
Pillow |
PIL |
beautifulsoup4 |
bs4 |
python-dateutil |
dateutil |
Python 包管理器安装的是 distribution distribution,发行包;Python 导入系统查找的是 sys.path 中的模块和包目录。
这一区分对于诊断污染很重要:
python -m pip show Pillow
python -c "import PIL; print(PIL.__file__)"
前一条检查发行包元数据,后一条检查实际被导入的文件。二者路径不一致时,通常存在解释器错用、路径覆盖或残留文件。
1.3 虚拟环境
Python Packaging Authority 将虚拟环境定义为:供特定应用使用、相对于系统范围安装更隔离的 Python 环境。标准虚拟环境模型由 PEP 405 引入;它通常包含一个 Python 可执行文件、一个独立的 site-packages 和一个 pyvenv.cfg 文件。虚拟环境通常共享基础 Python 的标准库,但拥有自己的第三方包目录。(packaging.python.org)
因此,虚拟环境不是:
- 一台虚拟机;
- 一个容器;
- 一个新的操作系统;
- 一份完整复制的 Python 安装;
- 自动锁定依赖版本的机制。
它主要隔离的是:
- 项目使用的 Python 入口;
- 第三方包安装位置;
- 由包安装生成的命令行脚本;
- 解释器用于计算安装路径时的前缀。
1.4 激活
激活是修改当前 shell 状态的一组脚本操作,典型效果包括:
- 将虚拟环境的可执行文件目录放到
PATH前面; - 设置
VIRTUAL_ENV; - 修改命令提示符;
- 使
python和pip更可能解析到虚拟环境。
激活不是虚拟环境本身,也不是 Python 解释器的运行模式。Python 程序即使没有激活,也可以直接使用虚拟环境中的解释器:
.venv/bin/python app.py
.venv/bin/python -m pip install requests
Windows PowerShell:
.\.venv\Scripts\python.exe app.py
.\.venv\Scripts\python.exe -m pip install requests
Python 文档明确指出,激活步骤是可选的;判断程序是否处于虚拟环境中,应使用解释器运行时的前缀,而不能依赖 shell 激活带来的变化。(packaging.python.org)
2. venv 创建了什么
在 Python 3.14 中,最基本的创建命令是:
python3.14 -m venv .venv
这里有一个容易被忽略的因果关系:
venv使用的是执行python3.14 -m venv的那个解释器版本。
如果执行命令的是 Python 3.14,环境中的 Python 就以这个 3.14 解释器为基础;如果执行命令的是 Python 3.13,环境就不会因为目录名叫 .venv 而自动变成 3.14。Python 教程也明确说明,venv 使用执行命令时的 Python 版本。(docs.python.org)
创建后,Unix 系统大致会得到:
project/
├── .venv/
│ ├── bin/
│ │ ├── python
│ │ ├── pip
│ │ └── activate
│ ├── include/
│ ├── lib/
│ │ └── python3.14/
│ │ └── site-packages/
│ ├── lib64 -> lib
│ └── pyvenv.cfg
└── app.py
Windows 的目录名称通常是:
project\
├── .venv\
│ ├── Include\
│ ├── Lib\
│ │ └── site-packages\
│ ├── Scripts\
│ │ ├── python.exe
│ │ ├── pip.exe
│ │ └── Activate.ps1
│ └── pyvenv.cfg
└── app.py
pyvenv.cfg 是关键元数据文件,内容通常类似:
home = /usr/local/bin
include-system-site-packages = false
version = 3.14.0
其中:
home指向创建该环境时所使用的基础 Python 安装位置;include-system-site-packages = false表示默认不搜索系统级site-packages;version描述创建时的 Python 版本信息。
标准文档将虚拟环境视为可丢弃、不可移动的目录:不应把项目代码放进去,也不应把它当作可复制部署物。目标位置变化后,应该重新创建环境。(docs.python.org)
3. venv 并没有复制整个 Python
虚拟环境的“隔离”需要精确定义。
假设:
基础 Python:
/opt/python/3.14/
项目虚拟环境:
/home/alice/project/.venv/
创建 .venv 后,虚拟环境通常拥有:
/home/alice/project/.venv/bin/python
/home/alice/project/.venv/lib/python3.14/site-packages/
但标准库通常仍来自基础 Python:
/opt/python/3.14/lib/python3.14/
可以把它抽象为:
其中:
- :虚拟环境;
- :环境自己的解释器入口;
- :环境自己的第三方包目录;
- :基础 Python 提供的标准库;
- :基础 Python 安装位置。
默认情况下:
而不是:
这就是“共享标准库、隔离第三方包”的基本模型。PyPA 虚拟环境规范也将“独立 Python 二进制文件、独立包目录、共享标准库”作为标准模型的核心组成。(packaging.python.org)
这意味着:
- 删除虚拟环境通常不会删除基础 Python 的标准库;
- 基础 Python 被删除或升级到不兼容状态,旧虚拟环境可能失效;
- 安装包到
.venv通常不会修改系统site-packages; .venv不是完全自包含的二进制分发物。
4. sys.prefix 如何标识虚拟环境
Python 运行时有两组重要的前缀:
import sys
print("sys.executable:", sys.executable)
print("sys.prefix:", sys.prefix)
print("sys.base_prefix:", sys.base_prefix)
print("sys.exec_prefix:", sys.exec_prefix)
print("sys.base_exec_prefix:", sys.base_exec_prefix)
在基础环境中,通常满足:
在虚拟环境中,通常满足:
其中:
sys.prefix:当前运行环境的前缀;sys.base_prefix:基础 Python 的前缀;sys.exec_prefix和sys.base_exec_prefix:对应平台相关库和扩展模块的前缀。
Python 文档将 sys.prefix != sys.base_prefix 作为运行时识别虚拟环境的可靠方式之一。虚拟环境规范也采用这一判定模型。(packaging.python.org)
可以用下面的检查函数:
import sys
def describe_runtime() -> None:
in_venv = sys.prefix != sys.base_prefix
print(f"executable = {sys.executable}")
print(f"version = {sys.version.split()[0]}")
print(f"prefix = {sys.prefix}")
print(f"base_prefix = {sys.base_prefix}")
print(f"running in venv = {in_venv}")
describe_runtime()
典型输出:
executable = /home/alice/project/.venv/bin/python
version = 3.14.0
prefix = /home/alice/project/.venv
base_prefix = /opt/python/3.14
running in venv = True
注意:VIRTUAL_ENV 环境变量只能说明某个激活脚本曾经设置过该变量。它可能与真正执行的 sys.executable 不一致,因此不能作为 Python 程序判断环境的唯一依据。
5. Python 启动时如何构造 sys.path
sys.path 是 Python 导入模块时依次搜索的路径列表:
import sys
for index, path in enumerate(sys.path):
print(index, repr(path))
一个简化后的构造过程如下:
flowchart TD
A[启动 python] --> B[确定脚本目录或当前目录]
B --> C[读取 PYTHONPATH]
C --> D[确定基础 Python 的标准库路径]
D --> E[检查 pyvenv.cfg]
E --> F[设置 sys.prefix 与 sys.base_prefix]
F --> G[处理 site 模块]
G --> H[加入虚拟环境 site-packages]
H --> I{include-system-site-packages=true?}
I -- 是 --> J[加入系统 site-packages]
I -- 否 --> K[不加入系统 site-packages]
J --> L[处理 .pth、sitecustomize、usercustomize]
K --> L
L --> M[得到最终 sys.path]
更严格地说,Python 3.14 的路径初始化大致包含以下来源:
5.1 脚本目录或当前目录
执行:
python app.py
sys.path 的首项通常是 app.py 所在目录。
执行:
python -m package.module
或者:
python
python -c "..."
首项通常与当前工作目录有关。
这解释了一个常见污染:
project/
├── json.py
└── app.py
执行:
python app.py
如果 app.py 中有:
import json
Python 可能优先导入项目目录中的 json.py,而不是标准库的 json。因此“虚拟环境隔离”并不等于“当前目录隔离”。
5.2 PYTHONPATH
如果设置了:
export PYTHONPATH=/home/alice/shared
Python 会把该目录加入模块搜索路径。Python 官方文档特别提醒,PYTHONPATH 会影响所有已安装的 Python 版本和虚拟环境,不应轻易在 shell 配置文件或全局环境变量中设置。(docs.python.org)
检查方法:
python -c "import os, sys; print('PYTHONPATH =', os.environ.get('PYTHONPATH')); print(*sys.path, sep='\n')"
清除当前 shell 中的设置:
unset PYTHONPATH
PowerShell:
Remove-Item Env:PYTHONPATH
5.3 标准库和扩展模块
Python 会根据基础安装位置寻找:
- 平台无关的标准库;
- 平台相关的扩展模块;
- 其他解释器运行所需的目录。
虚拟环境通过 pyvenv.cfg 使 sys.prefix 指向环境目录,同时保留 sys.base_prefix 指向基础 Python。Python 3.14 已经把虚拟环境前缀的设置前移到路径初始化阶段,而不再依赖 site 模块。(docs.python.org)
5.4 site-packages
最后,site 模块会加入第三方包目录,并处理其中的路径配置文件。
虚拟环境默认的第三方包目录类似:
Linux/macOS:
.venv/lib/python3.14/site-packages/
Windows:
.venv\Lib\site-packages\
精确路径不要靠目录猜测,应使用解释器查询:
python -c "import sysconfig; print(sysconfig.get_path('purelib')); print(sysconfig.get_path('platlib'))"
purelib:纯 Python 包的安装目录;platlib:平台相关包或扩展模块的安装目录。
使用 sysconfig 查询的原因是不同操作系统、发行版、架构和 free-threaded 构建可能采用不同目录布局。
6. pip 为什么应该写成 python -m pip
最常见的包安装错误是:
pip install requests
python app.py
这里的 pip 和 python 可能不是同一个环境中的程序。
例如:
pip -> /usr/local/bin/pip
python -> /home/alice/project/.venv/bin/python
结果是:包被安装到系统环境,而项目解释器仍然找不到它。
更可靠的写法是:
python -m pip install requests
它表达的是:
使用当前这个
python解释器加载并执行它对应的pip模块。
完整验证:
python -c "import sys; print(sys.executable)"
python -m pip --version
python -m pip show requests
python -c "import requests; print(requests.__file__)"
预期情况下,几个路径都应指向同一个 .venv:
/home/alice/project/.venv/bin/python
pip 25.x from /home/alice/project/.venv/lib/python3.14/site-packages/pip (...)
Location: /home/alice/project/.venv/lib/python3.14/site-packages
/home/alice/project/.venv/lib/python3.14/site-packages/requests/__init__.py
这里需要区分两个事实:
python -m pip能显著降低“pip 和 python 不匹配”的风险;- 它不能修复
PYTHONPATH、当前目录同名模块、--system-site-packages或.pth文件造成的路径污染。
7. 激活、直接调用和命令脚本
7.1 Unix shell
创建并激活:
python3.14 -m venv .venv
source .venv/bin/activate
检查:
command -v python
command -v pip
echo "$VIRTUAL_ENV"
退出:
deactivate
激活脚本的本质是修改当前 shell 的状态,而不是修改 .venv 中的 Python 二进制文件。
7.2 Windows PowerShell
创建并激活:
py -3.14 -m venv .venv
.\.venv\Scripts\Activate.ps1
如果执行策略阻止脚本运行,Python 文档给出的用户级设置方式是:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
也可以完全不激活,直接调用:
.\.venv\Scripts\python.exe -m pip install requests
.\.venv\Scripts\python.exe app.py
7.3 为什么安装的命令能直接运行
当包提供命令行入口时,安装工具通常会在环境的可执行文件目录生成脚本:
.venv/bin/tool
.venv/Scripts/tool.exe
这些脚本通常包含指向虚拟环境解释器的 shebang 或启动信息。Python venv 文档也说明,虚拟环境中安装的脚本会指向环境解释器。(docs.python.org)
因此,激活后运行:
tool
依赖的是 PATH 顺序;而直接运行:
.venv/bin/tool
依赖的是该脚本自身记录的解释器路径。
如果把 .venv 从 /home/alice/project 移动到 /tmp/project,脚本中的解释器路径可能仍然指向旧位置。这是虚拟环境“不应移动或复制”的主要原因之一。
8. --system-site-packages 如何打破默认隔离
默认创建:
python3.14 -m venv .venv
相当于:
include-system-site-packages = false
创建时显式指定:
python3.14 -m venv --system-site-packages .venv
则会允许环境访问系统级 site-packages:
include-system-site-packages = true
此时可导入集合近似变为:
这会产生一个危险现象:
python -c "import requests; print(requests.__file__)"
即使你从未在 .venv 中安装 requests,导入也可能成功,因为它来自系统环境。
这类环境的失败往往不是“包不存在”,而是:
- 本地环境测试通过;
- 换一台机器后导入失败;
- CI 中找不到包;
- 同一个包在系统环境升级后,项目行为发生变化。
检查当前环境是否允许系统包:
python -c "import sys; print(sys.prefix); print(sys.base_prefix)"
grep include-system-site-packages .venv/pyvenv.cfg
如果已经使用了该选项,不能通过简单删除一行配置就保证整个环境恢复干净,因为此前安装到环境中的包、生成的脚本和其他路径配置仍可能存在。更安全的恢复方式是删除并重建:
rm -rf .venv
python3.14 -m venv .venv
Windows:
Remove-Item -Recurse -Force .venv
py -3.14 -m venv .venv
9. 用户级 site-packages 与 -s
除了系统包,Python 还可能存在用户级包目录,例如 Unix 上常见的:
~/.local/lib/python3.14/site-packages
Windows 上则可能位于用户应用数据目录。site 模块会根据运行配置决定是否将用户级包目录加入 sys.path。(docs.python.org)
检查用户包目录:
python -m site
典型输出会包含:
sys.path = [...]
USER_BASE: '/home/alice/.local'
USER_SITE: '/home/alice/.local/lib/python3.14/site-packages'
ENABLE_USER_SITE: True
可以使用 -s 禁止用户级 site-packages:
python -s -c "import sys, site; print(site.ENABLE_USER_SITE); print(*sys.path, sep='\n')"
但 -s 不是修复项目环境的常规方案。它适合诊断:
python -c "import some_package; print(some_package.__file__)"
python -s -c "import some_package; print(some_package.__file__)"
如果第一条成功、第二条失败,说明包可能来自用户级目录。
Python 3.14 有一个重要变化:在虚拟环境中,sys.prefix 和 sys.exec_prefix 的设置已经不再依赖 site 模块,因此即使使用 -S 禁用 site 初始化,前缀仍会正确反映虚拟环境。(docs.python.org)
这并不意味着 -S 适合日常运行。-S 会跳过 site 初始化,从而影响 site-packages、.pth、用户定制和其他启动行为;它是诊断参数,不是替代虚拟环境的隔离机制。
10. .pth 文件:最隐蔽的路径污染来源之一
在 site-packages 中,名字形如:
something.pth
的文件可以:
- 添加额外目录到
sys.path; - 执行以
import开头的代码行。
例如,某个 .pth 文件可能包含:
/home/alice/old-project/src
于是即使当前虚拟环境没有安装某个包,Python 仍可能从旧项目目录导入它。
更危险的是:
import startup_hook
.pth 中的可执行行会在每次 Python 启动时处理,而不要求程序真的导入某个业务模块。Python 官方文档明确警告,.pth 中的可执行行会在每次启动执行,因此应尽量保持影响最小。(docs.python.org)
列出当前环境中的 .pth 文件:
python -c "import sysconfig, pathlib; p=pathlib.Path(sysconfig.get_path('purelib')); print(p); print(*p.glob('*.pth'), sep='\n')"
查看内容:
find .venv -name '*.pth' -type f -print -exec sed -n '1,120p' {} \;
Windows PowerShell:
Get-ChildItem .venv -Filter *.pth -Recurse | ForEach-Object {
$_.FullName
Get-Content $_.FullName
}
.pth 文件并不天然有害。某些安装机制会使用它们处理路径或导入钩子;问题在于项目不应无意中依赖指向旧工作区、临时目录或个人目录的路径。
11. 可编辑安装产生的“看似未安装”状态
开发项目经常使用:
python -m pip install -e .
这称为可编辑安装。它的目标不是把源码完整复制到 site-packages,而是让环境中的导入路径指向当前源码目录,使源码修改可以立即生效。
因此,下面两件事可能同时成立:
.venv/lib/python3.14/site-packages/ 中看不到完整的项目源码
import myproject 成功
诊断时应同时检查:
python -m pip show myproject
python -c "import myproject; print(myproject.__file__)"
如果 myproject.__file__ 指向当前源码目录,这是可编辑安装的正常结果;如果指向另一个工作区,则可能是旧的可编辑安装、.pth 或错误解释器造成的污染。
查看已安装发行包:
python -m pip list
查看某个包对应的实际导入位置:
python -c "import importlib.util; spec=importlib.util.find_spec('myproject'); print(spec); print(spec.origin if spec else None)"
这里的因果链是:
pip install -e .
↓
环境记录项目元数据
↓
导入系统获得源码目录路径
↓
import 使用工作区源码,而不是复制后的源码
当工作区被移动、删除或切换分支后,旧的可编辑安装可能继续存在,但指向无效或错误的位置。重建环境可以清除这类残留。
12. 常见污染模式与失败表现
12.1 pip 和 python 不属于同一个环境
错误操作:
pip install flask
python app.py
诊断:
command -v pip
command -v python
pip --version
python -c "import sys; print(sys.executable)"
如果两组路径不一致,改用:
python -m pip install flask
12.2 当前 shell 曾激活过旧环境
例如先进入项目 A:
source ~/projects/a/.venv/bin/activate
随后切换到项目 B,却忘记重新激活。提示符可能仍显示:
(.venv)
诊断不能只看提示符:
python -c "import sys, os; print(sys.executable); print(os.environ.get('VIRTUAL_ENV'))"
如果 sys.executable 指向项目 A,应执行:
deactivate
source ~/projects/b/.venv/bin/activate
或者绕过 shell 状态:
~/projects/b/.venv/bin/python -m pip install -r requirements.txt
12.3 PYTHONPATH 引入共享目录
表现:
- 包没有安装到当前环境,却可以导入;
- 修改共享目录中的源码后,项目行为变化;
- CI、容器和本地结果不一致。
诊断:
python -c "import os, sys; print(os.environ.get('PYTHONPATH')); print(*sys.path, sep='\n')"
12.4 --system-site-packages 隐式提供依赖
表现:
python -m pip show some-package
# 当前环境没有显示
python -c "import some_package"
# 却能成功
此时要检查:
grep include-system-site-packages .venv/pyvenv.cfg
python -c "import some_package; print(some_package.__file__)"
如果导入位置在系统 Python 目录,当前环境并不自洽。
12.5 项目目录遮蔽标准库或第三方包
例如项目中存在:
logging.py
typing.py
email/
json.py
导入路径首项通常包含脚本目录或当前目录,因此这些名称可能遮蔽标准库模块。
诊断:
python -c "import json; print(json.__file__)"
如果输出项目路径而不是 Python 标准库路径,就发生了名称遮蔽。
12.6 编辑器使用了另一个解释器
终端中执行:
python -c "import sys; print(sys.executable)"
输出 .venv/bin/python,但编辑器运行测试时使用:
/usr/bin/python3
那么终端验证不能代表编辑器验证。应在编辑器的运行配置中明确设置解释器路径,并在测试命令中打印:
import sys
print(sys.executable)
12.7 直接运行带 shebang 的旧脚本
某个脚本可能以如下方式开头:
#!/home/alice/old-project/.venv/bin/python
即使当前 shell 已激活新环境,直接执行该脚本:
./tool
仍可能调用旧环境,因为 shebang 是脚本自身的解释器选择。
诊断:
head -n 1 ./tool
必要时重新安装生成脚本,或明确使用当前解释器:
python -m package.module
13. 运行时检查:不要只检查“能不能 import”
“能导入”只证明某个路径上存在可导入对象,并不证明它来自正确环境。
可以建立一个诊断脚本:
from __future__ import annotations
import importlib.metadata as metadata
import importlib.util
import os
import site
import sys
import sysconfig
from pathlib import Path
def print_module(name: str) -> None:
spec = importlib.util.find_spec(name)
print(f"{name}:")
print(f" spec = {spec}")
print(f" origin = {spec.origin if spec else None}")
print("=== interpreter ===")
print("executable:", sys.executable)
print("version:", sys.version)
print("prefix:", sys.prefix)
print("base_prefix:", sys.base_prefix)
print("in_venv:", sys.prefix != sys.base_prefix)
print("\n=== environment variables ===")
print("VIRTUAL_ENV:", os.environ.get("VIRTUAL_ENV"))
print("PYTHONPATH:", os.environ.get("PYTHONPATH"))
print("\n=== installation paths ===")
print("purelib:", sysconfig.get_path("purelib"))
print("platlib:", sysconfig.get_path("platlib"))
print("scripts:", sysconfig.get_path("scripts"))
print("\n=== site ===")
print("ENABLE_USER_SITE:", site.ENABLE_USER_SITE)
print("USER_SITE:", site.getusersitepackages())
print("GLOBAL_SITE:", site.getsitepackages())
print("\n=== sys.path ===")
for index, path in enumerate(sys.path):
print(index, repr(path))
print("\n=== selected modules ===")
print_module("json")
print_module("pip")
print("\n=== selected distributions ===")
for name in ("pip", "setuptools"):
try:
print(name, metadata.version(name))
except metadata.PackageNotFoundError:
print(name, "<not installed>")
运行:
python diagnose.py
重点观察:
sys.executable是否位于目标.venv;sys.prefix是否不同于sys.base_prefix;purelib和platlib是否位于目标.venv;sys.path中是否出现旧项目目录、用户目录或意外系统目录;json等标准库模块是否来自正确的标准库位置;VIRTUAL_ENV是否与sys.executable所属环境一致。
一个环境是否“干净”,可以形式化为:
其中:
- :运行时实际参与导入的目录;
- :预期基础 Python 标准库目录;
- :目标虚拟环境包目录;
- :项目自身源码目录。
如果额外出现:
- 旧工作区;
- 未声明的用户包目录;
- 非预期系统包目录;
- 临时构建目录;
- 与当前项目无关的
.pth路径;
那么环境就存在隐式输入。
14. 如何建立一个可验证的 Python 3.14 环境
下面是一条完整流程。
14.1 选择并验证基础解释器
python3.14 --version
python3.14 -c "import sys; print(sys.executable)"
必须先确认这里确实是目标 Python 3.14,而不是名称相似的其他解释器。
14.2 创建环境
python3.14 -m venv .venv
如果希望创建时更新虚拟环境中的核心依赖,可以使用:
python3.14 -m venv --upgrade-deps .venv
--upgrade-deps 会升级核心依赖;它并不等于安装项目依赖,也不等于生成锁文件。Python 3.14 文档将该选项描述为升级核心依赖 pip。(docs.python.org)
14.3 不依赖激活进行验证
.venv/bin/python --version
.venv/bin/python -c "import sys; print(sys.executable); print(sys.prefix != sys.base_prefix)"
.venv/bin/python -m pip --version
Windows:
.\.venv\Scripts\python.exe --version
.\.venv\Scripts\python.exe -c "import sys; print(sys.executable); print(sys.prefix != sys.base_prefix)"
.\.venv\Scripts\python.exe -m pip --version
预期:
Python 3.14.x
.../.venv/bin/python
True
pip ... from .../.venv/.../site-packages/pip
14.4 安装并核对包的来源
.venv/bin/python -m pip install requests
.venv/bin/python -m pip show requests
.venv/bin/python -c "import requests; print(requests.__version__); print(requests.__file__)"
这里要验证的是:
发行包元数据位置 = 虚拟环境 site-packages
实际导入文件位置 = 虚拟环境 site-packages
如果两者不一致,应先诊断路径,而不是继续安装更多包。
14.5 记录依赖,但不要把虚拟环境当作依赖清单
虚拟环境目录包含安装结果,不是项目的可读依赖声明。项目仍需使用声明文件或项目元数据记录依赖,例如 pyproject.toml:
[project]
name = "example-app"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = [
"requests>=2.32,<3",
]
版本约束描述“允许哪些版本”,而虚拟环境只描述“当前这次安装实际得到了什么”。锁定、哈希和供应链完整性属于依赖解析与安装策略,不能由 venv 自动提供。PyPA 的规范列表将依赖说明、安装元数据和可复现环境分别作为不同规范领域。(packaging.python.org)
15. venv 与依赖解析、锁定的边界
可以把工程输入分成三层:
第一层:解释器选择
Python 3.14.x
它决定:
- 字节码和语言特性;
- 标准库版本;
- ABI 和平台兼容性;
- 可接受的包版本范围。
第二层:依赖约束
例如:
requests >= 2.32, < 3
它只描述允许集合:
第三层:具体解析结果
解析器还需要考虑:
- 直接依赖;
- 间接依赖;
- Python 版本;
- 操作系统;
- CPU 架构;
- environment markers;
- wheel 和源码包可用性;
- 额外依赖 extras;
- 已有环境中的安装状态。
最终安装的是某个具体解:
venv 只提供一个安装边界 ,并不负责定义 ,也不负责求解 。
因此:
python3.14 -m venv .venv
不能保证:
- 所有机器安装到相同版本;
- 所有包都来自同一个索引;
- 下载文件具有相同哈希;
- 间接依赖不会随时间变化;
- 依赖解析结果可重复。
这些问题应由项目的依赖声明、锁定格式、索引配置和安装校验共同处理。
16. 为什么虚拟环境通常应该删除重建
虚拟环境常被误解为“可以持续修补的开发机器”。实际上,它更接近一个由输入生成的构建产物:
其中:
- :解释器版本;
- :依赖版本和约束;
- :平台;
- :操作系统;
- :架构和 ABI;
- :生成的虚拟环境。
当环境经历很多次:
pip install ...
pip uninstall ...
pip install -U ...
pip install -e .
之后,环境中可能出现:
- 不再被项目需要的包;
- 旧版本残留;
- 旧的可编辑安装;
- 旧
.pth文件; - 依赖树与当前声明不一致;
- 被手工复制进去的文件;
- 与当前解释器不匹配的编译扩展。
此时继续补丁式修复的成本可能高于重建:
rm -rf .venv
python3.14 -m venv .venv
.venv/bin/python -m pip install ...
删除重建并不自动产生可复现性。它只是移除了历史状态;要获得可复现环境,还需要稳定的依赖声明、解析结果和安装来源。
17. 生产环境中的取舍
17.1 应用部署
对于应用,常见做法是:
- 在明确版本的 Python 3.14 基础上创建环境;
- 安装经过解析和验证的依赖;
- 运行时直接调用环境中的解释器或入口脚本;
- 不把
.venv提交到源码仓库; - 不依赖开发者机器上的用户包和系统包。
生产中不一定必须使用 venv。容器、系统包管理器或专用部署工具也可以提供环境边界。但无论使用什么方式,都应明确:
哪个解释器?
哪些包?
从哪里下载?
允许哪些版本?
最终使用哪些文件?
17.2 Python 库开发
对于库项目,虚拟环境用于:
- 测试某个 Python 版本;
- 测试不同依赖组合;
- 执行构建和检查工具;
- 避免污染开发者的全局 Python。
库本身不应把 .venv 打包发布,也不应假设用户会使用与你相同路径的虚拟环境。库应该通过项目元数据声明运行时依赖和 Python 版本范围。
17.3 --system-site-packages 的合理边界
该选项有时用于:
- 系统提供大型二进制库;
- 受限机器不允许重复安装依赖;
- 发行版希望让系统包管理器提供 Python 包。
但它牺牲了环境自洽性。只要项目需要跨机器、进入 CI、构建容器或进行供应链审计,就应谨慎使用,且必须把系统包视为显式外部输入,而不是“免费可用”的依赖。
18. 一组最小但有效的排查命令
遇到“明明安装了却无法导入”时,按以下顺序执行:
python -c "import sys; print(sys.executable)"
python -m pip --version
python -c "import sys; print(sys.prefix); print(sys.base_prefix)"
python -c "import sys; print(*sys.path, sep='\n')"
python -m site
python -m pip show 包名
python -c "import importlib.util; print(importlib.util.find_spec('导入名'))"
判断逻辑如下:
sys.executable错了:先修正解释器选择;python -m pip --version路径错了:不要继续安装,先修正环境;sys.prefix == sys.base_prefix:当前不是虚拟环境;sys.path出现旧目录:检查PYTHONPATH、当前目录和.pth;pip show找不到但import成功:检查系统包、用户包和路径注入;pip show能找到但import失败:安装名和导入名可能不同,或安装不完整;- 导入文件来自错误目录:检查名称遮蔽、编辑器解释器和旧可编辑安装。
最重要的诊断原则是:
先确认“哪个解释器在运行”,再确认“它从哪些路径导入”,最后才检查“包是否安装”。
虚拟环境的核心不是提示符中的括号,也不是目录名 .venv,而是解释器、前缀、安装目录和 sys.path 之间形成的一组可验证关系。
系列导航与关联阅读
- 系列入口:Python 完整学习路线:从语言模型、并发到 Web、数据、AI 与生产交付
- 上一篇:Python pyproject.toml:构建系统、项目元数据、入口和工具配置
- 下一篇:Python 依赖解析与锁定:版本约束、Lock、哈希和供应链
- 延伸:Python 3.14 工具链:安装、解释器、REPL、脚本与版本管理
官方资料
本文依据 Python 官方文档、相关 PEP 与生态项目官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。

评论
0 条讨论