🎮 第1章 起步
📄 一句话概括

装 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 秒实验

  1. 把 pyvenv.cfg 里的 include-system-site-packages 改成 true, 再用虚拟环境那个解释器跑 probe.py,加一句 print(sys.path),看搜索路径多了什么。
  2. 在虚拟环境里(先 activate)敲 pip install pygame,然后敲 pip -V, 看它报告的路径是不是 .venv 底下那份。
  3. 把自己项目的 .venv 目录右键看属性,和本篇演示里那个「几倍」对一对。

常见误区

  1. 「虚拟环境是复制一份 Python。 」半兆对一个 G,差两千倍。它只建壳:一个启动器、一个空的 site-packages、 一个 pyvenv.cfg。标准库用的是原来那份。
  2. 「装好包就到处能用。」装进哪个环境就只有那个环境能用。 在系统里装、在虚拟环境里用,是两件不相干的事。 查 sys.prefix 就知道自己在哪。
  3. 「有 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:第一次让一个窗口亮起来, 而且要看清楚「亮起来」这件事底下到底发生了什么。

相关笔记