📕 第2章 Python 的准备与基本语法
📄 一句话概括

Mac 上装包的步骤和 Windows 一样,只是包管理器换成了 conda 或 brew。 真正该学的不是哪条命令,而是把环境写成一张清单。 清单长这样:一行一个 包名==版本,九十行。 有了它,换机器、上服务器、交给同学,都能一次装回来。 这一节的 Demo 把当前环境导成清单: 90 行、1642 字节;五个包连带出 12 条依赖要求,违例 0 条;新建的干净 venv 里只有 1 个包。

📝 本节和前面几节的关系

上一节讲 Windows 的装包,这一节讲 Mac,重点是「装完怎么留住」。 requirements.txt 这套做法跟平台无关,Windows 也用它,所以这一节对谁都适用。 5.10 节训练卷积网络时,第一件事也是写清楚「这个结果是在什么环境里跑出来的」。

一、Mac 版 Anaconda

macOS 有图形安装包,双击到底就行,默认装到 ~/opt/anaconda3。 装完要执行一次 conda init zsh(或 bash),让 shell 认得 conda 命令。 不 init 的典型症状:装了 Anaconda,但新开终端敲 conda 说找不到命令。 这不是没装,是 shell 没加载它的初始化脚本。 Anaconda 自带一个 base 环境,不要往 base 里装东西。 要用就 conda create -n proj python=3.11,用完 conda deactivate。

二、brew 与 conda 的分界

brew 管的是系统层面的软件(包括 Python 解释器本身);conda 管的是一个环境里的包。 两者都能装 Python,但来源不同。 分界线记住一句:解释器用 brew 或官方 pkg 装一个,包用 conda 或 pip 装进环境。 不要用 brew 装 numpy——它是给整个系统装的,多个项目会互相覆盖。 如果已经混着装了, 用 which python3 和 python3 -c "import sys; print(sys.prefix)"确认当前到底是哪一份。 路径里带 anaconda3 就是 conda 的,带 Cellar 是 brew 的。

三、把环境写成清单

两条命令:pip freeze > requirements.txt 导出,pip install -r requirements.txt 装回。 conda 那边对应 conda env export 和 -f。 导出的清单必须包含版本号,否则「装回来」装的是当天的最新版,和当初跑出结果的环境不是一回事。 清单里常有平台相关的包(比如 Windows 上的 pywin32),换平台装会报错。 处理办法是给这类行加环境标记:pywin32==306; sys_platform == "win32"。

四、锁死与留活口

== 是锁死,>= 是留活口。做实验、写论文、交作业,一律锁死;做库、给别人用,才留下限。 锁死的好处是可复现:同一个 requirements.txt 在任何机器上装出来都一样。 坏处是安全补丁进不来,得手动升。 折中做法是两层:requirements.txt 锁死(给复现用),requirements.in 只写直接依赖(给升级用), 中间用工具生成锁定文件。小项目直接锁死就够了。

五、换机器三步

第一步导清单:在能跑的环境里 pip freeze > requirements.txt。 第二步建空环境:python -m venv v 并激活。 第三步装回去:pip install -r requirements.txt。 三个步骤里最容易漏的是第二步之后的「确认」:装完要跑一遍 python -m pip check, 它会把互相冲突的依赖列出来。 如果装到一半失败,先看是哪一行——通常是某个包在新平台上没有 wheel。 把那一行单独拎出来装,成功后再装其余部分,别整个清单重来。

六、动手 Demo:把环境导成清单,再建一个干净环境对比

两件事:把当前环境的包导成 包名==版本 的清单,再新建一个空 venv 数数它自带几个包。 两个数字放在一起,就看得出「能跑」和「能复现」差在哪。

import importlib.metadata as md, os, subprocess, sys, tempfile

d = {}
for x in md.distributions():
    nm = x.metadata["Name"]
    if nm:
        d[nm] = x.version
lines = sorted("%s==%s" % kv for kv in d.items())

tmp = tempfile.mkdtemp(prefix="req43_")
p = os.path.join(tmp, "requirements.txt")
open(p, "w", encoding="utf-8").write("\n".join(lines) + "\n")
print(len(lines), os.path.getsize(p))

# 新建一个干净环境,看它自带几个包
subprocess.run([sys.executable, "-m", "venv", os.path.join(tmp, "v")],
               check=True)
py = os.path.join(tmp, "v", "Scripts", "python.exe")
print(subprocess.run(
    [py, "-c",
     "import importlib.metadata as m; print(len(list(m.distributions())))"],
    capture_output=True, text=True).stdout.strip())

实跑输出:

--------------------------------------------------------------------
函式库的安装~Mac 篇~:把环境写成清单再复现
--------------------------------------------------------------------

  把当前环境导成一张清单:90 行,1642 字节。前 5 行长这样:

    Markdown==3.10.3
    PyPDF2==3.0.1
    PySDL2==0.9.17
    PyYAML==6.0.3
    Pygments==2.21.0
    ……

  **== 是死锁,意思是「就要这个版本,高一点低一点都不要」。**
  下面换一种写法:只锁下限(>=),看看有多少条其实是被别人牵着走的。

  五个包连带出来的依赖要求共 12 条,其中 0 条在当前环境里不满足。

  一条违例都没有——说明这套环境本身是自洽的。

  最后一步:新建一个干净 venv,里面只有 1 个包。
  **90 行清单 vs 1 个包**——这就是「能跑」和「能复现」的差距。
  所以交作业、上服务器、换电脑,都要带着 requirements.txt,别只说「装个 numpy」。

90 行、1642 字节。平均一行十八个字节,这就是一整套环境的全部信息量。 接下来的核对:从 matplotlib、numpy、av、cryptography、anyio 五个包出发,读它们各自声明的依赖, 共 12 条,逐条判断当前版本是否满足——0 条违例。说明这套环境本身是自洽的, 没有「装了但版本不对」的隐患。 最扎眼的是最后一行:新建的 venv 里只有 1 个包(pip)。 90 对 1——你辛辛苦苦装的九十个包,一点都没跟着过去。 这就是为什么要清单。「我这能跑」这句话在换机器的时候一文不值,能交出去的只有那张清单。

30 秒实验

  1. 在清单里把某一行改成不存在的版本(比如 numpy==9.9.9),执行安装,看报错在哪一步。
  2. 数一数清单里有多少行带 ;(环境标记),那些就是平台相关的包。
  3. 把 90 行清单去掉一半(只留 numpy、matplotlib 和它们的依赖),再装一次, 看能不能跑通本书第 1 章的 Demo。

常见误区

  1. 用 pip freeze 导出的清单直接给别人。 里面混着自己项目的包和平台相关的包,别人装一半就报错。 要么清理,要么加环境标记。
  2. 以为 requirements.txt 会自动处理 Python 版本。 它只管包版本。 Python 版本要另写,一般放在项目 README 或 runtime.txt 里。
  3. 装完不验证。 装成功 ≠ 能跑。 装完跑一遍 pip check 和项目自带的测试,才知道这次复现到底成不成。
自测三问(点开对答案) 1. 为什么导出的清单里必须有版本号? —— 因为不写版本号就装最新版。 半年后最新版换了 API,装出来的环境跑不出当初的结果,而你手上只有一句「以前能跑」。 2. pip freeze 和 pip list 有什么区别? —— freeze 输出的是 `包名==版本` 这种能直接喂给 `pip install -r` 的格式, list 输出的是给人看的表格。导清单用 freeze。 3. 清单里有平台相关的包怎么办? —— 给它加环境标记,比如 `; sys_platform == "win32"`,pip 会只在对应平台上装它, 换平台时自动跳过。

环境这套到此结束。最后一节回到语言本身——把这本书后面要用到的 Python 语法一次过完,边跑边看结果。

相关笔记