# Python 模块与包：import、pip、venv 与库，通俗讲解

URL: https://skillaria.top/zh/guides/python/moduli-python

模块是 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 实际上做了什么

1. 在缓存 `sys.modules` 中查找模块。如果已经加载，直接返回。
2. 如果没有加载，就按 `sys.path` 中的目录查找文件：运行脚本所在的文件夹、标准库、虚拟环境的 site-packages。
3. 从上到下完整执行找到的文件。
4. 把模块对象放入缓存，并绑定到名字上。

由此得出两个实用结论：再次导入同一模块几乎不耗时——代码不会第二次执行；而模块顶层的所有代码，包括 `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 虚拟环境中——这样项目之间互不干扰，环境也容易复现。课程中这一主题同样结合实践讲解：支出记录器被整理成一个带依赖的包，并借助惰性导入加快启动速度。

## FAQ

**每个项目都需要单独创建 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 文件夹复制到另一台电脑上？**

不能。虚拟环境与路径和本机的解释器绑定。应该复制依赖列表，然后在新机器上重新创建环境。


---
Source: Skillaria.Top (https://skillaria.top)
