pip 不是 Python 语法,它是个独立程序,只是刚好用 Python 写的。
所以要用 python -m pip 叫它,保证用的是同一个解释器。
pip list 看到的是包名,import 用的是模块名,两个名字经常不一样。
opencv-python-headless 装完 import 的是 cv2,Pillow 装完 import 的是 PIL。
MP4 是盒子,H.264 是盒子里装的东西。同一个盒子可以装不同编码,所以「下个 mp4」这句话并不精确。
前四章只用标准库,一个第三方包装都没碰。这一章开始装东西。
教材第 5 章开头两节是【使用 pip 安装 Python 套件】和【YouTube 影音的 Codec 与下载模块】。
两节看着不搭界,其实是一件事的两步:先学会装工具,
再搞清楚这个工具要处理的「视频格式」到底是什么。
本篇的演示全部真跑,包括 pip list 和那条失败的 import。
只有 yt-dlp 的格式串标了「平台原样 · 本沙箱不执行」——那台机器上没装它。
一、pip 不是语法,是一个程序
import 是 Python 语法,pip 不是。pip 是一个独立的程序,只是碰巧用 Python 写的。
所以你可以在命令行敲 pip install 包名,也可以敲 python -m pip install 包名。
两条命令做的事情一样,但后者更安全。
为什么?因为一台机器上可能装了好几个 Python(系统自带一个、你自己装的又一个、虚拟环境里还有)。
pip 这个词可能指向其中一个,python -m pip 明确说「用我指定的这个解释器去跑它自己的 pip」。
装了多个版本时,这是「明明装了却 import 不到」的头号原因。第 10 章讲虚拟环境会再碰到这件事。
二、包名和模块名,经常不是一回事
pip list 打出来的是包名,import 写的是模块名。
大部分时候它们一样,但不一样的情况非常多。
| pip 装的包名 | import 写的模块名 |
|---|---|
opencv-python-headless |
cv2 |
Pillow |
PIL |
beautifulsoup4 |
bs4 |
scikit-learn |
sklearn |
PyYAML |
yaml |
演示第二段把本机装了的四个包列出来了:flask 3.1.3、requests 2.34.2、numpy 2.5.2、
opencv-python-headless 4.10.0.84。
最后一个就是典型:装的时候叫 opencv-python-headless,用的时候写 import cv2。
名字对不上的时候,去看包的文档第一页,那儿一定写了「怎么 import」。
pip show 包名 也能帮上忙,它会打出包的位置和它提供的模块。
实在找不到,import 一下看报什么错,报错信息里会写清楚找不到的是哪个名字。
三、没装会怎样:ModuleNotFoundError
演示第三段 import yt_dlp 直接抛 ModuleNotFoundError: No module named 'yt_dlp'。
这个名字值得记:它是 ImportError 的子类,专门表示「这个名字我压根找不到」。
看到这个错,九成是没装,剩下一成是装到了另一个解释器里。
「装到了另一个解释器」是什么情况?你在 A 环境 pip install 了,但跑代码用的是 B 环境。
症状跟没装一模一样。
排查办法:python -c "import sys; print(sys.executable)" 看当前用的是哪个解释器,
python -m pip list 看这个解释器里有什么。两个对上了,问题就找到了。
四、解释器去哪儿找模块
sys.path 是一个列表,import 的时候从上往下找,找到就停。演示第四段把前五条打出来了。
第一条通常是当前目录(或者脚本所在目录,取决于你怎么启动的——第 1.2 篇讲过)。
最后几条是标准库和 site-packages。
「从上往下找,找到就停」这条规则解释了两种怪事。
第一种:你自己写了个 random.py,放在脚本旁边。
结果 import random 拿到的是你的文件,不是标准库那个——因为当前目录排在前面。
这是新手非常容易踩的坑,而且报错很莫名其妙。
第二种:装了新版本却 import 到旧的。多半是旧版本还在前面某个目录里躺着。
pip show 看位置,sys.path 看顺序,一比就明白。
所以别用标准库的名字给你的文件起名。random.py、json.py、email.py 都是雷区。
五、容器和编码是两回事
要下载视频,得先搞清楚「格式」这个词到底指什么。它其实是两层。
| 容器(后缀) | 常见视频编码 | 常见音频编码 | 典型用途 |
|---|---|---|---|
| MP4(.mp4) | H.264/H.265 | AAC | 兼容性最好 |
| WebM(.webm) | VP9/AV1 | Opus | 网页用,体积小 |
| MKV(.mkv) | 几乎都行 | 几乎都行 | 收藏,能装多字幕 |
| MP3(.mp3) | 没有视频 | MP3 | 纯音频 |
MP4 是盒子,H.264 是盒子里装的东西。 同一个盒子可以装不同的编码。 所以「下个 mp4」这句话并不精确。 你想要的多半是「H.264 的 mp4」——因为老设备、老电视、某些播放器只认这一个组合。 这就是下载工具为什么非要让你选格式:它得知道你要哪个盒子、里面装哪种编码、什么分辨率。 三件事,一个都不能少。
六、格式选择串长什么样
yt-dlp 用一串自己的小语言来描述「我要什么」。它不是 Python 语法,是那个工具自己定的规则。
bv*[height<=1080]+ba/b[height<=1080] 这么一串,拆开看是四段:bv* 要最好的视频流、
[height<=1080] 高度不超过 1080、+ba 再配一条最好的音频流、/ 后面是「前面不成,就试这个」。
最后那半段 b[height<=1080] 是兜底:单个文件、高度不超过 1080。两个都拿不到就算了。
为什么要视频音频分开下? 因为 YouTube 的高清视频流里通常没有声音,声音是单独一条。 分开下完,还得用 FFmpeg 把两条合成一个文件。 这就是这一章后半段要装 FFmpeg 的原因。不是可选步骤,是必需步骤。
七、动手 Demo:pip 装的是包,import 进的是模块
先看 pip 自己是谁、装了什么、装在哪。
再故意 import 一个没装的东西,看报错长什么样。
然后把 sys.path 打出来,理解「从上往下找」这条规则。
import subprocess, sys
P = sys.executable
def sh(args):
r = subprocess.run([P, '-m'] + args, capture_output=True, text=True)
return (r.stdout + r.stderr).strip()
print('① pip 自己是个程序')
print(' python -m pip --version')
print(' ' + sh(['pip', '--version']))
print('② 已装了什么、装在哪')
out = sh(['pip', 'list'])
want = ('flask', 'requests', 'numpy', 'opencv-python-headless')
for ln in out.splitlines():
low = ln.lower()
if any(low.startswith(w) for w in want):
print(' ' + ln)
print(' flask 装在哪:' + sh(['pip', 'show', 'flask']).split('Location: ')[1].splitlines()[0])
print('③ 装了才能 import,没装会怎样')
try:
import yt_dlp
except ModuleNotFoundError as e:
print(' import yt_dlp → ModuleNotFoundError:', e)
import builtins
print(' ModuleNotFoundError 是 ImportError 的子类:',
issubclass(ModuleNotFoundError, ImportError))
print('④ 解释器去哪儿找模块')
for p in sys.path[:5]:
print(' ' + p)
print(' sys.path 是一个列表,import 时从上往下找,找到就停。')
print('⑤ 视频的 Codec:容器和编码是两回事')
rows = [('MP4(.mp4)', 'H.264 / H.265', 'AAC', '兼容性最好,手机网页都认'),
('WebM(.webm)', 'VP9 / AV1', 'Opus', '网页用,体积小'),
('MKV(.mkv)', '几乎都行', '几乎都行', '收藏用,能装多条字幕'),
('MP3(.mp3)', '没有视频', 'MP3', '纯音频')]
print('| 容器(文件后缀) | 常见视频编码 | 常见音频编码 | 典型用途 |')
print('|---|---|---|---|')
for r in rows:
print('| %s | %s | %s | %s |' % r)
print('⑥ 格式选择串长什么样')
print(' [平台原样 · 本沙箱不执行]')
print(' | # yt-dlp 的格式选择串(平台原样,本沙箱不执行)')
print(' | yt-dlp -f "bv*[height<=1080]+ba/b[height<=1080]" URL')
print(' |')
print(' | # 拆开看:')
print(' | # bv* 最好的视频流(* 表示可以是纯视频)')
print(' | # [height<=1080] 高度不超过 1080')
print(' | # +ba 再配一条最好的音频流')
print(' | # / 前面不成,就试后面这个')
print(' | # b[height<=1080] 退而求其次:单个文件,高度不超过 1080')
实跑输出:
--------------------------------------------------------------------
pip 装的是包,import 进的是模块
--------------------------------------------------------------------
① pip 自己是个程序
--------------------------------------------------------------------
python -m pip --version
pip 26.1.2 from C:\Users\admin\.workbuddy\binaries\python\envs\default\Lib\site-packages\pip (python 3.13)
pip 不是 Python 语法的一部分,它是个独立程序,只是刚好用 Python 写的。
所以要用 python -m pip 这种方式叫它,保证用的是同一个解释器。
② 已装了什么、装在哪
--------------------------------------------------------------------
flask 3.1.3
requests 2.34.2
numpy 2.5.2
opencv-python-headless 4.10.0.84
flask 装在哪:es\python\envs\default\Lib\site-packages\flask
pip list 看到的是一堆包名,import 用的是模块名。
两个名字不一样是常事:opencv-python-headless 装完 import 的是 cv2,Pillow 装完 import 的是 PIL。
查不到就去看包的文档。
③ 装了才能 import,没装会怎样
--------------------------------------------------------------------
import yt_dlp → ModuleNotFoundError: No module named 'yt_dlp'
ModuleNotFoundError 是 ImportError 的子类。
看到这个错,九成是没装,剩下一成是装到了另一个解释器里。
④ 解释器去哪儿找模块
--------------------------------------------------------------------
I:\Desktop\自我教学\.workbuddy\scripts\ch58_dev
D:\WorkBuddy\resources\app.asar.unpacked\cli\vendor\shim
C:\Users\admin\.workbuddy\binaries\python\versions\3.13.12\python313.zip
C:\Users\admin\.workbuddy\binaries\python\versions\3.13.12\DLLs
C:\Users\admin\.workbuddy\binaries\python\versions\3.13.12\Lib
sys.path 是一个列表,import 时从上往下找,找到就停。
所以同名的模块,排在前面的目录会赢——这也是「装了却 import 到旧版本」的常见原因。
⑤ 视频的 Codec:容器和编码是两回事
--------------------------------------------------------------------
| 容器(文件后缀) | 常见视频编码 | 常见音频编码 | 典型用途 |
|---|---|---|---|
| MP4(.mp4) | H.264 / H.265 | AAC | 兼容性最好,手机网页都认 |
| WebM(.webm) | VP9 / AV1 | Opus | 网页用,体积小 |
| MKV(.mkv) | 几乎都行 | 几乎都行 | 收藏用,能装多条字幕 |
| MP3(.mp3) | 没有视频 | MP3 | 纯音频 |
MP4 是盒子,H.264 是盒子里装的东西。
同一个盒子可以装不同编码,所以「下个 mp4」这句话并不精确——你想要的是「H.264 的 mp4」,
因为老设备只认这个。
这就是下载工具为什么要让你选格式:它得知道你要哪个盒子、里面装哪种编码、什么分辨率。
⑥ 格式选择串长什么样
--------------------------------------------------------------------
[平台原样 · 本沙箱不执行]
| # yt-dlp 的格式选择串(平台原样,本沙箱不执行)
| yt-dlp -f "bv*[height<=1080]+ba/b[height<=1080]" URL
|
| # 拆开看:
| # bv* 最好的视频流(* 表示可以是纯视频)
| # [height<=1080] 高度不超过 1080
| # +ba 再配一条最好的音频流
| # / 前面不成,就试后面这个
| # b[height<=1080] 退而求其次:单个文件,高度不超过 1080
这一串是 yt-dlp 自己的小语言,不是 Python 语法
为什么要视频音频分开下:YouTube 的高清视频流里通常没有声音,声音单独一条。
下载器下完还得用 FFmpeg 把两条合起来——这就是第 5-8 节要装 FFmpeg 的原因。
第一段那行版本信息里有三样东西:pip 自己的版本、它装在哪、跟着哪个 Python。 第三样最重要——它保证你装的东西进的是同一个解释器。 第二段注意最后一行:包名叫 opencv-python-headless,但代码里得写 import cv2。 这种不一致非常常见,不是你的错,去看文档第一页就行。 第三段那句 issubclass 为 True,说明 ModuleNotFoundError 是 ImportError 的一种。 所以写 except ImportError 也能抓到它。 第四段前五条路径要看顺序:当前目录排在最前面。 这就是为什么你自己写个 random.py 会顶掉标准库那个——同一个名字,前面的赢。 第六段那串是 yt-dlp 自己的小语言,不是 Python。 斜杠的意思是「前面不成试后面」,加号的意思是「两个都要,下完合起来」。
30 秒实验
- 敲
python -m pip install requests,看输出里「already satisfied」这行说明什么。 - 在演示第四段后面加一行
print(len(sys.path)),看你这台机器上一共几条。 - 在脚本旁边建一个空文件叫
random.py,然后import random再print(random.__file__), 看它指向哪个文件——看完记得删掉。
常见误区
- 「pip 装的包名就是 import 的名字。」经常不是。 opencv-python-headless 装完 import cv2,Pillow 装完 import PIL。 名字对不上看文档。
- 「ModuleNotFoundError 一定是没装。」也可能是装到了另一个解释器里。
用
sys.executable看当前解释器,用python -m pip list看它里面有什么。 - 「mp4 就是一种格式。」mp4 是容器,里面装什么编码是另一回事。 同一个 mp4 里可能是 H.264,也可能是 H.265。 老设备只认前者。
自测三问(点开对答案)
1. 为什么推荐写 `python -m pip install 包名`,而不是直接 `pip install 包名`? —— 因为一台机器上可能有多个 Python,`pip` 这个词可能指向其中一个。 `python -m pip` 明确说「用这个解释器跑它自己的 pip」,装的东西一定进这个环境。 多个版本共存时,这是「装了却 import 不到」的主因。 2. `pip list` 里明明有 opencv-python-headless,`import opencv` 却报错,为什么? —— 因为包名和模块名不是一回事。这个包装完之后模块名叫 `cv2`,得写 `import cv2`。 `pip show 包名` 能看到它提供的模块,文档第一页也会写。 3. MP4 和 H.264 是什么关系? —— MP4 是容器(盒子),H.264 是编码(盒子里装的东西)。 同一个 MP4 文件里可以是 H.264,也可以是 H.265。 所以「下个 mp4」这种说法不精确,得说清「哪个盒子装哪种编码」。下一篇给这个下载器做规划:默认存哪、选项怎么去重、用户没给的参数怎么补。
- ☐ (本篇完,下一篇 5.2 程序规划:默认路径与 set 选项)