Day03 · 虚拟环境与依赖管理入门
阶段:阶段一 · 核心基础 | 前置:Day02 | 技术基准:Python 3.15.0
学习目标
- 说清楚为什么每个 Python 项目都要有自己独立的虚拟环境,它和"全局环境"到底差在哪
- 熟练用标准库
venv完成创建、激活、装包、退出的完整闭环 - 掌握 pip 的
install/uninstall/list/show四个最常用命令,会用-i临时换镜像、用-U升级 - 会用
pip freeze导出requirements.txt,再用pip install -r在新环境一键复现 - 认识现代项目配置文件
pyproject.toml长什么样,知道它和requirements.txt的分工 - 从今天起养成"每个项目一个 venv"的工作习惯
昨天(Day02)我们装好了 Python 3.15.0 解释器,也在最后走马观花地看了一眼 venv。今天就把这第一件"工程习惯"彻底讲透、亲手跑通。别小看它------很多新手学了半个月语法就卡住,原因不是语法难,而是装包装乱了、环境搞脏了,最后连自己写的脚本都跑不起来。今天这一天,是后面所有"装第三方库"的日子(Flask、NumPy、爬虫、机器学习......)的地基。
一、为什么需要虚拟环境
1.1、没有虚拟环境会出什么事
刚学 Python 的时候,我们最自然的动作就是打开命令行,直接敲 pip install requests。这条命令看着很省事,但它把包装到哪去了?答案是:装到全局解释器 的 Lib\site-packages\ 目录里------也就是你电脑上那唯一一个 Python 3.15.0 共享给所有项目用的"大仓库"。
只做一个小练习时,这完全没问题。可一旦你开始同时碰两个以上的项目,三件经典事故就会轮流找上门:
- 全局环境污染:每个新项目都往同一个大仓库里倒包。三个月后你根本记不清:到底装了哪些包?哪个版本是干嘛的?哪个包是为了哪个项目装的?大仓库变成了一个谁也不敢动的杂物间。
- 版本冲突:项目 A 要求用 Flask 2.x 写,项目 B 却依赖一个只兼容 Flask 3.x 的库。全局环境只能装一个 Flask,两个项目注定有一个跑不起来,你只能反复卸了装、装了卸。
- 不可复现 :你把代码发给同事,同事一
pip install,pip 给他装的是"今天最新版"。万一这个最新版改了接口(所谓 breaking change),"我电脑上明明是好的"这句话,就成了事故的开端。
虚拟环境(Virtual Environment)就是冲着这三件事来的:给每个项目单独开一个小仓库,各装各的,互不打扰。
1.2、全局环境 vs 虚拟环境:一次实测对比
空口无凭,小嘉在本机(Python 3.15.0)直接对比给你看。先看全局环境里装了多少东西:
text
$ py -3 -m pip list
Package Version
------------------ ------------
click 8.5.0
hatch 1.18.1
numpy 2.5.4
pandas 3.0.6
pytest 9.1.1
requests 2.34.2
rich 15.0.0
six 1.17.0
...(此处省略,全局共约 90 个包)
再看一个刚创建、什么都没装的全新虚拟环境:
text
$ .\.venv\Scripts\python.exe -m pip list
Package Version
------- -------
pip 26.2.1
看出来了吗?全新的虚拟环境里只有 pip 自己一个包,全局环境里那九十多个包它一个都看不见。这就是隔离的直观效果。两者的区别可以列成一张表:
| 对比项 | 全局环境 | 虚拟环境 |
|---|---|---|
| 包装到哪 | 全局 Lib\site-packages\(所有项目共享) |
项目目录里的 .venv\Lib\site-packages\(仅此项目用) |
| 隔不隔离 | 不隔离,项目之间互相影响 | 隔离,项目 A 装的包项目 B 完全看不见 |
| 装错了怎么办 | 要手动一个个卸,容易留残渣 | 不用了整个 .venv 文件夹删掉,系统零残留 |
| 适合谁 | 跑系统自带的小程序 | 真实项目(本课程的主线做法) |
1.3、venv 到底是什么
venv 是 Python 标准库 自带的模块(自 Python 3.3 起内置),不需要你 pip install 任何东西。它不是"再装一个 Python",而是在你项目目录里建一个文件夹 (约定叫 .venv),这个文件夹里放一份指向系统 Python 的"小启动器",外加一个属于你这个项目自己的 site-packages\ 目录。
一句话记住:venv = 一个文件夹 + 一份独立的装包小仓库。 后面 Day61 会深入讲它背后的三个机制(解释器重定向、独立 site-packages、激活改 PATH),今天我们先用起来。
二、venv 的创建、激活与退出
2.1、创建:py -3 -m venv .venv
先 cd 进入你的项目根目录,然后执行:
bash
# 用 Python 3.15 解释器,在当前目录创建一个名为 .venv 的虚拟环境
py -3 -m venv .venv
小嘉提醒:为什么写
py -3 -m venv而不是直接python -m venv?和 Day02 讲的一样------你的电脑上可能装了好几个 Python 版本,py -3明确锁定"用 3 系列里的那个解释器"。本机实测它就是 Python 3.15.0,避免"这个环境是被别的版本顺手建的"这种经典坑。
目录名约定俗成用 .venv(前面那个点让它在 Linux/macOS 上变成隐藏目录,主流 IDE 也都默认认识这个名字,会自动把它当虚拟环境)。
2.2、激活:让"python / pip"指向这个小环境
创建完 .venv 之后,它还"没生效"------你直接敲 python,用的还是全局那个。必须先激活 它,系统才会把命令行里的 python、pip 都指向 .venv 里的那一份。不同平台命令不一样:
powershell
# Windows PowerShell
. .\.venv\Scripts\Activate.ps1
bash
# Windows 命令提示符(CMD)
.venv\Scripts\activate.bat
# Linux / macOS
source .venv/bin/activate
激活成功的标志是:命令行提示符前面多出一个 (.venv) 标记,长这样:
text
(.venv) C:\myproject>
小嘉提醒:Windows 的 PowerShell 默认可能禁止运行
.ps1脚本,激活时报"无法加载文件,因为在此系统上禁止运行脚本"。临时解决办法是在当前 PowerShell 窗口里先执行一句Set-ExecutionPolicy -Scope Process Bypass -Force(只放开当前这一个窗口,不改系统设置),或者干脆改用上面 CMD 的activate.bat。
不想激活也可以 :在非交互脚本里,直接用 .venv 里的解释器全路径去跑 pip,效果完全等价(本课程所有实测就是这么跑的):
powershell
# 不激活,直接点名 venv 里的 python 来调用 pip
.\.venv\Scripts\python.exe -m pip install six
2.3、验证隔离:看一眼"现在用的到底是哪个 Python"
激活(或直接用 venv 解释器)之后,最直观的验证方法是看 sys.executable------它告诉你当前运行的是哪个 python.exe。本机实测:
python
import sys
print("当前解释器:", sys.executable)
print("环境前缀:", sys.prefix)
print("基础解释器:", sys.base_prefix)
text
当前解释器: 项目目录\.venv\Scripts\python.exe
环境前缀: 项目目录\.venv
基础解释器: D:\devtools\Python
看这三行就能确认隔离成立:
当前解释器指向了.venv\Scripts\python.exe------命令行里的 python 已经是这个小环境的了;环境前缀是.venv自己------它有独立的"地盘";基础解释器还是全局的D:\devtools\Python------venv 并没有真的复制一份解释器,而是"借"全局的来跑,只是装包、查包都只认自己的小仓库。
再对比一下装包前后。全新 venv 里 pip 只认识自己:
text
$ .\.venv\Scripts\python.exe -m pip list
Package Version
------- -------
pip 26.2.1
2.4、退出:deactivate
今天用完了,想回到全局环境,在命令行敲:
bash
deactivate
提示符前面那个 (.venv) 标记就消失了。退出后,你在 venv 里装的包并没有被删除,它们还好好躺在 .venv\Lib\site-packages\ 里;只是当前命令行"看不见"它们了------下次激活同一个 .venv,它们立刻又能用。这正是"每个项目一个小仓库"的好处。
2.5、.venv 目录约定与 .gitignore
.venv 整个文件夹不要 提交到 git 仓库、不要拷给别人------它体积不小,而且可以在任何一台机器上用 requirements.txt 一键重建 (第四节讲)。所以项目根目录的 .gitignore 里加一行:
text
# .gitignore
.venv/
2.6、pyvenv.cfg 初识
打开 .venv 文件夹根目录,里面有个很小的 pyvenv.cfg 文件,记录了这个环境是"从哪个 Python 建出来的"。本机实测内容:
text
home = D:\devtools\Python
include-system-site-packages = false
version = 3.15.0
现在只需认识其中一行:include-system-site-packages = false。它就是隔离开关 ------为 false 表示这个 venv 不去看全局大仓库里的任何第三方包。这就是"为什么全局有 90 个包、venv 却一个都看不见"的答案。这几行背后的完整原理,Day61 会再带你精读一遍。
三、pip 基础命令
pip 是 Python 的包管理器,负责从 PyPI(Python Package Index,全球第三方仓库)下载、安装、升级、卸载第三方库。venv 里已经自带 pip,不需要额外装。记住一个稳妥写法:python -m pip 子命令------它保证用的是"当前这个环境"的 pip,不会误用全局的。
3.1、安装:pip install
我们往刚建的 venv 里装两个很小的纯 Python 包 six 和 pycodestyle,本机实测完整过程:
bash
python -m pip install six pycodestyle
text
Looking in indexes: https://mirrors.huaweicloud.com/repository/pypi/simple/
Collecting six
Downloading six-1.17.0-py2.py3-none-any.whl (11 kB)
Collecting pycodestyle
Downloading pycodestyle-2.15.0-py2.py3-none-any.whl (31 kB)
Installing collected packages: six, pycodestyle
Successfully installed pycodestyle-2.15.0 six-1.17.0
注意最后一行 Successfully installed ...------这就是装成功的标志。哪怕全局环境里本来就有 six,venv 里还是会重新下载一份装进自己的小仓库,两边互不影响。
3.2、查看已装:pip list 与 pip show
pip list 列出当前环境里所有已装的包和版本号。装完后再看一眼:
bash
python -m pip list
text
Package Version
----------- -------
pip 26.2.1
pycodestyle 2.15.0
six 1.17.0
想查某个包的详细信息(作者、主页、装在哪、还被谁依赖),用 pip show 包名:
bash
python -m pip show six
text
Name: six
Version: 1.17.0
Summary: Python 2 and 3 compatibility utilities
Home-page: https://github.com/benjaminp/six
Author: Benjamin Peterson
License: MIT
Location: 项目目录\.venv\Lib\site-packages
Requires:
Required-by:
重点看 Location 这一行:它指向的是项目目录里的 .venv\Lib\site-packages,而不是全局目录。这就用 pip 自己的话,证明了"包装进了小环境、没污染全局"。
3.3、卸载:pip uninstall
不想要了就卸掉。加 -y 表示"不再问我确认,直接卸"。本机实测把 pycodestyle 卸掉:
bash
python -m pip uninstall pycodestyle -y
text
Found existing installation: pycodestyle 2.15.0
Uninstalling pycodestyle-2.15.0:
Successfully uninstalled pycodestyle-2.15.0
卸完再 pip list,pycodestyle 就从列表里消失了,只剩 pip 和 six。
3.4、常用选项:-i 镜像源 与 -U 升级
国内直连 PyPI 官方源有时很慢 ,可以用 -i(等价 --index-url)临时指定一个国内镜像站。本机实测用清华源再装一次 six(因为已经装过了,pip 会提示 already satisfied,我们重点看它真的去问了清华源):
bash
python -m pip install six -i https://pypi.tuna.tsinghua.edu.cn/simple
text
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Requirement already satisfied: six in .\.venv\Lib\site-packages (1.17.0)
常用镜像源记两三个就够:清华 https://pypi.tuna.tsinghua.edu.cn/simple、阿里云 https://mirrors.aliyun.com/pypi/simple/、中科大 https://pypi.mirrors.ustc.edu.cn/simple/。
升级某个包 用 -U(等价 --upgrade):
bash
# 把 six 升级到最新版
python -m pip install -U six
小嘉提醒:装包、卸包、升级之前,养成先看一眼命令行提示符前有没有
(.venv)的习惯。没有它,你辛苦建的隔离就白费了,包装到全局去了。
四、requirements.txt 基础用法
4.1、它解决什么问题
虚拟环境解决了"项目之间不打架",但还有个问题没解决:怎么把这个项目的环境原样搬到另一台机器? 总不能把整个 .venv 文件夹拷过去(里面是和你电脑路径绑死的东西,拷过去也用不了)。
标准答案是一个纯文本文件 requirements.txt:把"这个环境里装了哪些包、各自什么版本"记下来。别人拿到项目后,新建一个空 venv,再照着这份清单一条命令把包装回来,环境就和你一模一样。这就是"可复现"。
4.2、导出:pip freeze
在已经激活(或正在用)这个项目 venv 的状态下执行 pip freeze,它会把当前环境所有包连同精确版本号打印出来:
bash
python -m pip freeze
text
pycodestyle==2.15.0
six==1.17.0
把这份输出重定向到文件,就得到了 requirements.txt:
bash
# 把冻结清单写入项目根目录的 requirements.txt
python -m pip freeze > requirements.txt
小嘉提醒:一定要在你自己项目的 venv 里 freeze!如果忘了激活、在全局环境里 freeze,这份清单会把全局那九十多个无关包全列进去,换机器复现时必出问题。
4.3、复现:pip install -r requirements.txt
现在做个实验:小嘉新建了第二个完全干净的 venv(初始只有 pip),然后执行:
bash
# 在第二个干净 venv 里,照着 requirements.txt 把包装回来
python -m pip install -r requirements.txt
text
Looking in indexes: https://mirrors.huaweicloud.com/repository/pypi/simple/
Collecting pycodestyle==2.15.0 (from -r requirements.txt (line 1))
Downloading pycodestyle-2.15.0-py2.py3-none-any.whl (31 kB)
Collecting six==1.17.0 (from -r requirements.txt (line 2))
Downloading six-1.17.0-py2.py3-none-any.whl (11 kB)
Installing collected packages: six, pycodestyle
Successfully installed pycodestyle-2.15.0 six-1.17.0
装完再 pip list,第二个干净环境里就出现了和第一个环境完全相同的两个包、完全相同的版本号:
text
Package Version
----------- -------
pip 26.2.1
pycodestyle 2.15.0
six 1.17.0
这就是 requirements.txt 的全部魔力:一份文本清单 + 一条命令 = 换台机器也能复现同一个环境。
4.4、最小 requirements.txt 长什么样
它本质上就是一个"每行一个包 + 版本号"的纯文本文件,本机实测内容:
text
# requirements.txt
pycodestyle==2.15.0
six==1.17.0
每行格式是 包名==版本号,双等号表示"精确锁定这个版本"。今天先会用 pip freeze 自动生成、会用 pip install -r 安装就够了;至于 >=、~= 这些版本运算符、base/dev/prod 多文件怎么拆分、和 poetry/uv 的 lockfile 有什么区别------这些进阶内容统一放在 Day61,今天不展开。
五、pyproject.toml 初识
5.1、它是什么
pyproject.toml 是现代 Python 项目的"统一配置中心",遵循 PEP 621 规范。从 Python 3.12 起,旧的打包模块 distutils 已经被移除,现在不管是发布自己写的库,还是用现代工具(pip-tools、poetry、uv 等)管理项目,都围着这个文件转。它用一种叫 TOML 的简单格式写配置(Python 3.11+ 标准库的 tomllib 模块就能直接读它)。
5.2、一个最小示例
一个最简单的项目,根目录放一个 pyproject.toml,长这样:
toml
# 文件位置:项目根目录\pyproject.toml
[project]
name = "xiaojia-demo"
version = "0.1.0"
description = "跟小嘉学 Python 的第一个示例项目"
requires-python = ">=3.15"
dependencies = [
"six>=1.16",
"pycodestyle>=2.11",
]
现在你不需要背它的语法,只要认得:[project] 这一段是在"声明这个项目叫什么、需要哪些第三方库"。
5.3、它和 requirements.txt 的分工(一句话)
今天先建立一个直觉:pyproject.toml 是"声明"------这个包是什么、大概需要哪些库;requirements.txt 是"快照"------这个环境今天精确装了哪几个版本。 前者给打包和别人用你的库看,后者给部署和复现环境用。两者不冲突,而是分工。更细的用法(怎么用 pyproject 统一管理依赖、和 lockfile 怎么取舍)留到 Day61 与 Day62 再深入。
六、从今天起的工作习惯
学完今天,小嘉希望你以后每开一个新项目,都按下面这个顺序来------把它当成肌肉记忆:
- 先建项目文件夹,进去第一件事就是建 venv :
py -3 -m venv .venv。 - 装包前先激活 (或直接用
.\.venv\Scripts\python.exe -m pip ...),确认提示符前有(.venv)。 .venv/加进.gitignore,这个文件夹不进版本库。- 项目跑通、装好了依赖,就
pip freeze > requirements.txt,把这份清单提交进 git,跟着代码一起走。 - 换机器或重装时 :建空 venv →
pip install -r requirements.txt,环境立刻复原。
这样坚持下来,你就再也不会经历"项目 A 把项目 B 的包搞坏了"或者"我电脑上明明是好的"这类噩梦了。
小嘉预告:今天讲的是入门够用版 的环境与依赖管理。等你学到后面真实项目,会遇到"想自动锁依赖树""想区分开发用库和生产用库"这类需求------那就是 Day61「虚拟环境与 pip 依赖管理」 的进阶内容(pip-tools、lockfile 对比、多环境拆分);而 Day62「现代包管理器」 会带你认识 uv、poetry 这些比 pip 更快、更省心的新工具。今天先把 venv + pip + requirements.txt 这一套练熟就好。
练习题
- 动手题:在任意一个新文件夹里执行
py -3 -m venv .venv,激活后敲python -c "import sys; print(sys.executable)",看看打印出来的路径是不是指向了你刚建的.venv\Scripts\python.exe;然后deactivate再看一次,解释两次结果为什么不同。 - 动手题:激活 venv 后执行
python -m pip install six,再pip list;接着deactivate退出,在全局环境再pip list,对比两个列表里 six 的"可见性",说说为什么退出后 venv 里的包就"不见了"。 - 动手题:激活 venv,
pip install six pycodestyle,然后pip freeze > requirements.txt,打开这个文件看看里面写了哪几行;再新建第二个 venv,执行pip install -r requirements.txt,验证两边版本号完全一致。 - 思考题:为什么
.venv文件夹要加进.gitignore?如果直接把整个.venv拷给另一台电脑,大概率会出什么问题?(提示:想想pyvenv.cfg里写死的home路径。) - 思考题:
pip show six输出里的Location这一行,在激活 venv 和不激活两种情况下,分别会指向哪里?它能帮你判断什么? - 对比题:用自己的话说一说
pyproject.toml(声明)和requirements.txt(快照)分工有什么不同?为什么一个项目常常两个文件都要有?
小结
- 为什么需要虚拟环境:全局环境会污染、版本冲突、不可复现;venv 给每个项目一个独立小仓库,本机实测全新 venv 只有 pip、全局却有九十个包,对比鲜明。
- venv 是标准库自带模块(Python 3.3+),创建
py -3 -m venv .venv,激活后提示符出现(.venv),deactivate退出;.venv整个目录加进.gitignore。 - 隔离开关是
pyvenv.cfg里的include-system-site-packages = false;sys.executable和pip show的Location是验证隔离是否成立的两大利器。 - pip 四大基础命令:
install(装)、uninstall(卸)、list(列)、show(查详情);-i临时换国内镜像、-U升级。 - requirements.txt:
pip freeze导出当前环境快照,pip install -r在新环境一键复现;今天用 six + pycodestyle 实测了"导出→在第二个干净 venv 复现"全流程,版本号完全一致。 - pyproject.toml(PEP 621)是现代项目配置中心,与 requirements.txt 分工:一个声明、一个快照;进阶的 pip-tools / lockfile / 多环境拆分留给 Day61,现代工具留给 Day62。
扩展阅读
- 官方 venv 文档:https://docs.python.org/3/library/venv.html
- 官方 pip 用户指南:https://pip.pypa.io/en/stable/
- PEP 621(pyproject.toml 元数据规范):https://peps.python.org/pep-0621/
- PyPI 官方包仓库(可搜索包):https://pypi.org/
- 清华 PyPI 镜像使用帮助:https://mirrors.tuna.tsinghua.edu.cn/help/pypi/