这篇文章用前端的 import/export 视角,带你搞清楚 Python 的模块系统——import 的各种写法、init.py 的作用、规范的项目结构,以及 requirements.txt 和 package.json 的对比。
前言
写了几十行代码还好,所有东西放一个文件里。
但随着项目变大,单文件就开始混乱了——函数越来越多,职责越来越杂,改一处可能影响全局。这时候就需要拆模块。
前端开发者对模块化一定不陌生:import React from 'react'、export default function、npm install……Python 有一套类似但不完全相同的模块系统。
最大的区别: 前端有打包工具(webpack/vite)帮你解决模块问题,Python 是原生的模块系统,需要你自己理解路径规则。
这篇文章覆盖:
- requirements.txt(对比 package.json)
一、什么是模块?
Python 里,每一个 .py 文件就是一个模块。模块名就是文件名(不含 .py)。
my_project/├── main.py├── utils.py ← 这就是一个模块,模块名叫 utils└── calculator.py ← 模块名叫 calculator
# calculator.pydef add(a, b): return a + bdef subtract(a, b): return a - bPI = 3.14159
# main.py(同一目录下)import calculatorresult = calculator.add(3, 5)print(result) # 8print(calculator.PI) # 3.14159
二、import 的各种写法
2.1 整体导入
import calculatorcalculator.add(3, 5) # 必须带模块名前缀calculator.subtract(10, 3)
2.2 导入特定内容(from...import)
from calculator import add, PIadd(3, 5) # 不需要前缀print(PI) # 直接用# 导入时重命名(解决命名冲突)from calculator import add as calc_add
2.3 导入全部内容(不推荐)
from calculator import * # 导入所有公开名称# ❌ 不推荐:不清楚哪些名称来自哪个模块,容易产生命名冲突
2.4 对比 JS ESM
// JS:导入整个模块import * as calculator from './calculator.js'calculator.add(3, 5)// JS:导入特定内容import { add, PI } from './calculator.js'// JS:重命名导入import { add as calcAdd } from './calculator.js'// JS:默认导出/导入export default function add(a, b) { return a + b }import add from './calculator.js'
# Python 没有"默认导出"的概念# 每个名称都是具名导出,用 from module import name 导入
关键差异:
| | |
|---|
| .py | .js |
| import module | import * as m from '...' |
| from module import name | import { name } from '...' |
| import name as alias | import { name as alias } |
| | export default ... |
| import utils | import './utils.js' |
三、模块搜索路径
当你写 import utils 时,Python 按以下顺序查找:
- 已安装的第三方包(site-packages 目录)
import sysprint(sys.path) # 查看完整的搜索路径列表
这就是为什么:
- import requests 需要先 pip install requests(第三方包)
- import utils 能成功是因为 utils.py 在当前目录(或包内)
四、包(Package):模块的目录
当模块越来越多,就需要用目录来组织。一个包含 __init__.py 文件的目录就是一个包(Package)。
my_project/├── main.py└── utils/ ← 这是一个包 ├── __init__.py ← 让 utils/ 变成包的关键文件 ├── string_utils.py ├── math_utils.py └── file_utils.py
# utils/math_utils.pydef add(a, b): return a + bdef factorial(n): return 1 if n <= 1 else n * factorial(n - 1)
# main.pyfrom utils.math_utils import add, factorialfrom utils import string_utils # 导入整个子模块result = add(3, 5)print(result)
__init__.py 的作用
__init__.py 文件有两个作用:
作用1:标识这个目录是一个 Python 包(Python 3.3+ 可以省略,但建议保留)
作用2:控制 from package import * 的行为,以及包级别的初始化
# utils/__init__.py# 1. 集中暴露包的公开接口(类似 JS 里的 index.js)from .math_utils import add, factorialfrom .string_utils import capitalize, truncate# 2. 包级别的配置VERSION = "1.0.0"# 3. 这样外部可以直接:# from utils import add (而不必知道 add 在哪个子模块里)
# 有了 __init__.py 的集中导出后from utils import add # 更简洁import utilsprint(utils.VERSION) # 1.0.0
类比 JS:__init__.py 就像目录下的 index.js,是包的入口文件。
五、相对导入 vs 绝对导入
# 绝对导入(从项目根目录算起,推荐)from utils.math_utils import addimport utils.string_utils# 相对导入(用 . 表示当前包,.. 表示上级包)from .math_utils import add # 同包内的其他模块from ..config import DATABASE_URL # 上级包中的模块
建议: 在包内部模块之间互相引用时,用相对导入;从外部使用包时,用绝对导入。
六、标准项目结构
小型项目(脚本/工具):
my_tool/├── main.py # 入口├── requirements.txt # 依赖├── README.md # 说明└── utils/ ├── __init__.py └── helpers.py
中型项目(Web 应用/API):
my_app/├── app/ # 主应用包│ ├── __init__.py│ ├── main.py # 应用入口(FastAPI/Flask app 对象)│ ├── models/ # 数据模型│ │ ├── __init__.py│ │ └── user.py│ ├── routers/ # 路由处理│ │ ├── __init__.py│ │ ├── users.py│ │ └── products.py│ └── utils/ # 工具函数│ ├── __init__.py│ └── auth.py├── tests/ # 测试目录│ ├── __init__.py│ ├── test_users.py│ └── test_products.py├── requirements.txt # 生产依赖├── requirements-dev.txt # 开发依赖├── .env # 环境变量(不提交 git)├── .gitignore└── README.md
对比 JS 前端项目:
my-frontend/├── src/│ ├── main.js ↔ app/main.py│ ├── components/ ↔ app/models/│ └── utils/ ↔ app/utils/├── tests/ ↔ tests/├── package.json ↔ requirements.txt└── .env ↔ .env(完全一样)
七、requirements.txt vs package.json
requirements.txt(Python 依赖管理):
# requirements.txtfastapi==0.100.0 # 精确版本(== 等同于 npm 的精确版本)uvicorn>=0.20.0 # 最低版本(>= 类比 npm 的 ^)requests~=2.31.0 # 兼容版本(~= 意为 >=2.31.0, <3.0.0)python-dotenv # 不指定版本(安装最新)# 注释用 # 号
// package.json(JS 依赖管理){ "dependencies": { "react": "^18.2.0", "axios": "~1.4.0" }}
命令对照表:
| | |
|---|
| pip install -r requirements.txt | npm install |
| pip install requests | npm install axios |
| pip uninstall requests | npm uninstall axios |
| pip list | npm list |
| pip freeze > requirements.txt | npm init |
| pip install --upgrade requests | npm update axios |
pip 没有的功能(相比 npm):
- 没有内置的 lock 文件(需要用 pip-tools 或 poetry 实现)
- 没有内置的 scripts 字段(但可以用 Makefile 或 taskipy 代替)
现代 Python 项目推荐用 poetry 或 uv 来管理依赖,它们类似 npm,有 lock 文件,依赖管理更可靠。
八、虚拟环境最佳实践
(这是第 02 篇的延伸,这里做总结)
# 1. 创建虚拟环境python -m venv venv# 2. 激活(macOS/Linux)source venv/bin/activate# 2. 激活(Windows PowerShell).\venv\Scripts\Activate.ps1# 3. 安装依赖pip install -r requirements.txt# 4. 退出虚拟环境deactivate
.gitignore 里一定要加上 venv/,不要把虚拟环境提交到 Git。
类比 JS:venv/ 对应 node_modules/,都不应该提交 Git,都通过依赖文件重新安装。
九、if __name__ == "__main__" 是什么?
你可能经常看到这行代码:
# calculator.pydef add(a, b): return a + bif __name__ == "__main__": # 只有直接运行这个文件时,才执行下面的代码 # 被 import 时不执行 print(add(3, 5)) print("这是测试代码")
解释:
- 直接运行 python calculator.py 时,__name__ 的值是 "__main__",条件成立,执行测试代码
- 被其他文件 import calculator 时,__name__ 的值是 "calculator",条件不成立,不执行
这个写法让一个文件既可以作为独立脚本运行,又可以作为模块被导入——类似 JS 里判断 process.argv 或 require.main === module。
小结
| | |
|---|
| | |
| import module | import x from './module' |
| | |
| __init__.py | index.js |
| requirements.txt | package.json |
| pip | npm |
| venv | node_modules |
3 个必记要点:
- __init__.py 让目录变成包,可以在里面集中暴露接口(类似 index.js)
- pip freeze > requirements.txt 导出当前环境的精确依赖版本
- 每个项目都应该用独立的虚拟环境,venv/ 不提交 Git
下篇预告
第 12 篇:Python 常用内置库精选:os、datetime、re 实用指南
不用安装任何第三方库,Python 标准库就能解决大量日常问题。下一篇挑选最实用的几个,每个都有具体场景和代码示例。