Python 项目中跨模块导入的正确姿势

在开发稍具规模的 Python 项目时,你是否也遇到过这样的困扰:
“为什么我在 A 模块里 import B 模块会报错?明明文件就在那儿啊!”

这其实不是 Python 的锅,而是模块路径管理的问题。尤其当你的项目开始分层、拆目录,或者需要从不同位置运行脚本时,导入问题就会频繁冒头。

别担心——本文将为你梳理三种经过验证的解决方案,并告诉你:什么时候该用哪一种


项目结构(标准布局)

my_project/
├── pyproject.toml
├── main.py
└── my_project/                # 主包目录
    ├── __init__.py
    ├── core/config.py
    ├── services/data_service.py
    └── tools/analyzers/report_generator.py

✅ 按场景选方案

场景方案特点
快速原型 / 脚本动态注入路径零配置,直接跑
正式项目(推荐)可编辑安装 (pip install -e .)规范、IDE 友好、团队协作佳
包内调试python -m 运行无需改代码,适合已打包项目

方案一:动态注入路径(脚本/原型)

在入口 .py 文件开头加:

import sys
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parents[N]  # N=当前文件到根目录的层级数
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

例如 report_generator.py 在第3层,用 parents[3]

✅ 优点:零配置、独立运行
⚠️ 缺点:重复代码、IDE可能报错、不适合长期维护


方案二:可编辑安装(正式项目首选 pip install -e .

  1. pyproject.toml
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "my_project"
version = "0.1.0"

[tool.setuptools.packages.find]
where = ["."]
include = ["my_project*"]
  1. 安装
pip install -e .
# 或 uv pip install -e .
  1. 任意位置直接导入
from my_project.core.config import load_config

✅ 优点:规范、IDE支持好、依赖管理清晰、适合团队和CI/CD


方案三:python -m 运行(包内调试,通常用于 相对导入

在项目根目录执行:

python -m my_project.tools.analyzers.report_generator

可在模块中使用相对导入:

from ...core.config import load_config

⚠️ 注意:不能直接 python report_generator.py,必须在根目录运行


最后一点思考

Python 并没有“不让”你导入自己的模块,它只是把选择权交给了你:

  • 对于小任务,允许你用几行代码灵活应对;
  • 对于大项目,则提供了标准化的包管理机制。

关键不在于“哪种方法更高级”,而在于根据项目阶段选择合适的策略,并保持风格一致

毕竟,我们写代码不只是为了让程序跑起来,更是为了让别人(包括未来的自己)能轻松看懂、修改和协作。

清晰 > 聪明,可靠 > 巧妙。这才是工程之道😉。