Python 基础体系 · 第 2/112 篇。示例统一以 Python 3.14 为语言基线;第三方库使用与其兼容的现代稳定版本,版本敏感行为会单独说明。

Python 3.14 工具链:安装、解释器、REPL、脚本与版本管理

Python 工具链不是一条叫作“运行 Python 文件”的命令,而是一组共同决定程序如何被找到、解析、执行和复现的组件:

操作系统
  │
  ├─ Python 安装目录
  │    ├─ 解释器 python
  │    ├─ 标准库
  │    └─ site-packages
  │
  ├─ 版本选择器
  │    ├─ PATH
  │    ├─ Windows Python install manager
  │    └─ Unix 上的版本管理工具或显式路径
  │
  ├─ 虚拟环境 .venv
  │    ├─ 独立解释器入口
  │    ├─ 独立 site-packages
  │    └─ 独立脚本入口
  │
  └─ 执行入口
       ├─ REPL
       ├─ python script.py
       ├─ python -m package.module
       ├─ python -c "..."
       └─ 标准输入、目录或 zip 文件

理解这张图的关键是区分三个问题:

  1. 使用哪个 Python 解释器?
  2. 这个解释器从哪里加载模块和第三方包?
  3. 输入的源码以什么方式进入执行模型?

如果这三个问题没有分开,常见故障就会表现为“明明安装过却导入失败”“终端显示的是一个版本,程序实际用的是另一个版本”“激活了虚拟环境但仍加载了系统包”。

Python 3.14 官方文档将解释器命令行、环境变量、平台安装、虚拟环境和运行时模型分开描述;语言参考则把程序结构、执行帧、命名绑定和运行时组件作为语言执行模型的一部分。(docs.python.org)

一、先建立正确的对象模型

1. Python 不是一个文件,而是一组运行时对象

通常所说的“安装 Python”,至少包括以下对象:

  • 一个可执行的 Python 解释器
  • 一套标准库;
  • 一个模块搜索路径;
  • 一个用于安装第三方包的包目录;
  • 一组命令行入口和脚本入口。

这里的“解释器”有两个容易混淆的含义。

第一种含义是命令行中的 python 可执行程序。它负责初始化运行时、读取命令行选项、寻找输入代码,并启动执行。

第二种含义是运行时模型中的 Python interpreter。它是进程中的一个运行时状态容器,包含 sys.modules 等状态;真正逐条运行 Python 字节码的是更低层的字节码解释器。Python 3.14 语言参考明确区分了这两个概念:运行时中的 interpreter 不等同于执行字节码的 bytecode interpreter。(docs.python.org)

因此,下面两个命令虽然都写着 python,但它们依赖的实际解释器可能完全不同:

python -c "import sys; print(sys.executable)"
/usr/bin/python3 -c "import sys; print(sys.executable)"

第一个命令依赖当前 PATH;第二个命令直接指定了可执行文件。诊断 Python 环境时,sys.executable 比命令提示符中的 (venv) 更可信,因为它报告的是当前进程实际使用的解释器路径。

2. “版本”至少有三个层次

工程中常说“项目使用 Python 3.14”,但这句话可能指:

  1. 语言版本:源码允许使用哪些 Python 语法;
  2. 解释器版本:实际执行代码的 Python 版本;
  3. 依赖版本:例如 requestsnumpy 等包的版本。

它们不是同一个约束。

例如:

项目 A:Python >= 3.12,requests == 2.x
项目 B:Python == 3.14,requests == 3.x

即使两个项目都使用同一台机器上的 Python,也不能把两个项目的依赖安装到同一个全局 site-packages 中。官方教程用“应用 A 需要包版本 1.0、应用 B 需要版本 2.0”的冲突说明了虚拟环境的必要性。(docs.python.org)

所以版本管理的正确分层是:

解释器版本管理
    选择 Python 3.12、3.13、3.14

环境隔离
    为项目创建 .venv

依赖版本管理
    在 .venv 中安装并锁定第三方包

仅仅切换 python 命令,不能自动隔离第三方依赖;仅仅创建虚拟环境,也不能把不存在的 Python 3.14 安装到机器上。


二、安装 Python 3.14:安装的是解释器,不是项目环境

1. 安装方式的共同目标

无论通过系统包管理器、官方安装程序、源码编译还是 Windows Python install manager 安装,最后都应验证四件事:

python3.14 --version
python3.14 -c "import sys; print(sys.executable)"
python3.14 -c "import sys; print(sys.prefix)"
python3.14 -m pip --version

预期结果类似:

Python 3.14.x
/usr/local/bin/python3.14
/usr/local
pip 25.x from .../site-packages/pip (python 3.14)

最后一行中的 (python 3.14) 很重要:它证明被调用的 pip 与当前 Python 版本关联。相比直接输入 pip --version,使用:

python3.14 -m pip --version

可以明确要求“由这个解释器运行 pip 模块”。

2. Unix:系统 Python、源码安装与 altinstall

Linux 通常已经带有 Python,或者可以通过发行版的软件包安装。系统自带 Python 可能被操作系统工具依赖,因此不应随意替换 /usr/bin/python3

如果从 CPython 源码构建,官方文档给出的基本流程是:

./configure
make
make install

但直接执行 make install 可能覆盖或伪装系统中的 python3 命令;官方建议使用:

make altinstall

这样通常只安装带具体版本号的解释器,例如 python3.14,避免覆盖系统的无版本入口。(docs.python.org)

一个更适合多版本共存的源码安装示例是:

./configure --prefix="$HOME/opt/python-3.14"
make -j"$(nproc)"
make altinstall

安装后直接验证:

"$HOME/opt/python-3.14/bin/python3.14" --version
"$HOME/opt/python-3.14/bin/python3.14" -c \
  "import sys; print(sys.executable); print(sys.prefix)"

这里没有修改系统 PATH,所以不会影响系统命令。代价是每次都要使用完整路径,或者为它增加一个受控的命令入口:

export PATH="$HOME/opt/python-3.14/bin:$PATH"
python3.14 --version

风险在于:PATH 只影响命令查找,不会改变已经运行的进程,也不会清理已有 shell 中的命令缓存。在某些 shell 中,修改 PATH 后可以执行:

hash -r

随后重新检查:

command -v python3.14
python3.14 -c "import sys; print(sys.executable)"

3. macOS:不要把系统组件当作项目解释器

macOS 上可以使用官方安装程序或其他发行版。无论使用哪种方式,都应通过以下命令查看实际命令解析结果:

type -a python3
which -a python3
python3 -c "import sys; print(sys.executable)"

which 只能告诉你命令路径;sys.executable 则来自当前 Python 进程。二者通常应该一致,但在别名、包装脚本或复杂开发环境中不一定一致。

如果机器上同时存在多个 Python 版本,应尽量使用带版本号的解释器创建环境:

python3.14 -m venv .venv

官方教程指出,venv 使用“执行创建命令的那个 Python 版本”创建虚拟环境;用 python3.12 创建就是 3.12,用 python3.14 创建就是 3.14。(docs.python.org)

4. Windows:区分 pythonpy 和 Python install manager

Python 3.14 的 Windows 文档将 Python install manager 作为多运行时管理入口。推荐的基础命令是:

python --version
py --version
py list

在多版本场景中,py 可以选择特定运行时:

py -V:3.14 --version
py -V:3.14 -c "import sys; print(sys.executable)"

Python install manager 支持列出、安装、更新和卸载运行时:

py list
py list --online 3.14
py install 3.14
py install --update
py uninstall 3.14

其中 py install 管理的是 Python runtime,不是虚拟环境中的第三方依赖。更新运行时会影响该运行时本身,但不会等价于在每个项目中重新安装依赖。官方文档还特别说明,更新安装可能移除运行时目录中的全局修改,而虚拟环境通常仍需根据项目依赖重新验证。(docs.python.org)

pythonpy 和直接路径的语义可以概括为:

python
  └─ 依赖 PATH、活动虚拟环境、默认运行时和脚本规则

py
  └─ 由 Windows Python install manager 选择运行时

C:\...\python.exe
  └─ 完全绕过命令查找,直接使用指定解释器

在 Windows 自动化脚本中,如果明确依赖 install manager,可以使用文档提到的无歧义命令 pymanager;因为旧版 py.exe 可能已经存在,pymanager 能减少命令名冲突。(docs.python.org)


三、解释器调用:python 到底会执行什么

Python 3.14 的命令行形式可以抽象为:

python [选项] [-c command | -m module-name | script | -] [args]

其中 -c-m、脚本路径和标准输入,决定了代码从哪里来以及 sys.argv[0]sys.path 如何初始化。(docs.python.org)

1. 直接进入 REPL

python3.14

当标准输入连接到终端时,解释器进入交互模式,读取一段输入,解析并执行,然后继续等待下一段输入。可以输入:

>>> 1 + 2
3
>>> name = "Python"
>>> f"hello, {name}"
'hello, Python'

表达式结果自动显示,是 REPL 与普通脚本的重要区别。交互输入在语言参考中被视为一个代码块;模块、函数体、类定义、脚本文件以及 -c 命令也都是代码块。每个代码块都在一个执行帧中执行。(docs.python.org)

退出方式:

Unix/macOS:Ctrl-D
Windows:Ctrl-Z,然后 Enter

如果希望程序执行完后保留交互环境,可以使用:

python3.14 -i app.py

-i 会在脚本、-c-m 执行结束后进入交互模式,适合检查全局变量或异常现场。(docs.python.org)

2. -c:执行命令字符串

python3.14 -c "print(2 + 3)"

输出:

5

-c 后面的内容是一个代码块,可以包含多条语句:

python3.14 -c $'x = 10\nprint(x * 2)'

在 Python 3.14 中,命令字符串会在执行前自动去除缩进。这是 3.14 的命令行行为变化,不应把它泛化为所有 Python 版本都如此。(docs.python.org)

-c 的一个重要路径效果是:当前工作目录会加入 sys.path 的开头。因此,下面的命令可能导入当前目录中的模块:

python3.14 -c "import mymodule; print(mymodule.__file__)"

如果当前目录不可信,可以使用:

python3.14 -P -c "import sys; print(sys.path)"

-P 禁止把潜在不安全的路径自动放到 sys.path 前面;-I 则进一步进入隔离模式,同时忽略 PYTHON* 环境变量、用户 site-packages 和相关路径注入。(docs.python.org)

3. script.py:按文件路径运行

创建文件 hello.py

import sys

print("source:", __file__)
print("argv:", sys.argv)
print("executable:", sys.executable)

运行:

python3.14 hello.py alpha beta

典型输出:

source: /work/hello.py
argv: ['hello.py', 'alpha', 'beta']
executable: /usr/bin/python3.14

脚本路径所在目录会加入 sys.path 的开头。这样做的直接原因是:脚本通常需要导入同目录中的模块。

例如:

project/
├── main.py
└── helper.py

main.py

import helper

print(helper.VALUE)

执行:

python3.14 project/main.py

解释器会把 project/ 放入模块搜索路径,因此可以找到 helper.py

但这种路径语义也会导致同一份代码被以不同模块身份加载,尤其在包项目中容易产生相对导入和全局状态问题。工程化项目通常更适合使用 -m

4. -m:按模块名运行

目录结构:

project/
├── pyproject.toml
└── app/
    ├── __init__.py
    ├── __main__.py
    └── cli.py

app/__main__.py

from .cli import main

if __name__ == "__main__":
    raise SystemExit(main())

app/cli.py

def main():
    print("running as a package")
    return 0

project/ 中运行:

python3.14 -m app

解释器会通过标准导入机制寻找 app,然后执行 app.__main__,并把它作为 __main__ 模块运行。模块名不是文件路径,因此不能写:

python3.14 -m app.py

官方命令行文档明确说明,-m 使用模块名,不应带 .py 扩展名;当参数是包名时,执行的是该包的 __main__ 子模块。(docs.python.org)

python script.pypython -m package.module 的关键差异如下:

维度 python script.py python -m package.module
输入形式 文件系统路径 导入系统中的模块名
主模块 文件以 __main__ 执行 模块内容以 __main__ 执行
搜索起点 脚本所在目录 当前工作目录及导入路径
相对导入 容易受包上下文影响 更适合包内模块
典型用途 单文件脚本 包、命令行模块、测试模块

例如运行测试时:

python3.14 -m unittest
python3.14 -m pytest

第二条要求环境中已安装 pytest,而第一条使用标准库。这里 -m 的价值不是“少写几个字符”,而是让测试模块通过当前解释器的导入路径运行。


四、从源码到执行:安装工具链与 Python 执行模型如何连接

1. 源码不是直接被“解释”成机器指令

对于 CPython,可以把典型执行路径抽象为:

源码文本
  │
  ├─ 词法分析与语法分析
  │
  ├─ AST(抽象语法树)
  │
  ├─ 编译
  │
  ├─ code object(代码对象)
  │
  ├─ 字节码指令
  │
  ├─ execution frame(执行帧)
  │
  └─ CPython 字节码解释器执行

AST 是语法结构的树状表示。例如:

x = 1 + 2

可以查看为:

import ast

tree = ast.parse("x = 1 + 2")
print(ast.dump(tree, indent=4))

输出结构类似:

Module(
    body=[
        Assign(
            targets=[
                Name(id='x', ctx=Store())],
            value=BinOp(
                left=Constant(value=1),
                op=Add(),
                right=Constant(value=2)))],
    type_ignores=[])

这里:

  • Assign 表示赋值语句;
  • Name(id='x', ctx=Store()) 表示把值写入名称 x
  • BinOp 表示二元运算;
  • Constant 表示字面量。

官方 ast 模块允许通过 ast.parse() 生成 AST,也允许通过 compile() 把 AST 编译成代码对象。AST 的具体语法结构可能随 Python 版本变化,因此 AST 处理工具不能假设不同 Python 版本的节点结构永远一致。(docs.python.org)

2. 从 AST 到字节码

可以使用 compile()dis 观察中间结果:

import dis

source = """
def add(a, b):
    return a + b
"""

code = compile(source, "<demo>", "exec")

print(type(code))
dis.dis(code)

compile()"exec" 模式表示输入是一段模块或脚本代码;若使用 "eval",输入必须是单个表达式;交互输入对应 "single" 模式。AST 文档也展示了 ModuleExpressionInteractive 三种根节点。(docs.python.org)

dis.dis() 显示的是 CPython 字节码层面的实现细节。它适合回答:

  • 这段源码大致被编译成了哪些指令?
  • 名称读取、常量加载、函数调用如何表现?
  • 两段源码的编译结果有何差异?

但字节码不是 Python 语言规范保证的稳定接口。即使同为 Python 3.14,不同构建、优化选项或实现也不应被假设为完全相同;更不能把 .pyc 当作跨版本、跨实现的可移植执行格式。

3. 代码对象与执行帧

代码对象保存编译后的执行信息,例如:

  • 字节码;
  • 常量;
  • 名称;
  • 局部变量布局;
  • 文件名和行号映射;
  • 函数参数信息。

代码对象本身不是一次函数调用。调用函数时,解释器会基于函数的代码对象创建执行帧。

例如:

def inner(x):
    return x + 1

def outer():
    value = inner(10)
    return value * 2

print(outer())

执行过程可以简化为:

模块帧
  └─ 调用 outer()
       └─ outer 帧
            └─ 调用 inner(10)
                 └─ inner 帧

inner 返回时,inner 帧退出,返回值交给 outer;当 outer 返回时,outer 帧退出,返回值交给模块帧。

语言参考把代码块与执行帧联系起来:代码块作为一个执行单元,执行帧保存调试所需的管理信息,并决定代码块执行完毕后如何继续。(docs.python.org)

异常时,调用栈提供的是“异常传播经过的帧链”。例如:

def parse():
    return int("not-a-number")

def load():
    return parse()

load()

典型 traceback 会显示:

Traceback (most recent call last):
  ...
ValueError: invalid literal for int() ...

它不是简单的“错误日志”,而是从当前异常点向外展示代码块调用关系。未被处理的异常会终止脚本,或者在 REPL 中返回交互主循环;解释器通常会打印 traceback。(docs.python.org)

4. 名称、模块和帧中的命名空间

Python 中变量名不是装着值的盒子,而是指向对象的名称绑定:

x = []
y = x

这里发生的是:

创建列表对象 L
x ─────┐
       ├──> L
y ─────┘

xy 是两个名称,但指向同一个对象。

函数调用会创建新的局部命名空间:

x = "module"

def f():
    x = "function"
    return x

print(f())
print(x)

输出:

function
module

函数帧中的局部绑定不会覆盖模块帧中的 x。如果函数体中出现赋值操作,编译器会把该名称视为局部名称;因此下面的代码会产生 UnboundLocalError

x = 10

def f():
    print(x)
    x = 20

f()

原因不是“运行到赋值时才决定作用域”,而是函数代码块中出现了对 x 的绑定,函数内对 x 的使用整体按局部变量处理。语言参考对名称绑定和局部变量判定给出了这一规则。(docs.python.org)

这也是为什么执行入口会影响行为:脚本文件、REPL、-c-m 都创建代码块,但其 __name__sys.argv[0]sys.path 和包上下文可能不同。


五、REPL:交互式执行不是“删掉文件后的脚本执行”

1. REPL 的基本语义

REPL 是 Read-Eval-Print Loop:

读取一段输入
  → 解析
  → 执行
  → 打印表达式结果
  → 读取下一段输入

示例:

>>> total = 10
>>> total + 5
15
>>> total
10

total 在多次输入之间保留,是因为这些交互式代码块共享交互解释器的主命名空间。

普通脚本则通常是:

读取整个输入
  → 解析整个代码块
  → 执行
  → 进程结束

Python 命令行文档指出,在非交互模式下,整个输入会在执行前完成解析。因此,脚本中后面的语法错误可能导致前面的语句也不会执行。(docs.python.org)

例如文件:

print("before")
if True print("broken")

执行时不会可靠地产生 before,因为整个脚本代码块在执行前必须先通过语法解析。

2. 多行输入和未完成代码块

REPL 会识别未完成的复合语句:

>>> def greet(name):
...     return f"hello, {name}"
...
>>> greet("Ada")
'hello, Ada'

... 不是 Python 语法,而是交互界面提示符,表示解释器正在等待同一代码块的后续输入。

如果把依赖终端交互的代码直接重定向到标准输入,要注意提示符、缓冲和 EOF 行为:

printf 'print(1 + 2)\n' | python3.14 -

这里 - 表示从标准输入读取 Python 代码。命令行文档将标准输入、文件、目录、zip 文件和 -c 命令都作为不同的解释器输入形式。(docs.python.org)

3. REPL 适合验证,不适合保存业务流程

REPL 的优势是状态反馈快:

>>> import sys
>>> sys.version
>>> sys.path
>>> sys.executable

它适合验证:

  • 某个包是否能导入;
  • 某个 API 的返回值;
  • 当前解释器和路径;
  • 一个小范围的语法或对象行为。

但 REPL 状态是隐式的。输入顺序、上一次绑定的变量以及已导入模块都会影响结果。下面的操作在同一 REPL 中可能成功:

>>> x = 10
>>> x + 1
11

但复制第二行到全新的进程中就会失败:

python3.14 -c "print(x + 1)"

输出:

NameError: name 'x' is not defined

因此,REPL 成功只能证明“当前交互状态下成功”,不能自动证明脚本、测试或生产进程也会成功。


六、脚本、模块与导入系统:路径决定了代码从哪里加载

1. import 做了两件事

执行:

import json

可以拆成两个动作:

搜索名为 json 的模块
  → 找到后创建或复用模块对象
  → 将结果绑定到当前命名空间中的 json

Python 官方语言参考明确指出,import 同时包含搜索模块和名称绑定两个动作;模块首次导入时会创建并初始化模块对象,导入失败则抛出 ModuleNotFoundError。(docs.python.org)

模块对象通常会缓存到:

import sys
print(sys.modules["json"])

因此,同一进程内第二次导入通常不会重新执行模块顶层代码:

import json
import json

这不意味着模块永远只初始化一次;不同解释器、不同进程或手动操作导入系统时,行为可能不同。

2. sys.path 不是单纯的环境变量

查看当前模块搜索路径:

python3.14 -c "import sys; print('\n'.join(sys.path))"

sys.path 可能包含:

  • 当前脚本所在目录;
  • 当前工作目录;
  • 标准库目录;
  • site-packages;
  • PYTHONPATH 注入的目录;
  • .pth 文件扩展的路径;
  • 虚拟环境路径。

PYTHONPATH 会扩展模块搜索路径,格式类似操作系统的 PATH,不同平台使用不同的路径分隔符。官方文档也提醒,PYTHONHOME 会影响标准库位置,而 PYTHONPATH 会影响模块搜索路径。(docs.python.org)

因此,下面的环境变量可能造成跨项目污染:

export PYTHONPATH="$HOME/shared-python"
python3.14 app.py

即使 app.py 位于虚拟环境中,$HOME/shared-python 仍可能被加入搜索路径。创建虚拟环境不会自动清除 PYTHONPATH;官方教程建议在使用虚拟环境时检查并取消它。(docs.python.org)

诊断时可以使用:

env | grep '^PYTHON'
python3.14 -c "import sys; print(sys.path)"
python3.14 -I -c "import sys; print(sys.path)"

如果普通模式失败、隔离模式成功,通常说明问题来自当前目录、用户 site-packages 或 PYTHON* 环境变量,而不是包本身不存在。

3. 同名文件遮蔽标准库或第三方包

假设当前目录存在:

json.py

再执行:

import json
print(json.__file__)

可能加载当前目录中的 json.py,而不是标准库的 json。这类问题的典型症状包括:

AttributeError: module 'json' has no attribute 'loads'

诊断方式是始终检查模块来源:

python3.14 -c "import json; print(json.__file__)"

对于第三方包:

python3.14 -c "import requests; print(requests.__file__)"

不要只检查“能否导入”,还要检查“从哪里导入”。


七、虚拟环境:解释器隔离、路径变化与常见污染

1. venv 创建了什么

创建项目环境:

mkdir demo
cd demo
python3.14 -m venv .venv

venv 会创建一个目录,通常包含:

.venv/
├── pyvenv.cfg
├── bin/                  # Unix/macOS
│   ├── python
│   ├── python3
│   └── activate
└── lib/python3.14/
    └── site-packages/

Windows 中对应目录通常是:

.venv\
├── pyvenv.cfg
├── Scripts\
│   ├── python.exe
│   └── Activate.ps1
└── Lib\
    └── site-packages\

官方文档说明,环境中的 pyvenv.cfg 会记录创建它的基础 Python;同时创建 binScripts、解释器入口和 site-packages 目录。(docs.python.org)

验证:

.venv/bin/python -c "import sys; print(sys.executable)"
.venv/bin/python -c \
  "import sys; print(sys.prefix); print(sys.base_prefix)"

输出关系应满足:

sys.prefix != sys.base_prefix

在虚拟环境中:

  • sys.prefix 指向虚拟环境;
  • sys.base_prefix 指向创建该环境的基础 Python;
  • sys.executable 指向虚拟环境解释器。

因此,判断当前进程是否在虚拟环境中,应优先使用:

import sys

print(sys.prefix != sys.base_prefix)

而不是:

import os

print(os.environ.get("VIRTUAL_ENV"))

因为不激活环境也可以直接调用 .venv/bin/python,这种情况下 VIRTUAL_ENV 可能没有设置。官方文档明确指出,VIRTUAL_ENV 不能作为判断当前是否使用虚拟环境的可靠依据。(docs.python.org)

2. 激活环境到底改变了什么

Unix/macOS:

source .venv/bin/activate

Windows PowerShell:

.venv\Scripts\Activate.ps1

激活脚本主要改变当前 shell 的环境:

PATH = .venv/bin:$PATH

于是:

python --version

会优先找到 .venv/bin/python

激活并不修改 Python 语言本身,也不改变 PYTHONPATH。这两个概念必须分开:

激活:
    改变 shell 的命令查找顺序

导入:
    由当前 Python 进程计算 sys.path

PYTHONPATH:
    额外注入模块搜索路径

可以不用激活,直接执行:

.venv/bin/python app.py
.venv/bin/python -m pip install requests

这通常更适合 CI、脚本和进程管理器,因为命令明确指定了解释器。

3. python -m pip 为什么比直接使用 pip 稳妥

激活环境后,下面两条命令通常都能工作:

pip install requests
python -m pip install requests

但它们的解析路径不同:

pip
  └─ 由 PATH 查找到的可执行脚本

python -m pip
  └─ 由当前 python 解释器导入并执行 pip 模块

在多版本环境中,pip 可能来自系统 Python,而 python 来自 .venv。所以工程脚本更适合写成:

python -m pip install -r requirements.txt
python -m pip list
python -m pip show requests

在没有激活环境时写成:

.venv/bin/python -m pip install -r requirements.txt

第三方包的安装位置由被调用的 Python 环境决定。官方教程也采用 python -m pip install ... 的形式,并说明 pip 默认从 Python Package Index 安装包。(docs.python.org)

4. --system-site-packages 会打破默认隔离

默认创建:

python3.14 -m venv .venv

虚拟环境不会使用基础环境的第三方包。

如果创建时使用:

python3.14 -m venv --system-site-packages .venv

虚拟环境可以访问系统 site-packages。这在需要复用系统预装库时可能有用,但会削弱可复现性:项目依赖可能不是由项目安装的,而是来自机器状态。官方文档将该选项定义为“允许虚拟环境访问系统 site-packages”。(docs.python.org)

验证当前环境的包来源:

python -c "import sys; print('\n'.join(sys.path))"
python -c "import site; print(site.getsitepackages())"

如果一个包来自环境外的路径,应明确判断这是刻意设计还是污染。

5. 虚拟环境不可移动

虚拟环境中的脚本通常包含指向解释器的绝对路径,例如:

#!/absolute/path/to/project/.venv/bin/python

因此,把整个项目目录从:

/home/alice/project

移动到:

/home/bob/project

后,原 .venv 可能仍指向旧路径。

正确恢复方式是删除并重建:

rm -rf .venv
python3.14 -m venv .venv
python -m pip install -r requirements.txt

官方文档明确将虚拟环境视为可丢弃、不可移动的目录;如果路径变化,应在新位置重新创建。(docs.python.org)


八、版本管理:从“默认版本”到“项目版本”

1. PATH 是选择机制,不是版本管理系统

假设机器上有:

/usr/bin/python3.12
/usr/local/bin/python3.14

当执行:

python3

最终使用哪个版本,取决于 PATH 中哪个目录先出现:

echo "$PATH"
command -v python3
python3 -c "import sys; print(sys.executable)"

但依赖 PATH 存在两个边界:

  • shell 配置可能因用户不同而不同;
  • IDE、服务管理器和 CI 不一定读取同一个 shell 配置。

因此,PATH 适合交互使用,不适合单独承担生产环境的版本契约。生产脚本应尽量在环境创建和执行时明确解释器来源。

2. 版本选择器与虚拟环境要配合

可靠流程是:

# 1. 选择基础解释器
python3.14 --version

# 2. 用它创建环境
python3.14 -m venv .venv

# 3. 使用环境中的解释器安装依赖
.venv/bin/python -m pip install -r requirements.txt

# 4. 使用同一解释器运行
.venv/bin/python -m app

这里的因果链是:

python3.14
  → 决定 .venv 的基础解释器版本

.venv/bin/python
  → 决定当前项目的解释器和 site-packages

python -m pip
  → 把包安装到当前解释器对应的环境

python -m app
  → 从同一个环境运行应用

任意一步换成另一个解释器,都可能产生“安装成功但运行失败”。

3. Windows install manager 的版本选择

Windows Python install manager 支持:

py list
py -V:3.14 --version
py -V:3.14 -m venv .venv

如果已安装多个 3.x 版本,可以显式指定 3.14 创建环境,而不是依赖默认版本。默认运行时可以通过配置或 PYTHON_MANAGER_DEFAULT 调整;但项目脚本更适合显式指定版本或依赖已创建的 .venv。(docs.python.org)

Windows 项目的完整示例:

mkdir demo
cd demo

py install 3.14
py -V:3.14 -m venv .venv

.venv\Scripts\python.exe -c "import sys; print(sys.version)"
.venv\Scripts\python.exe -m pip install -r requirements.txt
.venv\Scripts\python.exe -m app

如果 PowerShell 阻止执行激活脚本,可以不激活,直接调用 .venv\Scripts\python.exe;也可以根据官方文档调整当前用户的执行策略,但这属于操作系统安全策略,不应为了一个项目无条件修改全局策略。(docs.python.org)

4. Unix 上的版本管理工具应解决什么问题

Unix 上的版本管理工具通常负责:

下载或发现多个 Python 版本
  → 为目录或 shell 选择默认版本
  → 生成稳定的命令入口
  → 用选定版本创建虚拟环境

但它们不应被误认为虚拟环境本身。无论使用哪一种工具,最终都应落到可验证的解释器路径:

python --version
python -c "import sys; print(sys.executable)"
python -m venv .venv
.venv/bin/python -c "import sys; print(sys.version)"

如果版本管理工具显示当前为 3.14,而 .venv/bin/python 显示 3.13,说明虚拟环境是在切换版本之前创建的;重新切换默认版本不会自动升级已有环境。


九、常见失败路径与诊断顺序

1. python 不是预期版本

先不要安装任何包,执行:

python --version
python -c "import sys; print(sys.executable)"
type -a python

Windows:

python --version
python -c "import sys; print(sys.executable)"
where.exe python
py list

如果命令路径和 sys.executable 不符合预期,先修复解释器选择,再处理依赖。

2. pip install 成功,但程序仍然 ModuleNotFoundError

诊断:

python -m pip --version
python -c "import sys; print(sys.executable)"
python -c "import sys; print('\n'.join(sys.path))"

重点比较:

pip 显示的 Python 版本
当前程序的 sys.executable
当前程序的 sys.path

如果安装命令使用的是系统 Python,而运行命令使用的是 .venv,两者就是两个环境。

3. 已激活虚拟环境,却加载了错误模块

执行:

python -c "import sys; print(sys.executable)"
python -c "import package; print(package.__file__)"
python -c "import sys; print('\n'.join(sys.path))"

随后检查:

echo "$PYTHONPATH"
env | grep '^PYTHON'

Windows PowerShell:

$env:PYTHONPATH
Get-ChildItem Env:PYTHON*

如果路径中出现旧项目、用户目录或共享目录,问题通常是路径污染,而不是虚拟环境未激活。

4. python app.py 可以运行,python -m app 失败

这通常是包上下文不同,而不是 Python 随机行为。

检查:

项目根目录是否是当前工作目录
包中是否存在 __init__.py
包内是否使用了正确的相对导入
是否把项目根目录错误地当成了包目录

例如:

from .config import settings

这种相对导入要求模块处于包上下文中。直接运行文件时,文件可能成为顶层 __main__,缺少预期的父包关系;用:

python -m package.module

通常能保留正确的包导入上下文。

5. 怀疑导入了本地同名文件

执行:

python -c "import sys; print(sys.path[0])"
python -c "import target; print(target.__file__)"

还可以启用导入跟踪:

python -v -c "import target"

-v 会输出模块初始化以及搜索和加载信息,适合定位“到底从哪个路径加载了模块”。官方命令行文档将 -v 定义为模块初始化和查找过程的详细输出选项。(docs.python.org)

6. 怀疑导入耗时

Python 3.14 支持:

python -X importtime=1 -c "import asyncio"
python -X importtime=2 -c "import asyncio"

importtime 会显示模块的自身导入耗时和累计耗时;3.14 新增的 =2 还能标识已经加载过的模块。多线程程序中该输出可能不完整,因此它适合作为诊断线索,而不是严格性能基准。(docs.python.org)


十、一个可复现的 Python 3.14 项目启动流程

下面的流程把解释器、虚拟环境、REPL、脚本和模块运行统一起来。

1. 创建项目

mkdir python314-demo
cd python314-demo

2. 明确基础解释器

python3.14 --version
python3.14 -c "import sys; print(sys.executable)"

3. 创建虚拟环境

python3.14 -m venv .venv

4. 不激活,直接验证环境

Unix/macOS:

.venv/bin/python -c \
  "import sys; print(sys.version); print(sys.executable); print(sys.prefix)"

Windows:

.venv\Scripts\python.exe -c `
  "import sys; print(sys.version); print(sys.executable); print(sys.prefix)"

5. 使用环境中的 pip

.venv/bin/python -m pip install --upgrade pip

如果项目有依赖文件:

.venv/bin/python -m pip install -r requirements.txt

6. 用 REPL 检查导入来源

.venv/bin/python
>>> import sys
>>> sys.executable
>>> sys.prefix
>>> import some_package
>>> some_package.__file__

7. 用模块方式运行

python314-demo/
├── .venv/
└── app/
    ├── __init__.py
    ├── __main__.py
    └── config.py

app/config.py

NAME = "Python 3.14"

app/__main__.py

from .config import NAME

def main():
    print(NAME)
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

执行:

.venv/bin/python -m app

预期输出:

Python 3.14

这个命令同时验证了:

  • 当前使用的是 .venv 中的解释器;
  • 包可以被导入;
  • 相对导入上下文正确;
  • app.__main__ 可以作为程序入口执行。

结语:把“能运行”拆成四个可验证条件

Python 3.14 工具链的核心不是记住更多命令,而是让每个命令的状态变化可解释:

安装:
    机器上有哪些 Python 解释器?

选择:
    当前命令实际调用了哪个解释器?

隔离:
    这个解释器从哪些 site-packages 加载包?

执行:
    输入是 REPL、脚本、模块、命令字符串还是标准输入?

最小而可靠的验证组合是:

python --version
python -c "import sys; print(sys.executable)"
python -c "import sys; print(sys.prefix, sys.base_prefix)"
python -m pip --version
python -c "import sys; print('\n'.join(sys.path))"

当这几条命令的结果能够被解释时,安装、解释器、REPL、脚本、模块、虚拟环境和版本管理就不再是相互割裂的命令技巧,而是同一条执行链上的不同状态。


系列导航与关联阅读

官方资料

本文依据 Python 官方文档、相关 PEP 与生态项目官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。