🧠 第三章 编程语言
📄 一句话概括

同一个 probe.py,不设 PYTHONPATH 时返回码 1、报 ModuleNotFoundError; 设上就返回 0、打出 VALUE = 38。两个目录里放同名模块,PYTHONPATH 谁在前就 import 谁。

📝 本节和前面几节的关系

上一节装好了 Python,这一节解决「为什么装好了还是找不到」。答案几乎总在环境变量里。

一、PATH 和 sys.path 是两条路

这两个名字长得像,管的事完全不同: - PATH 管敲命令:你打 python,系统按 PATH 的条目一个个找,找到第一个就用。 - sys.path 管 import:你写 import requests,Python 按这个列表一个个找同名的 .py 或包目录。 改 PATH 不会让 import 变好,改 PYTHONPATH 也不会让命令找得到。 搞混这两个,是新手最容易卡住的地方。

二、PYTHONPATH 的作用

PYTHONPATH 是专门给 import 用的:里面的每个目录都会被插到 sys.path 前面(在标准库之前、 脚本目录之后)。 Demo 把一个 mytool.py 放进一个不在 sys.path 里的中文目录,然后起子进程跑 import mytool。 不设 PYTHONPATH:返回码 1,报 ModuleNotFoundError: No module named 'mytool'。 设上:返回码 0,打出 import 到了,VALUE = 38。 同一份代码、同一个解释器,只差一个环境变量,一个能跑一个不能。这就是为什么别人能跑你不能跑。

三、顺序决定谁生效

更麻烦的情况是同名:两个目录里都有 same.py,内容不同,import 到哪个? Demo 的答案很干脆:PYTHONPATH 里谁在前就是谁。A 在前打出 A 版,B 在前打出 B 版。 所以名字冲突是静默的错误——不报错,只是用了错的那个文件。 排查这种问题只能靠打印 模块.__file__。

四、本机的 sys.path 长什么样

Demo 列出本进程前 5 项:第 0 项是脚本所在目录,接着是一个 shim 目录,然后是 python313.zip、 DLLs、Lib——一共 11 项。 注意第 0 项就是当前脚本所在的目录。 这解释了一个常见现象:同一个文件名,你在不同目录下运行结果不一样。 python313.zip 出现在列表里,说明标准库有一部分是打包成 zip 的。 这也是为什么有的模块「明明没装却能 import」——它在标准库里。 排查口诀:先打 sys.path,再看 __file__。 两行代码,能解决九成的「模块找不到」和「用错了模块」。

五、动手 Demo:加不加 PYTHONPATH,同一个 import 两种命

在临时目录里造一个模块和一个探针脚本, 用子进程分别在不带 PYTHONPATH 和带 PYTHONPATH 的环境下各跑一次; 再造两个同名模块,交换 PYTHONPATH 的顺序,看 import 到的是哪一个。 最后打印本进程 sys.path 的前五项。

env = dict(os.environ)
env.pop('PYTHONPATH', None)
r1 = subprocess.run([sys.executable, probe], capture_output=True,
                    text=True, env=env)

env['PYTHONPATH'] = mod_dir
r2 = subprocess.run([sys.executable, probe], capture_output=True,
                    text=True, env=env)

for order in ((dir_a, dir_b), (dir_b, dir_a)):
    env['PYTHONPATH'] = os.pathsep.join(order)
    subprocess.run([sys.executable, probe2], env=env)

实跑输出:

--------------------------------------------------------------------
环境变量到底管什么:同一个 import,加不加 PYTHONPATH 两种命
--------------------------------------------------------------------
  模块放在                 一个不在 sys.path 里的中文目录

  不设 PYTHONPATH          返回码 1
  报错最后一行             ModuleNotFoundError: No module named 'mytool'
  设了 PYTHONPATH          返回码 0
  标准输出                 import 到了,VALUE = 38

  PYTHONPATH 顺序 A 在前   A 版
  PYTHONPATH 顺序 B 在前   B 版

  序   本进程 sys.path 这一项
  ----------------------------------------------------------------------------
   0   I:\Desktop\自我教学\.workbuddy\scripts\ch38_dev
   1   D:\WorkBuddy\resources\app.asar.unpacked\cli\vendor\shim
   2   C:\Users\admin\.workbuddy\binaries\python\versions\3.13.12\python313.zip
   3   C:\Users\admin\.workbuddy\binaries\python\versions\3.13.12\DLLs
   4   C:\Users\admin\.workbuddy\binaries\python\versions\3.13.12\Lib
  这 11 项谁在前面,谁就先生效——不是「都一样」。

不设 PYTHONPATH 那次返回码是 1, 最后一行是 ModuleNotFoundError: No module named 'mytool'——报错原文直接告诉你缺哪个名字。 设上之后返回码 0,打出 import 到了,VALUE = 38。 环境变量是进程级的,所以必须用子进程验证,在本进程里改 os.environ 对已经启动的解释器没用。 同名模块那一组:A 在前打 A 版,B 在前打 B 版。不报错、不警告,只是悄悄用了另一个文件。 本进程 sys.path 共 11 项,第 0 项是脚本所在目录,第 1 项是个 shim 目录。 这两个不是标准库给的,是启动方式带进来的。

30 秒实验

  1. 写两行 import sys; print(sys.path) 和 import 某模块; print(某模块.__file__),存成备忘片段。
  2. 把项目根目录加进 PYTHONPATH,看能不能省掉一堆 sys.path.append。
  3. 想一想:为什么 PYTHONPATH 在标准库之前生效,会带来风险?

常见误区

  1. 「PATH 加进去就能 import 了」——那是找命令,不是找模块。
  2. 「装了两个同名包也没事,反正都能 import」——只会用一个,另一个永远不生效。
  3. 「报 ModuleNotFoundError 就是没装」——也可能装了但不在 sys.path 里。
自测三问(点开对答案) 1. `PATH` 和 `PYTHONPATH` 分别管什么? —— 前者管命令行找可执行文件,后者管 import 找模块。 2. 两个目录有同名模块,谁生效? —— `PYTHONPATH` 里排在前面的那个,且不会报错。 3. 排查「模块找不到」先看什么? —— 打印 `sys.path`,再打印目标模块的 `__file__`。

环境配好,可以写第一行代码了——从最简单的「打出一串英文字符」开始。

相关笔记