×

注意!页面内容来自https://www.zhihu.com/tardis/bd/art/30454417840,本站不储存任何内容,为了更好的阅读体验进行在线解析,若有广告出现,请及时反馈。若您觉得侵犯了您的利益,请通知我们进行删除,然后访问 原网页

2 赞同
8 收藏

一、__init__.py 的核心作用

1. 定义包的“身份”

  • 历史背景:在 Python 3.3 之前,没有 __init__.py 的目录会被视为普通文件夹,无法通过 import 导入内部的模块。例如:
     my_project/
       ├── utils/          # 没有 __init__.py
           └── helpers.py
       └── main.py
  • 解决方案:添加 utils/__init__.py(即使是空文件),Python 就会将 utils 视为包。

2. 初始化包的代码

  • 自动执行:当包或子包被导入时,__init__.py 中的代码会自动运行一次。例如:
     # my_package/__init__.py
     print("正在初始化 my_package...")

当用户执行 import my_package 时,会看到此输出。

  • 典型用途
    • 加载包依赖的配置文件。
    • 初始化数据库连接或全局对象。
    • 预加载资源(如图片、模型)。

3. 定义包的公共接口

  • 简化导入路径:通过在 __init__.py 中导入子模块或函数,用户可以直接访问它们,无需关心内部路径。
     # my_package/__init__.py
     from .submodule1 import ClassA
     from .submodule2 import function_b

二、你可以在 __init__.py 中做什么?

1. 导入子模块(简化用户使用)

   # my_package/__init__.py
   from . import submodule1  # 导入整个子模块
   from .submodule2 import func1, func2  # 导入具体函数/类
  • 用户可以直接 import my_package.submodule1,或通过 my_package.func1() 调用。

2. 定义包级变量和常量

   # my_package/__init__.py
   VERSION = "1.0.0"
   AUTHOR = "Your Name"
  • 用户可以通过 my_package.VERSION 访问这些变量。

3. 控制导出内容(__all__ 变量)

   # my_package/__init__.py
   __all__ = ["ClassA", "func1"]  # 定义使用 `from my_package import *` 时的导出内容
   from .submodule1 import ClassA
   from .submodule2 import func1
  • 用户执行 from my_package import * 时,只会导入 ClassAfunc1

4. 处理循环依赖

  • 如果子模块之间需要互相引用,可以在 __init__.py 中延迟导入:
     # my_package/__init__.py
     def get_class_a():
         from .submodule1 import ClassA  # 延迟导入
         return ClassA()

5. 注册插件或扩展

   # my_package/__init__.py
   PLUGINS = []

   def register_plugin(plugin):
       PLUGINS.append(plugin)
  • 其他模块可以通过 my_package.register_plugin(...)动态注册功能。

6. 配置日志或警告

   # my_package/__init__.py
   import logging
   logging.getLogger(__name__).addHandler(logging.NullHandler())  # 配置包级日志

三、实际场景示例

场景 1:构建一个数学工具包

# math_tools/__init__.py
from .geometry import calculate_area, calculate_volume  # 导入几何模块的函数
from .algebra import solve_equation  # 导入代数模块的函数
__all__ = ["calculate_area", "solve_equation"]  # 仅导出部分功能

用户可以直接:

from math_tools import calculate_area
print(calculate_area("circle", radius=5))

场景 2:初始化数据库连接

# my_database/__init__.py
import sqlite3
from pathlib import Path

DB_PATH = Path(__file__).parent / "database.db"
connection = sqlite3.connect(DB_PATH)  # 包被导入时自动连接数据库

def close_connection():
    connection.close()

用户使用:

import my_database
cursor = my_database.connection.cursor()
# ... 操作数据库 ...
my_database.close_connection()  # 手动关闭

四、Python 3.3+ 的隐式命名空间包

  • 何时使用:当你想将多个分散的目录合并为一个逻辑包(例如跨多个代码仓库)。
  • 示例
  # 目录结构
  project1/
    └── my_package/
        └── module1.py
  project2/
    └── my_package/
        └── module2.py

如果两个 my_package 目录都没有 __init__.py,Python 会将它们视为同一个命名空间包,用户可以通过 import my_package.module1import my_package.module2 访问。

  • 限制
  • 不支持 __all__ 控制导出。
  • 无法在包级别初始化代码(因为没有 __init__.py)。

那么我们什么时候需要 __init__.py

场景是否需要 __init__.py说明
传统包(单一目录)推荐明确包结构,支持所有功能
隐式命名空间包(跨多个目录)不需要用于特殊场景(如跨项目共享包名)
简单脚本目录不需要若不需要包结构,可直接导入模块

编辑于 2025-03-16 · 著作权归作者所有