1.概述
在日常写代码的过程中,经常能在很多地方看到__init__.py
文件,似乎感觉没有什么用,但有时候却又不得不存在这个文件,而文件里面的内容通常又是空,那这个文件到底有什么作用呢?今天让我们就来探究一下。
2.Python的模块和包
在Python代码组织里面有两个基本单位模块(module)和包(package),两者之间的定义如下所示:
- 模块:简单来讲就是一个
py文件
。里面定义了变量、函数、类、方法等,示例如下所示:
# @IDE: PyCharm
# @Project: PyCharmProjects
# @File: hello.py
# @Time 2025-08-24 15:29
# @Author: Surpass
def hello(name:str="surpass")->str:
return f"Hello, {name}!"
def nationality(nationality:str="China")->str:
if nationality == "China":
return "I am Chinese"
elif nationality == "Japan":
return "I am Japanese"
elif nationality == "USA":
return "I am American"
else:
return "I am Astronaut"
使用方法如下所示:
# @IDE: PyCharm
# @Project: PyCharmProjects
# @File: main.py
# @Time 2025-08-24 15:35
# @Author: Surpass
import hello
if __name__ == '__main__':
print(hello.hello())
print(hello.nationality())
输出结果如下所示:
Hello, surpass!
I am Chinese
- 包:则是一个文件夹里面可以包含多个模块文件,即在一个文件夹里面可以包含多个py文件。示例如下所示:
├─calcuator
│ ├─add.py
│ ├─sub.py
│ ├─__init__.py
使用方法如下所示:
from calcuator.add import add
from calcuator.sub import sub
if __name__ == '__main__':
print(add(2, 3))
print(sub(2, 3))
3.__init__.py文件在做什么
要理解__init__.py
有什么作用,可以先看看这个文件在做什么操作?拿以上示例为例,文件目录如下所示:
├─calcuator
│ ├─add.py
│ ├─sub.py
│ ├─__init__.py
各文件内容如下所示:
# add.py
def add(a:int,b:int)->int:
return a+b
# sub.py
def sub(a:int,b:int)->int:
return a-b
# __init__.py
print("Call __init__")
# main.py
from calcuator.add import add
from calcuator.sub import sub
if __name__ == '__main__':
print(add(2, 3))
print(sub(2, 3))
在main.py运行代码,其结果如下所示:
Call __init__
5
-1
从输出结果,可以看出__init__.py
中的代码也执行了。则可以说明,当从一个包里面调用时,__init__.py
中的代码会被首先执行。
4.__init__.py文件的作用
虽然__init__.py
文件大部分情况文件内容为空,但其功能非常灵活,常用用法如下所示。
4.1 标识Python包
在模块所在文件夹里面添加__init__.py
后,则IDE会自动将这个文件夹识别为Python包
4.2 简化模块导入操作
假设包的目录结构如下所示:
├─calcuator
│ ├─add.py
│ ├─sub.py
│ ├─div.py
│ ├─mul.py
│ ├─__init__.py
如果要使用包里面的所有方法,常规写法如下所示:
from calcuator.add import add
from calcuator.sub import sub
from calcuator.div import div
from calcuator.mul import mul
如果不想每次都写这么麻烦,可以在__init__.py
这样写
# __init__.py
from .add import add
from .sub import sub
from .mul import mul
from .div import div
print("Call __init__")
则在其他地方引用和调用时,可以简写为如下形式
import calcuator
if __name__ == '__main__':
print(calcuator.add(2, 3))
print(calcuator.sub(2, 3))
4.3 批量导入
如果一个模块里面包含有很多变量、方法,而在调用时,嫌麻烦不想一个个写,则可以在__init__.py
文件中这样写
# add.py
from typing import List
def add_number(a:int,b:int)->int:
return a+b
def add_str(a:str,b:str)->str:
return a+b
def add_list(a:List[int],b:List[int])->List[int]:
return a+b
# __init__.py
from .add import *
在调用时,可以这样写
# main.py
import calcuator
if __name__ == '__main__':
print(calcuator.add_number(2, 3))
print(calcuator.add_list([2], [3]))
通过这种方法,就可以实现批量导入,但需要注意导入存在有类和方法同名的情况,则遵从后面导入的会覆盖先导入的。
4.4 限定导入范围
在模块导入时,需要遵从一个原则最小化导入原则,即需要使用哪些类、函数时,仅导入这些类、函数即可,尽可能避免使用 import * 这种形式,因此__init__.py
又可以改写为这个样子
from .add import add_number
这样改写后,在引用时,仅会引用add_number一个方法,而这样又会带来另一个问题,如果还要使用其他方法或模块,该怎么办呢?Python也提供了另一种方式,__init__.py
可以改写为这种形式
# 通过 __all__ 显式定义了要导入的模块列表
__all__=[
"add", # 对应于add.py
"div", # 对应于div.py
"sub" # 对应于sub.py
]
print("Call __init__")
在调用时,虽然了可以使用 import * ,但在__init__.py
已经限定了引用的范围。
from calcuator import *
if __name__ == '__main__':
print(add.add_str(2, 3))
print(mul.mul(2, 3)) # __init__.py已经有限定,因此这里会出现报错
4.5 初始化操作
在前面的示例,我们已经得知,在调用包时__init__.py
会优先执行,因此也可以在里面进行一些初始化操作。示例如下所示:
# __init__.py
import platform
import os
# 通过__all__ 显式定义了要导入的模块列表
__all__=[
"add", # 对应于add.py
"div", # 对应于div.py
"sub", # 对应于sub.py
]
# 演示在 __init__.py 里进行初始化操作
if platform.system() == "Windows":
os.makedirs(r"F:\python_test",exist_ok=True)
elif platform.system() in ["Darwin","Linux"]:
os.makedirs(r"/tmp",exist_ok=True)
else:
exit(255)
print("Call __init__")
4.6 版本管理
在存在多人协作时,方便快速确认版本,示例代码如下所示:
# __init__.py
__version__ = "8.9.0"
print("Call __init__")
# main.py
import calcuator
if __name__ == '__main__':
print(calcuator.__version__)
4.7 动态加载
在一些大型项目中,如果包里面的模块太多,一个个写,很容易出现遗漏的情况,于是就出现动态导入的情况,示例如下所示:
# __init__.py
import os
import importlib
__version__ = "8.9.0"
current_path=os.path.dirname(__file__)
for module in os.listdir(current_path):
if module.endswith(".py") and module != "__init__.py":
module_name=module[:-3]
import_module=f"{__name__}.{module_name}"
print(f"import {import_module}")
importlib.import_module(f"{__name__}.{module_name}")
print("Call __init__")
# main.py
import calcuator
if __name__ == '__main__':
print(calcuator.__version__)
print(calcuator.add.add_number(1,2))
print(calcuator.mul.mul(10, 20))
运行结果如下所示:
import calcuator.add
import calcuator.div
import calcuator.mul
import calcuator.sub
Call __init__
8.9.0
3
200
5. __init__.py最佳实践
虽然__init__.py
使用非常灵活,但也遵循以下建议:
- 在项目中,一定添加这个文件,用以标识为python包
- 在
__init__.py
同样可以添加代码,但尽量不要使用过多的逻辑,否则在导入包的时候会影响性能 - 合理使用
__all__
从而避免暴露过多的内部细节 - 动态加载功能很好,但也可能会带来性能问题