一文教你看懂__init__.py这个被大家轻视的文件
大家好,我是
码匠er
。在开源工作中,你是否有过下面的经历?克隆了一个Python开源项目,满怀期待准备运行时,却收到了这样的报错:ModuleNotFoundError: No module named 'xxx'
。这个问题就是因为我们今天的主角__init__.py
文件造成的,今天我们就扒一扒这个文件的神秘面纱!
1. 揭开__init__.py 的神秘面纱
- **标识包身份:**当 Python 解释器遇到一个包含__init__.py的目录时,会自动将其识别为一个包(Package),而非普通文件夹。
- **模块预处理:**当使用
import package
语句导入包时,Python 会优先执行__init__.py中的代码。我们可以在这里完成模块的预加载、全局变量定义等初始化操作。 - **控制导出内容:**通过定义
__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 开源项目,涵盖多个方向。无论你是初学者还是想积累项目经验,都欢迎你的加入!
💬 感兴趣的话欢迎在后台留言关注我公众号哦
- 点赞
- 收藏
- 关注作者
评论(0)