【QuotationTool的代码实现】framework部分
项目链接:https://gitee.com/xyjtysk/quotationTools
本章主要介绍framework的功能实现
之前我们讲到,framework其实存放的是所有可以复用的代码,包括
-
调用第三方库读写Excel
-
对log工具的改进包
-
对MySQL的操纵包
代码结构
我们先来看一下代码结构
功能函数
function,includeList.py,pc.py都属于功能函数,也就是基本上每个项目里面都会有他们
function.py
function.py其实主要是为了M、V、C调用起来更为方便。
我们希望M、V、C这三种类型的代码命名更为规范,比如
-
业务模块M,我们希望它命名为类似“xxxxModelClass”这样,也就是“功能+Model+Class”这样。
-
控制模块C,我们希望它类似"xxxxControllerClass"这样,把Controller带上
-
视图模块V,我们也希望它能带上"xxxViewClass"字样
既然这三者都这么规范了,我们何不使用一个统一的函数来调用了,这样更为简单 。
于是对于Model的调用为,具体代码可参考python模块&类的导入
其实就是根据字符串动态的导入相应的模块,最后进行实例化
# name:字符
# 作用:初始化Model
def M(name):
libs = __import__('libs.Model.'+name+'ModelClass');#动态的导入
model = getattr(libs , "Model");#通过反射一步一步获得模块"xxxModelClass"
testModelClass= getattr(model , name+'ModelClass');
testModel = getattr( testModelClass, name+"Model");#通过反射获得类testModel
return testModel();#实例化
那么调用方法呢?
比如
rehandleInstance = M("rehandle");
实际上它就是首先将libs.Model.rehandleModelClass这个模块加载进去了,然后调用rehandleModelClass()
来进行实例化,赋给rehandleInstance
对于View和Controller的实例化代码也类似。
includeList.py
includeList.py把framework里面所有自定义的模块的加载语句都列出来。
这样的好处就是,我在其他地方只需要加载includeList.py就可把framework里面的所有模块加载进来
from framework.includeList import *
includeList.py的详细代码:
# !/usr/bin/env python3
#encoding:UTF8
# 将自定义的库加入
from framework.libs.config.configParserClass import *
from framework.libs.excel.XlsReaderClass import *
from framework.libs.logutil.LogUtilsClass import *
from framework.libs.view.XlsWriterClass import *
from framework.libs.db.MySQLClass import *
from framework.function.function import *
pc.py
pc.py的主要功能是提供一个对外调用的接口。
我们可以在admin.py里面调用
method = getParser("controller","method");
pc.run("quotation",method);
第1句指的是从配置文件里面读method方法是什么。
然后调用pc.py中的run函数,把要执行的controller和method同时传递进去。
也就是调用quotaitonControllerClass中的method方法。
具体实现:
# !/usr/bin/env python3
#encoding:UTF8
import os.path
import os
from framework.includeList import *
def run(controller,method):
C(controller,method);
# C("quotation","index");
自定义函数
在framework/libs里面存放的都是自定义函数。
config/configParserClass.py
configParserClass.py的作用是解析configure.conf文件的。
如下为configure.conf中的一段代码
[section]
key=value
其中 [section]
称为域,可以将这个configure.conf文件划分成若干个逻辑上的模块,
key=value
本质上就是一个键值对。
所以这个解析函数就是首先找到某个域,然后去这个域里面通过key
读取里面的value
需要注意的是:
configure.conf文件中不能存在两个相同的section和key
那调用方法是就是
getParser("section","key")
第一个参数是section,第二个参数就是要取的key
那么编写代码:
#获取config配置文件
# 配置文件格式
# [section]
# key=value
def getParser (section, key):
try:
config = configparser.ConfigParser();
# # 路径:当前文件上两层
# os.path.dirname(__file__):获取当前文件的路径
path = os.path.join(os.path.dirname(os.path.dirname (os.path.dirname(os.path.dirname(__file__)))),'configure.conf') ;
# 文件格式:UTF-8
config.read(path , 'UTF-8');
value = config.get(section,key)
except Exception as data:
print("configure文件中存在两个相同的section"+str(data))
return value
代码的含义为,先找到configure.conf存放的路径,然后读取它,最后使用get函数来获得key对应的value值。
logutil/LogUtilsClass.py
我们平时要打印信息,可能最常用的就是print()
函数了,但是在工程里面使用这个函数,多有不便。
它最大的问题在于不能对信息分级
怎么理解?
比如说有些信息是错误信息,我应该在任何时候都显示,有些信息只是调试中用到信息,我希望在上线以后就关闭它,有些信息是为了给客户看的,作为一个提示。
但是在python中就一个print()函数,无法很好的控制什么时候让它显示呢。
所以我们可以定义4种信息打印级别:
-
DEBUG:表示调试信息
-
INFO:给用户展示的信息
-
ERROR:错误信息
-
NOTHING:啥都不显示
DEBUG = 0;
INFO = 1;
ERROR = 2;
NOTHING =3 ;
设定当前的级别为level
,我们可以从配置文件中读取
level=int(getParser("loglevel","level"))
如果当前的level为3,说明它处于NOTHING状态,其他信息当然不显示。
也就是DEBUG,INFO,ERROR等信息只有在它大于level级别的时候才显示
所以
def debug(msg):
if (DEBUG >= level):
print(msg);
def info (msg):
if(INFO >= level):
print(msg);
def error (msg):
if (ERROR >= level):
print(msg);
那么在打印信息的时候,如果此时我们想打印一个debug信息就可以
debug("debug信息")
当level设置为INFO的时候,因为level > DEBUG,所以不会显示。
也就是说
在开发的时候,我们可以把调试信息用debug()打印,level = DEBUG,此时正常输出。
上线以后,我们把level级别设置为更高一级的,debug信息就不会打印了。
excel/XlsReaderClass.py
XlsReaderClass.py是使用xlrd
模块进行Excel读取的。
这个模块的具体使用方法可见:【python】python读excel-xlrd
数据结构
那么我们希望读出来的数据是这个什么样子呢?也就是数据结构是怎么样的?
既然Excel是个二维表格,那么读出来的数也放在一个二维表格里面得了。
产品编码 | 产品型号 | 数量 | 标准价(RMB) |
---|---|---|---|
0235A0W2 | RT-MSR5660 | 2 | 50000 |
这样的缺点在于:取每个元素,需要计算index,不方便编程。
比如我们要取第三行的“RT-MSR5660”,我们需要使用a[1][1]
来取,非常不方便。
我们知道最方便取的数据结构为dict,只要传进去一个key,它就会返回一个value,这样的好处是
-
可以为每一列赋予实际的含义,比如说可以把产品型号的key设为
BOM
,我们要取“RT-MSR5660”的时候,就可以用a[1]['BOM']
-
如果要调换列的顺序,可以轻松做到,更为的灵活
因为每一列赋予了实际的含义,我们根本不用担心具体的顺序
那么具体的数据格式应该是怎么样的呢?
[
{"BOM":"产品编码",
"typeID":"产品型号",
"description":"项目名称",
"totalQuantity":"数量",
"unitsNetListPrice":"标准价(RMB)"},
{"BOM":"0235A0W2",
"typeID":"RT-MSR5660",
"description":"H3C MSR 56-60路由器机框",
"totalQuantity":"2",
"unitsNetListPrice":"50000"}
]
-
原始表格中的每一行为一个dict
-
所有行组成一个list
从Excel中读数据
既然现在数据结构已经设计好了,我们就来看如何读数据,并形成这样的数据结构吧。
首先引入模块
import xlrd
然后定义一个操作类
class XlrdTool(XlsReader):
# 作用:获取关联数组
# inputHeaderKey:数组每一列的对应的键值
# 返回:一个数组,数组的每一行为一个dict,代表原来表格里面的每一行,其中此dict的键名为输入的inputHeaderKey,键值为读入的excel文件的对应值。
def getAssociativeArray (self, excelPathName, sheetName , inputHeaderKey):
list = []
try:
sheetList = xlrd.open_workbook(excelPathName).sheet_by_name(sheetName);
# row:表示从当前sheet读出了的每一行,
# 将每一行的row_values与inputHeaderKey组成dict
list = [dict (zip (inputHeaderKey , sheetList
.row_values(row))) for row in range(sheetList.nrows)];
except Exception as data:
print("打开文件失败,%s" % data);
return list;
里面最关键的代码其实只有:
list = []
sheetList = xlrd.open_workbook(excelPathName).sheet_by_name(sheetName);
list = [dict (zip (inputHeaderKey , sheetList
.row_values(row))) for row in range(sheetList.nrows)];
我们来一一看看。
-
从Excel中的某个sheet里面读出数据,
xlrd.open_workbook(excelPathName).sheet_by_name(sheetName)
-
[dict (zip (inputHeaderKey , sheetList .row_values(row))) for row in range(sheetList.nrows)];
我们可以拆解一下:- 这是一个列表生成式,
for row in range(sheetList.nrows)
表示对读出来的数组的每一行row
进行遍历,
对其中某一行row
-
首先使用xlrd中的函数
row_values
取出每一行的值 -
inputHeaderKey
表示为每一列赋予的一个key
,它是一个数组。 -
然后使用
zip
把读出来的每一行与inputHeaderKey
组成键值对,再在外面迁套dict形成一个字典。
也就是
- 这是一个列表生成式,
{"BOM":"0235A0W2",
"typeID":"RT-MSR5660",
"description":"H3C MSR 56-60路由器机框",
"totalQuantity":"2",
"unitsNetListPrice":"50000"}
- 所有的dict形成一个列表