Python 模块与包:import、pip、venv 与库,通俗讲解
模块是 Python 中的单个 .py 文件,包是包含模块和 __init__.py 的文件夹,库则是为某项任务准备的一组包。第三方库通过 pip(或 uv)安装到虚拟环境 venv 中——这是一个独立的文件夹,包含解释器和该项目的包,以免不同项目的版本互相冲突。
Python 中的模块就是普通的代码文件,import 则是从模块中取出函数和类的方式。模块组成包,包组成库;为了让不同项目的第三方库互不干扰,应把它们安装到虚拟环境 venv 中。下面梳理整个链条:导入时发生了什么,包的结构如何,pip 和 venv 如何工作,以及 Python 3.15 中新出现的惰性导入。
模块、包、库:有什么区别
| 概念 | 是什么 | 示例 |
|---|---|---|
| 模块 | 一个 .py 文件;模块名就是去掉扩展名的文件名 | categories.py → import categories |
| 包 | 包含模块和 __init__.py 文件的文件夹 | expenses/report.py → from expenses.report import build |
| 库 | 为某项任务准备好的一组模块和包 | requests、pandas、标准库中的 pathlib |
| 发行包(PyPI 上的包) | pip 安装的压缩包 | pip install requests |
在日常交流中,“库”“包”和“模块”经常被混用,这很正常。关键是要明白:从技术上讲,import 总是加载一个模块或包,而 pip 安装的是 PyPI 上的发行包,发行包内部含有各种包。
import 在 Python 中如何工作
导入有几种形式,实际代码中都会遇到:
import math # 整个模块,通过 math.sqrt 访问
from math import sqrt, pi # 指定的名字
import datetime as dt # 别名
from collections import Counter as C
print(math.sqrt(16), sqrt(9), pi)
print(dt.date.today())
对于大型库,常用别名:import numpy as np、import pandas as pd。不建议使用 from module import *:它会污染命名空间,并且看不出名字来自哪里。
import 实际上做了什么
- 在缓存
sys.modules中查找模块。如果已经加载,直接返回。 - 如果没有加载,就按
sys.path中的目录查找文件:运行脚本所在的文件夹、标准库、虚拟环境的 site-packages。 - 从上到下完整执行找到的文件。
- 把模块对象放入缓存,并绑定到名字上。
由此得出两个实用结论:再次导入同一模块几乎不耗时——代码不会第二次执行;而模块顶层的所有代码,包括 print 和耗时的计算,都会在导入时执行。
import sys
for p in sys.path:
print(p)
print("math" in sys.modules)
if __name__ == "__main__"
既然导入会执行文件,就需要一种方法区分“我被当作脚本运行”和“我被导入了”。为此可以使用变量 __name__:运行文件时它等于 "__main__",导入时则等于模块名。
# categories.py
def guess(title: str) -> str:
return "餐饮" if "咖啡" in title.lower() else "其他"
if __name__ == "__main__":
print(guess("家附近的咖啡馆")) # 只有在执行 python categories.py 时才运行
对于包,还可以在包内放一个 __main__.py 文件,然后通过 python -m expenses 运行。
Python 标准模块
Python 自带“电池”:数百个内置模块无需安装即可使用。最常用的有:
| 模块 | 用途 |
|---|---|
os、sys、pathlib、shutil | 系统、路径、文件 |
json、csv、pickle | 数据格式 |
datetime、time、calendar | 日期与时间 |
math、random、statistics、fractions | 数字 |
re、string | 文本处理 |
collections、itertools、functools | 数据结构与迭代器 |
logging、argparse、subprocess | 日志、命令行参数、运行程序 |
asyncio、threading | 异步与线程 |
标准库的全部模块名保存在 sys.stdlib_module_names 中(Python 3.10+)。查看模块的内容可以使用 dir(math) 和 help(math)。
自己的包:项目结构
当模块变多时,把它们组织成包。常见的布局是 src-layout:
expenses-project/
├── pyproject.toml
└── src/
└── expenses/
├── __init__.py
├── cli.py
├── parsing.py
├── categories.py
└── report.py
包内的模块可以通过包名互相导入(from expenses.categories import guess),也可以使用相对导入(from .categories import guess)。相对导入只在包内有效,对直接作为脚本运行的文件无效。
pip:安装 Python 库
第三方库存放在 PyPI 目录中,由随 Python 一起提供的 pip 工具安装。最可靠的方法是通过解释器调用 pip——这样库一定会安装到运行代码所用的那个 Python 中。
python -m pip install requests # 安装
python -m pip install "pandas>=2.3" # 指定版本范围
python -m pip install --upgrade requests
python -m pip uninstall requests # 卸载
python -m pip list # 已安装包列表
python -m pip show requests # 版本和依赖
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt
在 Windows 上,常用启动器 py 代替 python。如果找不到 pip,可以用内置模块安装它:python -m ensurepip --upgrade。
常用的 Python 库
PyPI 上的第三方包有数十万个,但大多数项目用到的只是下面这一小部分。从它们开始,而不是去找冷门的工具。
| 任务 | 库 |
|---|---|
| HTTP 请求与 API | requests、httpx |
| 数据分析与表格 | pandas、numpy |
| 图表 | matplotlib |
| Web 服务 | fastapi、django、flask |
| 测试 | pytest |
| 图像 | pillow(导入时名为 PIL) |
注意:安装时的名字和导入时的名字并不总是相同。安装 pillow 时使用 pip install pillow,导入时写 from PIL import Image;安装 opencv-python,导入的是 cv2。如果安装后 import 立即失败,请查阅该包的文档,确认导入名。
Linux 上安装库
在新版 Debian 和 Ubuntu 中,向系统 Python 执行 pip install 会以 externally-managed-environment 错误结束(PEP 668)。这是一种保护:系统 Python 属于操作系统的包管理器,pip 不应破坏它。正确的解决办法不是 --break-system-packages,而是使用虚拟环境。
venv:Python 虚拟环境
虚拟环境是一个独立的文件夹,里面有自己的解释器(指向原解释器的链接)和独立的 site-packages。在其中安装的库只对该项目可见。因此,一个项目可以使用 Django 4,另一个项目可以使用 Django 5,二者互不冲突。
模块 venv 是 Python 自带的,无需单独安装(在 Debian/Ubuntu 上有时需要系统包 python3-venv)。
# 在 .venv 文件夹中创建虚拟环境
python -m venv .venv
# 激活:Linux 和 macOS
source .venv/bin/activate
# 激活:Windows PowerShell
.venv\Scripts\Activate.ps1
# 现在 pip 会把包安装到 .venv 中
python -m pip install requests
python -c "import sys; print(sys.prefix)"
# 退出虚拟环境
deactivate
激活后,终端行首会出现 (.venv)。虚拟环境文件夹不应提交到 git(把 .venv/ 加入 .gitignore),也不应在不同机器之间复制——应该保存依赖列表,并在需要时重新创建环境。如果 PowerShell 阻止运行激活脚本,可以为当前用户允许执行:Set-ExecutionPolicy -Scope CurrentUser RemoteSigned。
uv 与 pyproject.toml
现代项目将依赖写在 pyproject.toml 中。课程中使用的 uv 工具把创建环境和安装合并在一起:uv add pandas 会下载包,把它写入 pyproject.toml,并把精确版本固定在 uv.lock 中。同事克隆项目后执行 uv sync,就能得到完全一致的环境。
[project]
name = "expenses"
version = "0.1.0"
requires-python = ">=3.15"
dependencies = [
"pandas>=2.3",
]
[project.scripts]
expenses = "expenses.cli:main"
Python 3.15 中的惰性导入(PEP 810)
本主题在 3.15 中的主要新功能是关键字 lazy。大型库的加载很慢:课程的测试中,在 python:3.15-slim 镜像上,普通的 import pandas 耗时约 415 毫秒,而 lazy import pandas 只需约 13 毫秒。对于那些只为了显示 --help 而运行的命令行工具,差别非常大。
lazy import pandas as pd
lazy from expenses.report import build_report
def main() -> None:
...
df = pd.read_csv(path) # 这里才真正加载 pandas
此时名字绑定的是一个代理对象(types.LazyImportType),真正的导入发生在第一次使用该名字时。如果一直没有用到,就完全不会耗费时间。
惰性导入的限制
- 只能用于模块的顶层:放在函数、类或
try/except中会产生SyntaxError。 - 不允许
lazy from x import *。 ModuleNotFoundError不会在启动时出现,而是在第一次使用时才抛出。- 模块的副作用(例如注册插件)会推迟到之后执行。
要让所有导入都变成惰性,可以使用参数 -X lazy_imports=all 或环境变量 PYTHON_LAZY_IMPORTS=all;例外情况通过 sys.set_lazy_imports_filter() 设置。对于还需要兼容旧版本的代码,可以使用模块变量 __lazy_modules__ = ["pandas"]:3.15 会把列出的导入变成惰性,而旧版本只会执行普通的 import。想知道启动时间花在了哪里,可以运行 python -X importtime -c "import pandas"。收益主要体现在频繁的短时启动程序上;对长期运行的服务器,惰性导入几乎没有帮助。
常见错误
ModuleNotFoundError: No module named 'requests'— 库安装在了另一个 Python 中,或者没有激活虚拟环境。用同一个解释器执行python -m pip show requests检查。- 自己的文件被命名为
json.py、random.py或csv.py— 脚本所在的文件夹位于sys.path的最前面,它会覆盖标准模块。Python 3.13+ 的错误信息会提示“consider renaming”。请重命名文件,并删除__pycache__。 - 循环导入:
a.py导入b.py,而b.py又导入a.py。把公共部分移到第三个模块中,或者在函数内部导入。 - 在 Python 解释器中输入
pip(>>> pip install)会得到SyntaxError。pip 命令应该在终端中执行。
小结
模块是文件,包是模块的文件夹,库是它们的成套集合;import 先在缓存和 sys.path 中查找模块,并且只执行一次。第三方库应当只通过 pip 或 uv 安装到 venv 虚拟环境中——这样项目之间互不干扰,环境也容易复现。课程中这一主题同样结合实践讲解:支出记录器被整理成一个带依赖的包,并借助惰性导入加快启动速度。
常见问题
每个项目都需要单独创建 venv 吗?
是的,这是良好的习惯:每个项目可能需要不同版本的库,独立的环境可以避免冲突,也不会弄乱系统 Python。
pip 和 uv 有什么区别?
pip 只负责安装包;uv 还能创建环境、把依赖写入 pyproject.toml,并把精确版本固定在锁文件中,同时速度明显更快。
如何查看已安装的 Python 库列表?
在目标环境中执行 python -m pip list。pip freeze 会以 requirements.txt 的格式输出同样的列表。
已安装的模块保存在哪里?
保存在当前环境的 site-packages 文件夹中。要查看具体模块的路径,可以使用 __file__ 属性,例如 import requests; print(requests.__file__)。
能否把 venv 文件夹复制到另一台电脑上?
不能。虚拟环境与路径和本机的解释器绑定。应该复制依赖列表,然后在新机器上重新创建环境。