Python笔记之项目依赖管理与模块运行机制详解:从 requirements.txt 到 python -m 和包入口
Python笔记之项目依赖管理与模块运行机制详解:从 requirements.txt 到 python -m 和包入口

code review!
文章目录
1.1 requirements.txt 是什么
在 Python 项目中,requirements.txt 是一个常见的依赖声明文件,用来记录项目需要安装的第三方 Python 包。
它的核心作用是:让不同环境安装一致的依赖,例如本地电脑、同事电脑、测试服务器、生产服务器等。
常见内容如下:
flask==3.0.2
requests>=2.31.0
numpy<=1.26.4
pandas
1.2 常见版本写法
flask==3.0.2
表示精确锁定版本,推荐在生产环境中使用。
requests>=2.31.0
表示安装大于或等于指定版本的包。
numpy<=1.26.4
表示安装小于或等于指定版本的包。
pandas
表示安装最新可用版本。
生产环境中不推荐不写版本号,因为未来包更新后可能导致项目行为变化。
1.3 生成 requirements.txt
在当前 Python 环境中导出已安装的包:
pip freeze > requirements.txt
更推荐写法:
python -m pip freeze > requirements.txt
需要注意,pip freeze 会导出当前环境中的所有已安装包。如果没有使用虚拟环境,它可能会把很多与当前项目无关的包也导出。
1.4 根据 requirements.txt 安装依赖
在新环境中安装依赖:
pip install -r requirements.txt
更推荐写法:
python -m pip install -r requirements.txt
pip 会读取 requirements.txt 中的每一行,并安装对应的包。
1.5 使用虚拟环境管理依赖
在正式项目中,强烈建议先创建并激活虚拟环境,再安装依赖。
创建虚拟环境:
python -m venv venv
Windows 激活虚拟环境:
venv\Scripts\activate
macOS / Linux 激活虚拟环境:
source venv/bin/activate
激活后再安装依赖:
python -m pip install flask requests
然后导出:
python -m pip freeze > requirements.txt
这样生成的 requirements.txt 会更干净,只包含当前虚拟环境里的依赖。
1.6 pip freeze 的局限
pip freeze 会把直接依赖和间接依赖全部导出。
例如你只安装了 flask,但 Flask 依赖的 Werkzeug、Jinja2、click 等包也会被一起写进 requirements.txt。
这有好处,也有缺点。
好处是环境可复现性强。
缺点是文件会变长,并且不容易看出哪些包是项目主动依赖的。
1.7 其他依赖管理工具
如果想只根据项目代码中的 import 语句生成依赖文件,可以了解 pipreqs:
pipreqs . --force
如果项目更复杂,也可以使用现代依赖管理工具,例如 Poetry、Pipenv 等。
它们通常使用 pyproject.toml 或专门的锁文件来管理依赖,比传统 requirements.txt 更适合大型项目。
2. pip install 与 python -m pip install
2.1 二者的共同点
在很多简单场景下:
pip install requests
和:
python -m pip install requests
都可以安装第三方包。
但它们定位 Python 环境的方式不同。
2.2 pip install 的执行方式
当执行:
pip install requests
系统会在环境变量 PATH 中查找名为 pip 的可执行文件。
如果电脑上只有一个 Python,并且环境配置正确,通常没有问题。
但如果存在多个 Python 版本、多个虚拟环境,或者系统 Python 与项目 Python 并存,就可能调用到错误的 pip。
例如电脑里同时有 Python 3.10 和 Python 3.12:
pip install requests
这个命令不一定会把 requests 安装到你实际运行项目的那个 Python 环境里。
2.3 python -m pip install 的执行方式
当执行:
python -m pip install requests
含义是:使用当前这个 python 解释器,运行它环境中的 pip 模块,并安装包。
也就是说,包会安装到当前 python 对应的环境中。
如果你明确使用 Python 3.12,可以写:
python3.12 -m pip install requests
这样可以更明确地把包安装到 Python 3.12 对应的环境中。
2.4 为什么推荐 python -m pip
推荐使用:
python -m pip install 包名
主要原因有三个。
第一,可以避免多个 Python 环境导致的安装错位。
第二,适合写进文档和自动化脚本,可读性和确定性更高。
第三,在某些系统上更新 pip 自身时更稳妥。
例如更新 pip:
python -m pip install --upgrade pip
比直接执行下面命令更推荐:
pip install --upgrade pip
2.5 Linux/macOS 上避免滥用 sudo pip
不推荐这样安装包:
sudo pip install requests
这样可能会污染系统 Python 环境,影响系统工具或其他项目。
更安全的做法是使用虚拟环境:
python -m venv venv
source venv/bin/activate
python -m pip install requests
如果确实要安装到当前用户目录,可以使用:
python -m pip install --user requests
2.6 总结建议
| 命令 | 推荐程度 | 说明 |
|---|---|---|
pip install 包名 |
一般推荐 | 在已经明确激活虚拟环境时可以使用 |
python -m pip install 包名 |
更推荐 | 更能确保包安装到当前 Python 对应的环境中 |
python3.12 -m pip install 包名 |
多版本场景推荐 | 明确指定 Python 版本 |
sudo pip install 包名 |
不推荐 | 容易污染系统 Python 环境 |
当遇到明明安装了包,但运行代码仍然报错:
ModuleNotFoundError
可以优先检查是否安装到了错误的 Python 环境中,并尝试使用:
python -m pip install 包名
3. python -m 的含义和用法
3.1 python -m 是什么
python -m 的作用是:把一个模块或包当作脚本运行。
基本语法是:
python -m 模块名
或者:
python -m 包名
这里的 -m 可以理解为 module,表示后面跟的是模块名或包名,而不是普通的文件路径。
3.2 python -m 如何查找模块
执行:
python -m pip
时,Python 会在当前解释器的模块搜索路径中查找 pip 模块。
这个搜索路径通常包括:
- 当前工作目录
- Python 标准库
- 当前环境中安装的第三方包
- 其他
sys.path中的路径
找到对应模块后,Python 会以脚本方式运行它。
3.3 常见使用场景
3.3.1 使用当前 Python 环境安装包
python -m pip install requests
这表示使用当前 Python 解释器对应的 pip 安装 requests。
3.3.2 创建虚拟环境
python -m venv venv
这表示运行 Python 标准库中的 venv 模块,在当前目录创建一个名为 venv 的虚拟环境。
3.3.3 启动临时 HTTP 文件服务器
python -m http.server 8000
这会在当前目录启动一个简单的 HTTP 服务。
访问地址通常是:
http://localhost:8000
3.3.4 运行代码格式化工具
如果安装了 black,可以这样运行:
python -m black myscript.py
3.4 只输入 python -m 会怎样
如果只执行:
python -m
而不提供模块名,Python 会报错,因为 -m 后面必须跟模块或包名。
正确格式是:
python -m 模块名
例如:
python -m pip --version
4. Python 脚本入口:if __name__ == '__main__'
4.1 它的作用
if __name__ == '__main__': 是 Python 中常见的程序入口判断。
它的作用是:让一个 Python 文件既可以被直接运行,也可以被其他文件导入,并且在导入时不会执行某些只应该在直接运行时执行的代码。
4.2 __name__ 是什么
每个 Python 文件在运行时,解释器都会自动提供一个变量:
__name__
它的值取决于当前文件的运行方式。
如果文件被直接运行:
python calculator.py
那么该文件中的:
__name__
值是:
'__main__'
如果文件被其他文件导入:
import calculator
那么 calculator.py 中的:
__name__
值通常是:
'calculator'
也就是模块名。
4.3 不使用入口判断的问题
假设有一个文件 calculator.py:
def add(a, b):
return a + b
print("正在测试 add 函数...")
print(add(2, 3))
直接运行它:
python calculator.py
输出测试信息是正常的。
但是如果另一个文件 main.py 导入它:
import calculator
print(calculator.add(5, 5))
运行:
python main.py
会发现 calculator.py 中的测试代码也被执行了。
原因是:import calculator 会执行 calculator.py 中的顶层代码。
4.4 使用入口判断解决问题
可以改成:
def add(a, b):
return a + b
if __name__ == '__main__':
print("正在测试 add 函数...")
print(add(2, 3))
这样:
直接运行 calculator.py 时:
python calculator.py
__name__ 等于 '__main__',测试代码会执行。
被其他文件导入时:
import calculator
__name__ 等于 'calculator',测试代码不会执行。
4.5 推荐写法:封装 main() 函数
更推荐把直接运行时要执行的逻辑放进 main() 函数中:
def my_awesome_function():
print("核心逻辑")
def main():
print("程序开始运行...")
my_awesome_function()
if __name__ == '__main__':
main()
这种结构更清晰,也方便后续维护、测试和复用。
5. Python 包中的 __init__.py 和 __main__.py
5.1 二者的核心区别
__init__.py 和 __main__.py 都是 Python 包中具有特殊含义的文件,但作用不同。
简单来说:
__init__.py 负责包的初始化
__main__.py 负责包被直接运行时的入口
5.2 __init__.py 的作用
__init__.py 通常放在包目录下。
例如:
my_game/
├── __init__.py
├── core.py
└── utils.py
它的主要作用包括:
- 标识这个目录是一个 Python 包
- 在包第一次被导入时执行初始化代码
- 简化外部导入路径
- 控制
from package import *的导出内容
在现代 Python 中,即使没有 __init__.py,某些目录也可以作为命名空间包被导入。
但在普通项目中,仍然建议为常规包添加 __init__.py,这样结构更清晰,也更兼容。
示例:
# my_game/__init__.py
__version__ = "1.0.0"
from .core import start_game
这样外部可以写:
from my_game import start_game
而不必写:
from my_game.core import start_game
5.3 __main__.py 的作用
__main__.py 用来让一个包可以被直接运行。
如果包结构如下:
my_game/
├── __init__.py
├── __main__.py
├── core.py
└── utils.py
当执行:
python -m my_game
Python 会查找并执行:
my_game/__main__.py
因此,__main__.py 通常放启动逻辑,例如命令行入口、服务启动入口等。
示例:
# my_game/__main__.py
from .core import start_game
def main():
print("游戏正在通过命令行启动...")
start_game()
if __name__ == '__main__':
main()
对应的核心逻辑文件:
# my_game/core.py
def start_game():
print("游戏开始")
5.4 __init__.py 和 __main__.py 的触发时机
| 文件 | 作用 | 触发时机 |
|---|---|---|
__init__.py |
初始化包 | 当包被 import 时执行 |
__main__.py |
包的运行入口 | 当执行 python -m 包名 时执行 |
示例:
import my_game
会触发:
my_game/__init__.py
执行:
python -m my_game
会触发:
my_game/__main__.py
需要注意的是,执行 python -m my_game 时,Python 通常也会先加载包,因此 __init__.py 中的代码也可能先执行,然后再执行 __main__.py。
所以不建议在 __init__.py 中放复杂逻辑或有副作用的代码。
6. 相对导入:from .core import start_game
6.1 .core 中的点是什么意思
在:
from .core import start_game
中,.core 表示相对导入。
这里的点 . 表示当前包。
所以:
from .core import start_game
意思是:从当前包中的 core.py 文件里导入 start_game。
如果项目结构是:
my_game/
├── __init__.py
├── __main__.py
└── core.py
那么在 my_game/__main__.py 中:
from .core import start_game
表示从:
my_game/core.py
导入:
start_game
6.2 为什么不直接写 from core import start_game
不建议在包内部写:
from core import start_game
因为这是绝对导入。
Python 会在模块搜索路径中查找名为 core 的模块,而不一定是当前包里的 core.py。
如果环境中刚好存在其他名为 core 的模块,就可能导入错误。
使用相对导入:
from .core import start_game
可以明确表示:从当前包内部导入。
6.3 一个点和两个点的区别
一个点:
from .core import start_game
表示当前包。
两个点:
from ..common import helper
表示上一级包。
三个点:
from ...utils import tool
表示上上一级包。
相对导入通常用于包内部模块之间的引用。
7. 如何运行包:python -m my_game
7.1 正确启动方式
如果项目结构如下:
project/
└── my_game/
├── __init__.py
├── __main__.py
├── core.py
└── utils.py
应该在 project 目录下执行:
python -m my_game
注意:不是进入 my_game 目录里面执行,而是在它的上一级目录执行。
7.2 python -m my_game 背后的过程
执行:
python -m my_game
时,大致过程如下:
- Python 在当前目录和
sys.path中查找名为my_game的包 - 找到
my_game目录 - 识别它是一个包
- 加载包
- 查找并执行
my_game/__main__.py
因此,如果想让一个包支持:
python -m 包名
包中需要有:
__main__.py
7.3 不推荐直接运行 __main__.py
不推荐这样运行:
python my_game/__main__.py
也不推荐进入包目录后运行:
python __main__.py
原因是:这样 Python 会把 __main__.py 当成一个普通脚本执行,而不是作为 my_game 包的一部分执行。
此时,类似下面的相对导入可能会失败:
from .core import start_game
常见错误是:
ImportError: attempted relative import with no known parent package
因为相对导入依赖包上下文,而直接运行单个文件时,Python 不知道它属于哪个父包。
7.4 推荐项目结构
一个较规范的包结构可以写成:
project/
└── my_game/
├── __init__.py
├── __main__.py
├── core.py
└── utils.py
core.py:
def start_game():
print("游戏开始")
__main__.py:
from .core import start_game
def main():
print("游戏正在通过命令行启动...")
start_game()
if __name__ == '__main__':
main()
在 project 目录下运行:
python -m my_game
输出:
游戏正在通过命令行启动...
游戏开始
8. 整体关系总结
8.1 依赖安装层面
requirements.txt 负责记录项目依赖。
推荐用虚拟环境隔离项目依赖:
python -m venv venv
安装依赖推荐使用:
python -m pip install 包名
导出依赖:
python -m pip freeze > requirements.txt
安装依赖文件:
python -m pip install -r requirements.txt
8.2 命令执行层面
python -m 表示把模块或包作为脚本运行。
例如:
python -m pip install requests
python -m venv venv
python -m http.server 8000
python -m my_game
8.3 单文件脚本层面
如果一个文件既要能直接运行,又要能被导入复用,推荐写:
def main():
pass
if __name__ == '__main__':
main()
8.4 包结构层面
__init__.py 负责包初始化。
__main__.py 负责包作为程序运行时的入口。
相对导入:
from .core import start_game
表示从当前包内部的 core.py 导入 start_game。
运行整个包:
python -m my_game
而不是:
python my_game/__main__.py
这样可以保证包上下文正确,相对导入也能正常工作。
更多推荐



所有评论(0)