一文教你看懂__init__.py这个被大家轻视的文件

举报
^码匠er 发表于 2025/05/15 23:04:23 2025/05/15
【摘要】 在很多开源项目中,总能看到各式各样的 __init__.py 文件。它看起来很普通,但却关系到整个包结构的运作。本篇文章将 系统地介绍 __init__.py 的历史背景、功能用途以及各种实践场景和高级用法,帮助你真正读懂这个“魔法”文件。

大家好,我是码匠er。在开源工作中,你是否有过下面的经历?克隆了一个Python开源项目,满怀期待准备运行时,却收到了这样的报错:ModuleNotFoundError: No module named 'xxx'。这个问题就是因为我们今天的主角__init__.py文件造成的,今天我们就扒一扒这个文件的神秘面纱!

1. 揭开__init__.py 的神秘面纱

  1. **标识包身份:**当 Python 解释器遇到一个包含__init__.py的目录时,会自动将其识别为一个包(Package),而非普通文件夹。​
  2. **模块预处理:**当使用import package语句导入包时,Python 会优先执行__init__.py中的代码。我们可以在这里完成模块的预加载、全局变量定义等初始化操作。​
  3. **控制导出内容:**通过定义__all__变量,我们能精确控制from package import *语句导入的模块列表,避免暴露无关接口。

2. 历史变迁小剧场

  • 在 Python 3.3 之前,必须在包目录下创建这个文件(可以是空文件),否则 Python 不会将其识别为包。

  • 从 Python 3.3 开始,根据 [PEP 420] 的规定,引入了隐式命名空间包(Implicit Namespace Packages),允许没有__init__.py 的目录也被当作包来导入。

3. 作用和常见用途

3.1 标记目录为Python包

没有__init__.py,Python 不会认为这是一个包,也就无法通过import package_name的方式引入它。

3.2 简化 API 接口暴露

目录结构:

main.py
demoA/
├── __init__.py
├── module_a.py  # 定义变量A
└── module_b.py  # 定义变量B

如果没有__init__.py文件中,我们在main.py中需要如下才能访问变量,我们需要从Python文件中获取我们需要的资源

from demoA.module_a import A
from demoA.module_b import B

print(A)
print(B)

如果有__init__.py文件中,我们可以在__init__py文件中将资源汇聚出来。

from .module_a import A
from .module_b import B

这样我们的demoA就是一个别识别的Python包,我们直接可以从包中获取想要的资源

from demoA import A
from demoA import B

print(A)
print(B)

有些大型库(如 Flask、Pandas)会在__init__.py中暴露最常用的接口,让用户更方便地使用。

3.3 初始化包级别的变量或配置

可以在__init__.py中定义一些全局变量或配置信息,可以供我们使用。将__init__.py修改为如下代码:

__version__ = "1.0.0"
__author__ = "码匠er"

print("init demoA")

将调用时的代码修改为如下代码:

import demoA

print(demoA.__version__)
print(demoA.__author__)

执行结果如下:

我们可以看出,在我们导包时就会执行__init__.py中的代码。所以我们将初始化代码写在这个文件中。

3.4 控制通配符导入

在__init__.py中定义__all__列表,可以控制from package import *时导入哪些模块或变量。

直接上代码:

# __init__.py
from .module_a import A
from .module_a import B
from .module_a import add

__all__ = ['A', 'B', 'add']
# module_a.py
A = 5
B = 6
C = 7
def add(a, b):
    return a + b
# main.py
from demoA import *

print(A)
print(B)
print(add(A, B))

# print(C) 报错

有时候我们不希望用户直接访问包内的某些模块,可以通过__init__.py进行接口封装,然后使用__all__变量进行控制。

3.5 延迟加载

为了提高性能,有时我们会选择在__init__.py中只导入常用模块,其他模块按需导入。

# __init__.py

def get_modules():
    from .module_b import B # 如果module_b中的B是一个不常用且很大的模块,我们不需要一直引用,而是需要的时候再调用
    return B()

3.6 单元测试的妙用

在测试框架中,__init__.py可以用于批量导入测试用例。

tests/
├── __init__.py
├── test_api.py
└── test_models.py

__init__.py文件中,我们可以这样写:

import unittest
from .test_api import TestAPI
from .test_models import TestModels

def suite():
    test_suite = unittest.TestSuite()
    test_suite.addTest(unittest.makeSuite(TestAPI))
    test_suite.addTest(unittest.makeSuite(TestModels))
    return test_suite

只需执行python -m tests即可运行所有测试。是不是方便了很多呢?

最后

通过这篇文章的学习,我相信你应该知道__init__.py虽然只是一个简单的文件,但它却是 Python 模块化体系中非常关键的一环。无论是构建自己的库,还是理解他人项目的结构,掌握它的用法都能让你事半功倍。还有什么其他的问题可以留言或者找我哦!

🎯 我正在做开源项目,如果你想提升编程能力、参与真实项目、写进简历加分项,我们正在开发多个有趣实用的 Python 开源项目,涵盖多个方向。无论你是初学者还是想积累项目经验,都欢迎你的加入!

💬 感兴趣的话欢迎在后台留言关注我公众号哦

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。