装 Python 装进来的是三样:解释器、标准库、pip。少一样都会在某天卡住你。
虚拟环境不是「再装一个 Python」,是个只改 sys.prefix 的壳。半兆对一个 G,差两千倍。
判据只有一条:装的包进没进这个壳。查 prefix,别猜。
上一篇讲了 Python 为什么能做游戏,这一篇把它装顺手。
教材第 1 章剩下两节是【搭建开发环境】,下面三个小标题:安装 Python、安装 PyCharm、
配置虚拟环境;再加一节【小结】。
本篇把小结并进来,四件事一条线:装什么 → 为什么要多一层壳 → 编辑器干什么 → 怎么验收。
演示里的虚拟环境是真建的,pyvenv.cfg 是真读的,
两个解释器的 sys.prefix 也是真跑出来对比的。
一、装进来的是三样,不是一个软件
从官网下的安装包,装完硬盘上留下的不是「一个软件」,是三样东西。
| 装进来的 | 是什么 | 少了它会怎样 |
|---|---|---|
解释器 python.exe |
读源码、编字节码、跑字节码的那个程序 | 什么都跑不了 |
| 标准库 | 几百个 .py 文件,import 就能用 |
连 import os 都报错 |
| pip | 包管理器,从网上装第三方包 | pip install pygame 用不了 |
这三样装在同一个目录里。
安装界面上有一个勾选框,叫 Add Python to PATH,意思是把这个目录写进系统的 PATH 变量,
这样你在任何目录敲 python 都能找到它。
没勾这个框,是新手第一个坑。 表现是:Python 明明装好了,
一敲 python 就提示「不是内部或外部命令」。
补救有两条:重跑安装包选 Modify 把框勾上;或者手动把那个目录加进环境变量。第一条省事。
装完怎么确认?开一个命令行敲 python --version,回你一行版本号就算成了。
注意是 --version 两个减号,一个减号会进交互模式。
二、虚拟环境不是复制,是壳
「虚拟环境」这个词很唬人,听起来像在电脑里又装了一个 Python。
不是。演示里真建了一个:整个目录不到半兆,而系统的标准库目录将近一个 G。
差了两千倍——它要是复制,不可能这么小。
打开那个目录看,只有一个 pyvenv.cfg、一个空的 Lib/site-packages、和 Scripts 底下几个启动器。
没有 python313.dll,没有标准库。
pyvenv.cfg 里那行 home 是关键:它指向原来那个真解释器。
启动器干的事就一件——找到 home 那个真解释器,把 sys.prefix 改成自己的目录,然后跑。
所以演示里两个解释器跑同一段代码,结果是这样的:prefix 变了,base_prefix 没变。
这两个名字别记混:
装包看 prefix,找解释器看 base_prefix。
pip install 装进的是 prefix 底下的 site-packages。
还有一点值得注意:include-system-site-packages = false 这一行。意思是这个壳不看系统那套包。
你在系统里装的东西,这里面一个都看不见。这正是我们要的效果。
三、为什么非要多这一层
因为包会打架。 具体点:手上有个两年前的项目要 pygame 2.1,新项目要 2.6。 全局装只能有一个版本,升了新的旧的坏,降回去新的坏。 虚拟环境之后,每个项目一个抽屉,各装各的,互不串味。项目删了,抽屉一起删,系统里干干净净。
建的方法就一句:python -m venv 目录名。
演示里加了 --without-pip,是因为这台机器上装 pip 那一步要联网,不影响要看的结论;
你自己建的时候不用加这个参数。
建完要「进去」才有用。Windows 上是 Scripts\activate,macOS 和 Linux 上是 source bin/activate。
进去的标志是命令行提示符前面多了个括号,里面是环境名。
进去之后敲 pip install pygame,装进的就是这个抽屉。要出来敲 deactivate。
四、PyCharm 只负责三件事
教材 1.2.2 那节讲装 PyCharm。
先把它的定位说清楚:它不负责让代码跑起来,它只是替你敲 python 文件名.py。
它真正给你的三件事:
一是编辑。敲三个字母它补全,点一下跳到定义,改个名字全项目一起改。
二是运行。右上角一个绿三角,省得你切到命令行。
三是调试。点一下行号设断点,程序跑到那儿停住,你能看每个变量当时的值。
这是 print 调试法唯一真正的替代品。
配解释器的地方在 Settings → Project → Python Interpreter → Add → Virtualenv Environment,
Base interpreter 选系统那个,Location 选项目里的 .venv。
有一个坑要提前说:IDE 会替你设好当前工作目录。所以经常出现「IDE 里能跑,
命令行一跑就报文件找不到」。
碰到这种情况别怀疑代码,在程序开头加一句 print(os.getcwd()),两边一比就清楚。
五、验收三条
环境搭完,用三条同时验收:
| 检查什么 | 怎么查 | 合格的样子 |
|---|---|---|
| 解释器在虚拟环境里 | 看 sys.prefix |
路径里带项目名 |
| 套件装对地方 | pip -V 看路径 |
指向 .venv\Lib\site-packages |
| pygame 能导入 | import pygame |
打印版本号,不报错 |
三条同时成立才算干净。只成立第一条不够——解释器对了,但包还装在系统里,那是另一种错, 而且更难查。 教材 1.3 节的小结很短,就一句「本章介绍了 Python 和开发环境」。 我给它补一句:这一章真正要带走的是「三样东西」和「一个壳」这两个模型。后面每一次装包、 每一次报「找不到模块」,都是回到这两个模型上找原因。
六、动手 Demo:搭一个干净的开发环境
先看手上这套环境是什么,再真建一个虚拟环境出来。
把 pyvenv.cfg 读出来看 home 指向哪,再对比两个解释器的 sys.prefix。
最后量一下这个壳有多大,和系统标准库比一比。
import os, subprocess, sys, time
PY = sys.executable
def info(s):
print(' ' + s)
def note(s):
print(' ! ' + s)
# ① 手上这套是什么
info('版本 %s' % sys.version.split()[0])
info('装在 %s' % sys.prefix)
# ② 真建一个虚拟环境
d = os.path.join(os.path.dirname(os.path.abspath(__file__)), '_tmp_venv')
os.makedirs(d, exist_ok=True)
t0 = time.perf_counter()
r = subprocess.run([PY, '-m', 'venv', '--without-pip',
os.path.join(d, 'gamedev')],
capture_output=True, text=True)
info('python -m venv gamedev 用时 %.0f 毫秒,退出码 %d'
% ((time.perf_counter() - t0) * 1000, r.returncode))
root = os.path.join(d, 'gamedev')
# ③ 关键在 pyvenv.cfg
with open(os.path.join(root, 'pyvenv.cfg'), encoding='utf-8') as f:
cfg = f.read()
for ln in cfg.strip().split(chr(10)):
if ln.startswith('command'): # 这行太长,跳过
continue
info(ln)
# ④ 两个解释器各跑一遍,对比 prefix
code = ('import sys' + chr(10) +
"print('prefix ', sys.prefix)" + chr(10) +
"print('base_prefix', sys.base_prefix)" + chr(10) +
"print('两者相同吗 ', sys.prefix == sys.base_prefix)")
probe = os.path.join(d, 'probe.py')
open(probe, 'w', encoding='utf-8').write(code)
info('用系统解释器跑:')
out = subprocess.run([PY, probe], capture_output=True, text=True,
encoding='utf-8')
for ln in out.stdout.strip().split(chr(10)):
info(' ' + ln)
info('用虚拟环境里那个 python.exe 跑:')
out = subprocess.run([os.path.join(root, 'Scripts', 'python.exe'), probe],
capture_output=True, text=True, encoding='utf-8')
for ln in (out.stdout or out.stderr).strip().split(chr(10)):
info(' ' + ln)
# ⑤ 这个壳到底有多大
def dir_size(p):
s = 0
for rt, _, fs in os.walk(p):
for f in fs:
try:
s += os.path.getsize(os.path.join(rt, f))
except OSError:
pass
return s
shell = dir_size(root)
stdlib = dir_size(os.path.join(sys.base_prefix, 'Lib'))
info('虚拟环境整个目录 %d 字节,系统标准库 %d 字节,差 %.0f 倍'
% (shell, stdlib, stdlib / shell))
实跑输出:
--------------------------------------------------------------------
搭一个干净的开发环境
--------------------------------------------------------------------
① 先确认手上这个 Python 是什么
--------------------------------------------------------------------
版本 3.13.14
解释器文件 python.exe
装在 C:\Users\admin\.workbuddy\binaries\python\envs\default
! sys.prefix 就是「我现在用的是哪套环境」,后面所有判断都靠它。
② 真建一个虚拟环境出来
--------------------------------------------------------------------
命令:python -m venv gamedev 用时 562 毫秒,退出码 0
建出来的目录:
├── .gitignore
├── Include/
├── Lib/
│ └── site-packages/
├── Scripts/
│ ├── Activate.ps1
│ ├── activate
│ ├── activate.bat
│ ├── activate.fish
│ ├── deactivate.bat
│ ├── python.exe
│ └── pythonw.exe
└── pyvenv.cfg
③ 里面那个 pyvenv.cfg 才是关键
--------------------------------------------------------------------
home = C:\Users\admin\.workbuddy\binaries\python\versions\3.13.12
include-system-site-packages = false
version = 3.13.14
executable = C:\Users\admin\.workbuddy\binaries\python\envs\default\Scripts\python.exe
(还有一行 command 记着建它的完整命令,路径太长,这里不列。)
home 指向原解释器,这说明虚拟环境不是复制了一份 Python,
只是个「壳」,把套件目录指到自己的地盘上。
④ 进去和出来的区别
--------------------------------------------------------------------
用系统解释器跑:
prefix C:\Users\admin\.workbuddy\binaries\python\envs\default
base_prefix C:\Users\admin\.workbuddy\binaries\python\versions\3.13.12
两者相同吗 False
用虚拟环境里那个 python.exe 跑(本沙箱不带 pip,仍能跑代码):
prefix I:\Desktop\自我教学\.workbuddy\scripts\ch59_dev\_tmp59\venv_demo\gamedev
base_prefix C:\Users\admin\.workbuddy\binaries\python\versions\3.13.12
两者相同吗 False
! prefix 变了、base_prefix 没变——套件装进前者,解释器还是后者那个。
⑤ PyCharm 里怎么配
--------------------------------------------------------------------
[平台原样 · 本沙箱不执行]
| File -> Settings -> Project: <你的项目> -> Python Interpreter
| -> 右上角齿轮 -> Add -> Virtualenv Environment
| -> Base interpreter 选系统的 python.exe
| -> Location 选项目里的 .venv
配好之后 PyCharm 右下角会显示当前解释器,点它能直接切。
! PyCharm 是界面操作,本沙箱没有它,上面这段是原样说明,不是运行结果。
⑥ 这个壳有多大
--------------------------------------------------------------------
刚建的虚拟环境整个目录:482.1 KB
系统的标准库目录(Lib):982.0 MB
! 差了 2086 倍,因为它压根没复制标准库,只是指过去。
真按「复制一份完整的 Python」来做,每建一个项目就要多占一份标准库。
所以 venv 选了另一条路:只建壳,共用里面那套东西。
⑦ 三步验收
--------------------------------------------------------------------
| 检查什么 | 怎么查 | 合格的样子 |
|---|---|---|
| 解释器在虚拟环境里 | 看 sys.prefix | 路径里带项目名 |
| 套件装对地方 | pip -V 看路径 | 指向 .venv\Lib\site-packages |
| pygame 能导入 | import pygame | 打印版本号,不报错 |
这三条同时成立,环境才算干净。只成立第一条不够。
第一段先给个参照:你的 sys.prefix 现在指向哪儿。
记住这个路径,后面每装一个包,都可以回来看看它到底装进了哪。
第二段那句命令只有三个词,但 --without-pip 是本沙箱的权宜:建环境这一步要联网装 pip,
这台机器上那一步不通。你自己建的时候去掉这个参数,几秒钟就完事。
第三段是这一篇的核心证据。
home 指向的是另一个目录——不是这个虚拟环境自己的目录,是最初那个真解释器所在的目录。
光这一行就能说明:这是壳,不是副本。
第四段那两个 prefix 值放在一起看最清楚:同一个 probe.py、同一段代码,换一个解释器跑,
prefix 从系统目录变成了虚拟环境目录,而 base_prefix 一动不动。这就是「壳」的全部含义。
第五段那两个字节数是这一篇最有说服力的地方:半兆对一个 G。
如果 venv 真复制一份 Python,每建一个项目就要多占一个 G,那虚拟环境这个设计根本不可能流行起来。
30 秒实验
- 把
pyvenv.cfg里的include-system-site-packages改成true, 再用虚拟环境那个解释器跑probe.py,加一句print(sys.path),看搜索路径多了什么。 - 在虚拟环境里(先 activate)敲
pip install pygame,然后敲pip -V, 看它报告的路径是不是.venv底下那份。 - 把自己项目的
.venv目录右键看属性,和本篇演示里那个「几倍」对一对。
常见误区
- 「虚拟环境是复制一份 Python。
」半兆对一个 G,差两千倍。它只建壳:一个启动器、一个空的
site-packages、 一个pyvenv.cfg。标准库用的是原来那份。 - 「装好包就到处能用。」装进哪个环境就只有那个环境能用。
在系统里装、在虚拟环境里用,是两件不相干的事。 查
sys.prefix就知道自己在哪。 - 「有 IDE 就不用管环境了。 」IDE 只是替你记住「选哪个解释器」, 它不会替你判断这个解释器对不对。 配错了照样报找不到模块,而且报错信息比命令行还难读。
自测三问(点开对答案)
1. 虚拟环境目录只有半兆,系统标准库却有一个 G,那它为什么还能跑代码? —— 因为它根本没复制标准库。 `pyvenv.cfg` 里的 `home` 指向原解释器,启动器找到那个真解释器, 只把 `sys.prefix` 改成自己的目录就跑起来了。 标准库、DLL 全都共用原来那份,各虚拟环境之间互不干扰的只有 `site-packages`。 2. `sys.prefix` 和 `sys.base_prefix` 有什么不一样,什么时候看哪个? —— `prefix` 是「我现在这个环境」,`base_prefix` 是「解释器真正装在哪儿」。 在虚拟环境里两者不同,在系统环境里两者相同。 **装包看 `prefix`**(包会进 `prefix` 下的 `site-packages`),**找解释器看 `base_prefix`**。 3. 为什么本课演示里建虚拟环境加了 `--without-pip`? —— 因为建环境那一步默认要联网装 pip,这台机器上不通。 去掉这参数不影响要看的结论——`pyvenv.cfg`、`prefix` 对比、目录大小这三样都跟 pip 无关。 **但你自己建的时候不要加它**,否则环境里没 pip,装不了 pygame。环境干净了,下一篇进 Pygame:第一次让一个窗口亮起来, 而且要看清楚「亮起来」这件事底下到底发生了什么。
- ☐ (本篇完,下一篇 2.1 第一次让窗口亮起来)