本文文章源自:__init__.py和__main__.py文件使用心得 - 知乎
在构建大型Python项目时经常会使用到__init__.py和__main__.py文件,通过它们可以灵活的管理模块和包。
1 __init__.py文件的应用
在Python项目中,如果某个文件夹被设置成包的形式,那么该文件夹中必须要包含一个__init__.py文件,即使它是空文件。当导入这个包时,__init__.py文件中的代码会被优先执行。
- 构建层级包
封装一个包,确保每个目录下都定义了一个__init__.py文件, 例如:
ml_api/
__init__.py
regression/
__init__.py
linear_regression.py
ridge_regression.py
...
classification/
__init__.py
svm.py
xgboost.py
...
run.py
这样就能够在其他项目模块执行各种import语句,如下所示:
import ml_api.classification.svm
from ml_api.classification import xgboost
import ml_api.regression import ridge_regression as ridge
__init__.py文件的目的是使得不同运行级别的包能够可选的初始化代码。如果执行了语句import ml_api
那么文件ml_api/__init__.py将被导入,建立以ml_api为命名空间的内容。像import ml_api.classification.svm
这样导入,那么文件ml_api/__init__.py和文件ml_api/classification/__init__.py将在文件ml_api/classification/svm.py导入之前导入。
- 控制子模块导入
绝大部分时候让__init__.py文件为空就好,但是有些情况下可以包含代码,从而行使特殊的功能,如控制子模块导入。
# ml_api/classification/__init__.py
from . import svm
from . import xgboost
现在仅通过import ml_api.classification
一行代码就可以替代import ml_api.classification.svm
和import ml_api.classification.xgboost
两行代码的功能。
- 对子模块进行重构
__init__.py还可以将多个模块合并到一个逻辑命名空间。程序模块可以通过变成包的形式分割成多个独立的文件,不妨考虑
# test_module.py
class C1:
def f1(self):
print("C1.f1")
class C2(C1):
def f2(self):
print("C2.f2")
如果想把test_module.py拆分成两个文件,然后分别定义成类,从而实现逻辑上独立。要做到这一点,首先需要使用test_module目录来替换文件test_module.py。 这这个目录下,创建如下文件:
test_module/
__init__.py
c1.py
c2.py
在c1.py文件中插入以下代码:
# c1.py
class C1:
def f1(self):
print("C1.f1")
在c2.py文件中插入以下代码:
# c2.py
from .c1 import C1
class C2(C1):
def f2(self):
print("C2.f2")
最后,在 __init__.py 中,将上述两个文件聚合在一起:
# __init__.py
from .c1 import C1
from .c2 import C2
至此,test_module就整合为统一的逻辑模块
>>>import test_module
>>>c1 = test_module.C1()
>>>c1.f1()
C1.f1
>>>c2 = test_module.C2()
>>>c2.f2()
C2.f2
上面讨论的其实是包的设计问题,在一个大型的代码库中,每个功能模块都是独立的文件,用户如果想使用相应的功能按需导入即可
from package.module_1 import C1
from package.module_2 import C2
...
但是这样往往会导致用户的负担增加,因为他需要知道不同的功能代码位于包的哪个模块,通常情况下应该是将这些逻辑统一起来,使用一条import语句简化导入流程
from package import C1, C2
因此需要使用__init__.py文件来将每个独立的模块聚合在一起。再举一个亲身使用的例子,假设使用fastAPI作为后端搭建机器学习算法API,为了叙述方便,简化的目录结构如下
ml_api/
ml/
__init__.py
regression.py
classification.py
run.py
构建回归算法的请求API
# regression.py
from fastapi import APIRouter
...
regression_app = APIRouter()
...
构建分类算法的请求API
# classification.py
from fastapi import APIRouter
...
classification_app = APIRouter()
...
使用__init__.py文件整合所有算法模块
# __init__.py
from .regression import regression_app
from .classification import classification_app
最后在run.py文件中统一所有的路由,组建完整的API
import uvicorn
from fastapi import FastAPI
...
from ml import regression_app, classification_app
app = FastAPI(...)
...
app.include_router(regression_app, prefix='/regression')
app.include_router(classification_app, prefix='/classification')
if __name__ == '__main__':
uvicorn.run('run:app', host='0.0.0.0', port=55555, reload=True, debug=True)
- 延迟加载
对于构建一个很大的包,如果在__init__.py文件中一次性导入了所有必需的模块,可能会引发一些问题。所以在某些特殊情况下,需要设计成当组件使用时才会被加载。以上一小节第一个项目为例,如果要实现这种逻辑,__init__.py文件需要改动一些代码:
# __init__.py
def C1:
from .c1 import C1
return C1()
def C2():
from .c2 import C2
return C2()
在这个版本中,只有当C1和C2函数被调用时才会加载C1和C2类,但是对于用户而言,使用方法上并不会有什么不同
>>>import test_module
>>>c1 = test_module.C1()
>>>c1.f1()
C1.f1
2 __main__.py文件的应用
在Python项目中,__main__.py相较于__init__.py文件的用法来说比较单一,一句话总结就是,如果想要使得某个文件夹能够执行,那么该文件夹中必须要包含一个__main__.py文件,否则就会抛出异常。假设项目目录结构如下
your_package/
__init__.py
__main__.py
a.py
b.py
...
然后在__main__.py文件中插入行使相关功能的代码,运行your_package即可
>>>python your_package