1. 什么是模块
在《失控》中提到一个几千万行的程序(操作系统)要想没有bug是很难,减少bug的解决方案就是不断的拆分功能项,直到不能拆分为止,只要保证拆分的最小功能项通过一定的单元测试,就可能消费bug(当然还有集成性和系统性的bug)。《失控》里说的就是现代软件工程的模块化开发方式,在面向对象开发以及架构设计上都会涉及到。
那什么是模块?从上面可知,模块可以理解为独立的功能拆分,可根据具体业务拆分成大模块和小模块。
举几个例子:
一个入库系统可以拆分成前端的展示模块,后端的业务处理以及中间的交互层;后端业务处理可以拆分成数据处理(如数据库操作等)和具体的不同的业务模块(如计费模块,折扣模块,物料模块等);
一个深度学习的训练可以拆分:
- 数据模块:负责处理和加载数据
- 网络模块:负责构建深度学习网络
- 训练模块:负责调用数据、构建网络以及设计损失和优化器,以及梯度方向传播,还有模型参数的保存和加载等
- 测试推理模块:负责模型测试和推理
- 功能模块:比如常见的utils
以上可能是比较大的模块,还可以细分成更细的小的模块;
- 比如数据模块,可以拆分成小的特定数据的模块,比如不同的数据集的解析不同,可以构建不同的数据集模块和以及可能的数据处理模块(如数据增强);
- 网络模块,可以拆分成不同类型的网络等
可想而知,模块和模块之间是存在引用的关系,设计时要尽可能做到松耦合,能更友好的变更,如果一次变更需要修改大部分的代码,可以考虑下是否做到合理的模块化设计。
总结一下:
- 模块是功能的划分,是软件程序的架构设计
- 模块化是减少bug的有效途径
- 可以适当的模块化,也不要过度模块化,看功能的复杂性,越复杂就有必要拆分的更细
- 千万不要把所有代码都写到一个文件哦!
- 模块不要相互引用
- 如果你听到别人说什么数据层,业务层,xx层,你可以理解为就是模块
2. python中模块
python中模块可以分为2个:
- 一个python的.py文件,真正的处理功能的模块,可以是公共变量、函数以及类等
- 对模块的逻辑划分即,通过目录层级的方式来管理模块,python是通过package包的方式来实现的;
一个简单的python模块设计:包括主程序main.py,模块model和两个package包(data/utils)
./
├── main.py
├── model.py
├── data
│ ├── dataset.py
│ ├── __init__.py
├── utils
│ ├── utils.py
│ ├── __init__.py
我们先来看下,和主程序平级的模块的调用,model.py 如下包括变量、函数和类等:
model.py
test = 1 def func(): print('this is func in model.py') class Cls(object): def __init__(self, a): print('init {}'.format(a)) self.a = a def process_a(self): return self.a + 1
调用
import model print(model.test) model.func() cls = model.Cls(1) print(cls.process_a()) # 1 # this is func in model.py # init 1 # 2
从上可知,模块是通过import
进行导入, 并通过模块名+.的方式调用;
import可以有不同的方式:
- 导入整个模块: import model
- 导入模块内的元素: import model.func as func
- 通过from 方式: from model import func
不过,强烈建议按导入整个模块
的方式,避免多个模块相互冲突;比如模块A和模块B都要func,如果通过from方式导入,最终func混淆,实际为后导入的func的功能,除非你又进行as重命名
接着,我们来看下包里模块的调用;我们知道包是同步目录的方式来管理模块,那如何区分目录和包?
是的,python是通过目录下添加__init__.py文件(可以是空的),来使得正常目录变成包。
import data.dataset as dataset
print(dataset.test)
dataset.data_func()
cls = dataset.DCls(1)
print(cls.process_a())
包下模块的调用,在import时需要带上包名,其他和正常的包一样。
包中模块的导入和目录路径一样,可以分为相对导入和绝对导入
data.dataset
是绝对导入from ..data import dataset as dataset
是相对导入(比如在utils中引用dataset), .表示当前路径,..表示上一级目录,一次类推
不建议相对导入
2.1 python如何寻找模块
我们一般是通过pip install
方式来安装第三方的包,安装完在程序里直接通过import
就可以直接调用模块的功能,而这些包和模块并不是我们的程序的同级目录,一个问题是python 解释器是如何正确找到这些包和模块?
python解释器搜索模块范围和优先级如下:
- 1.当前目录
- 2.PYTHONPATH环境变量目录
- 3.安装Python时配置的与安装相关的目录
安装第三方的包是属于第3种的情况下,一般是你的python目录或者虚拟环境目录。
所以说自己建立的模块名不要和第三方模块相同,否则你import的时候按照顺序你导入时自己的模块,而不是第三方的模块。了解了这个可以避免自己都觉得不可解释的问题。
对于模块,我们经常会遇到模块找不到的问题: ModuleNotFoundError: No module named 'data.dataseta'
可以检查下:
-
- 模块的包和模块名是否正确
-
- 如果1正确情况,python是否能够搜索到模块,可以通过查看
sys.path
看下是否包含你需要的模块
- 如果1正确情况,python是否能够搜索到模块,可以通过查看
以下为本地的python包搜索范围:
['',
'D:\\Programs\\anaconda3\\python36.zip',
'D:\\Programs\\anaconda3\\DLLs',
'D:\\Programs\\anaconda3\\lib',
'D:\\Programs\\anaconda3',
'D:\\Programs\\anaconda3\\lib\\site-packages',
'D:\\Programs\\anaconda3\\lib\\site-packages\\win32',
'D:\\Programs\\anaconda3\\lib\\site-packages\\win32\\lib',
'D:\\Programs\\anaconda3\\lib\\site-packages\\Pythonwin',
'D:\\Programs\\anaconda3\\lib\\site-packages\\IPython\\extensions']
如果sys.path
没有包含,可以手动添加的方式将目录添加到sys.path
中,如:
import os, sys
cur_path = os.path.abspath(os.path.dirname(__file__))
sys.path.append(cur_path)
当前你可以直接修改PYTHONPATH环境量,linux中为export PYTHONPATH="./"
3. 模块内置方法
python为每个模块提供了内置方法,如果遇到无法解释的现象可以看看这些方法,判断是否调用了正确的模块
- file 提供__file__ 获取模块路径
- name 模块名
- package 包名
- dir()内置函数返回包含模块定义的名称的字符串的排序
print(model.__file__)
print(dataset.__package__)
print(model.__name__)
print(dir(model))
# D:\mywork\notebook\model.py
# data
# model
# ['Cls', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'func', 'test']