Python开发-import, 项目结构与 __init__.py

如果你正从编写简单的 Python 脚本转向结构化项目开发,你很可能在 Import 和目录结构上遇到如下困惑:

应该使用 src 目录结构还是 flat 扁平目录结构吗?
在 Python 3 中还需要 __init__.py 吗?

这篇文章就带大家让深入讨论下这个问题。

1. 绝对导入 vs. 相对导入

假设有如下的电商项目结构:

ecommerce/
├── __init__.py
├── database/
│   ├── __init__.py
│   └── queries.py
└── payments/
    ├── __init__.py
    ├── stripe.py
    └── paypal.py

绝对导入

绝对导入使用从项目根目录开始的完整路径

  • 例子:stripe.py 中使用 queries.py 中的 save_transaction 函数。
# ecommerce/payments/stripe.py
# 绝对导入
from ecommerce.database import queries

def process_payment():
    queries.save_transaction()

相对导入

相对导入使用点号(.)来表示基于 _ 当前 _ 文件的路径。

  • 语法: from . import module (当前目录) 或 from ..subfolder import module (父级目录)。
  • 例子:上述例子变为
# ecommerce/payments/stripe.py

# 相对导入
from ..database import queries

def process_payment():
    queries.save_transaction()

核心结论

维度 绝对导入 (推荐) 相对导入 (慎用)
可读性 极高。一眼就能看出代码来源。 一般。在复杂层级中容易数错点号。
重构 较难。修改根文件夹名需全局替换。 较易。移动整个子包时内部引用不破裂。
脚本执行 正常运行。 报错。直接运行 python stripe.py 会失败。

实践建议

  • 总是使用绝对导入(PEP 8 推荐), 如果你不清楚相对导入的机制,请不要轻易使用。
  • 仅在编写大型框架且子包内部关系极其紧密时,才考虑使用显式相对导入
  • 绝对禁止使用 Python 2 时代的隐式相对导入。

2. flat 结构 Vs src 结构

这是开发者从 " 写脚本 " 转向 " 写工程 " 的分水岭。

结构对比

  • flat 结构:

    my_project/
    ├── pyproject.toml
    └── my_package/     # 源码就在根目录
        └── module.py
    
    • 诱惑: 简单。打开终端直接输入 python 就能导入。
    • 风险: 测试污染。由于当前路径 . 默认在 sys.path 里,即使你打包时漏掉了一个重要文件,你的本地测试依然会通过(因为它直接读取了文件夹里的文件)。由于当前目录在 sys.path 中,你会误以为代码已安装。这会导致你漏掉某些文件没打包,但本地测试却能通过的窘境。
  • src 结构 :

    my_project/
    ├── pyproject.toml
    └── src/            # 源码被包裹在 src 中
        └── my_package/
            └── module.py
    
    • 优势: 强制安装。通过增加一个 src/ 文件夹,物理上隔绝了 " 当前路径 " 和 " 源码路径 "。这逼迫你执行 pip install -e .(可编辑安装),让 Python 像对待标准库一样对待你的代码。这确保了你的测试环境与用户安装后的生产环境完全一致。
    • 整洁: 根目录只放配置文件,逻辑代码全部在 src 下。

核心结论

维度 扁平结构 (Flat Layout) Src 结构 (Src Layout)
结构简图 root/my_pkg/ root/src/my_pkg/
导入便捷性 极高。在根目录打开终端即可导入。 较低。需先安装或手动设置 PYTHONPATH
测试准确性 存在隐患。可能误导入本地源码而非安装包。 极高。强制测试 " 已打包 " 的代码。
打包发布 容易意外包含无关文件(如 tests)。 天然隔离,打包过程更纯净、规范。
适用场景 小型个人练手项目、简单脚本。 专业库、大型应用、开源项目。

实践建议

  1. 如果你在写库 (Library): 务必使用 src 结构。这能确保用户通过 pip install 获得的代码与你测试的代码完全一致。
  2. 避免 " 影子导入 ": 使用 src 结构可以防止你的项目无意中覆盖掉标准库。例如,如果你在 Flat 结构下有个文件叫 code.py,它可能会干扰 Python 自带的 code 模块。
  3. 配置配套: 使用 src 结构时,在 pyproject.toml 中需要显式指定源码路径:
[tool.setuptools.packages.find]
where = ["src"]
  1. 测试分离: 建议将 tests/ 文件夹放在 src/ 之外。这样测试代码就不会被错误地打包进你的发布版本中。

3. __init__.py 还需要吗?

虽然 Python 3.3+ 引入了 " 命名空间包 ",允许文件夹不包含 __init__.py,但在实际工程中,你仍然应该保留它。

为什么保留?

  1. 明确标识: 告知 pytestmypy 等工具这是一个完整的 Python 包。
  2. 作为 " 门面 " (Facade): 你可以在 __init__.py 中定义快捷访问。

代码示例:

# src/mypackage/__init__.py
from .payments import process_payment  # 暴露核心接口

# 这样用户只需输入:
# from mypackage import process_payment
# 而不需要输入冗长的:
# from mypackage.internal.subfolder.payments import process_payment

总结

  1. 导入:绝对导入。明确比隐晦好。
  2. 布局:src 结构。它能帮你挡掉 90% 的打包与路径错误。
  3. init.py: 保留它。它是包的灵魂,也是定义 API 接口的最佳场所。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容