本文文章源自:__init__.py和__main__.py文件使用心得 - 知乎
在构建大型Python项目时经常会使用到__init__.py和__main__.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
在Python项目中,__main__.py相较于__init__.py文件的用法来说比较单一,一句话总结就是,如果想要使得某个文件夹能够执行,那么该文件夹中必须要包含一个__main__.py文件,否则就会抛出异常。假设项目目录结构如下
your_package/ __init__.py __main__.py a.py b.py ...
然后在__main__.py文件中插入行使相关功能的代码,运行your_package即可
>>>python your_package