项目链接:https://gitee.com/xyjtysk/quotationTools
采用什么样的数据结构
那么我们希望读出来的数据是这个什么样子呢?也就是数据结构是怎么样的?
既然Excel是个二维表格,那么读出来的数也放在一个二维表格里面得了。
我们一个简化版的例子来看,下面的表格是从原始表格中截出来的一部分。
产品编码 | 产品型号 | 数量 | 标准价(RMB) |
---|---|---|---|
0235A0W2 | RT-MSR5660 | 2 | 50000 |
这样的缺点在于:取每个元素,需要计算index,不方便编程。
比如我们要取第三行的“RT-MSR5660”,我们需要使用a[1][1]
来取,非常不方便。
我们知道最方便取的数据结构为dict,只要传进去一个key,它就会返回一个value,这样的好处是
-
可以为每一列赋予实际的含义,比如说可以把产品型号的key设为
BOM
,我们要取“RT-MSR5660”的时候,就可以用a[1]['BOM']
-
如果要调换列的顺序,可以轻松做到,更为的灵活
因为每一列赋予了实际的含义,我们根本不用担心具体的顺序
那么具体的数据格式应该是怎么样的呢?
首先我们为所能用到的列分配一个key值,映射表格如下:
NHCT中的列 | key值 |
---|---|
ID | ID |
产品编码 | BOM |
产品型号 | typeID |
项目名称 | description |
单套数量 | quantity |
目录价 | unitsNetListPrice |
折扣 | discount |
单价 | unitsNetPrice |
总价 | totalPrice |
总目录价 | totalListPrice |
产线 | PL |
WATSON_LINE_ITEM_ID | waston |
备注 | remarks |
那么下面表格所对应的数据结构可以设计成这个样子。
产品编码 | 产品型号 | 数量 | 标准价(RMB) |
---|---|---|---|
0235A0W2 | RT-MSR5660 | 2 | 50000 |
[
{"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
然后定义一个操作类,从excel中读取数据并转换的函数为getAssociativeArray
,需要把Excel的完整路径传递进去,以及要读取的sheet的名称,还有就是为每列取的key值
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形成一个列表
总结一下就是,把为每列分配的key值数组与每一行进行zip,然后转换为dict,最后把所有的dict组成一个list。
数据结构的特点
上面讲了如何从Excel读取数据形成数组。
我们再来看一下这种数据结构
a = [
{"BOM":"产品编码",
"typeID":"产品型号",
"description":"项目名称",
"totalQuantity":"数量",
"unitsNetListPrice":"标准价(RMB)"},
{"BOM":"0235A0W2",
"typeID":"RT-MSR5660",
"description":"H3C MSR 56-60路由器机框",
"totalQuantity":"2",
"unitsNetListPrice":"50000"}
]
它是一个嵌套的数据结构,总体上是一个list,它有两个元素,每个元素都是一个dict
那么这个地方就有个坑点了
如果我们再把这个list赋给b,然后在b中把price的价格修改了。
那么list最开始指向的dict并没有变,没有指向另一个元素,所以list没有改变,
但是dict发生改变了。
也就是a对应的那个price也发生了改变了。
所以我们需要注意,如果把list赋给另一个变量以后,一定要深复制一份。
如何遍历
对于我们这个项目来说,遍历可能是最重要的算法了。首先我们来看一下我们官方给出来的表格吧。
一套配置清单其实有若干套设备构成,每套设备又有一个子标题以及相应的详细配置信息等,还有小计等。
多套设备组成了整个清单,
在讲遍历前,我们需要对每个区域取个名字。
加上colorTag
我们可以把所有行分为如下几类:
-
header:表示总的标题
-
site:表示每一套设备的子标题
-
subtotal:对每一套设备的小计
-
total:总计
-
general:详细配置信息
这几种他们对应的颜色也可以设为不同的,所以统称为colorTag
那么怎么在程序中区分不同的行的类型呢?
我们知道之前设计的数据结构本质就是一个list,而每一行是一个dict,所以只需要再加一个键值对即可,比如总计行就加上"colorTag":"total"
即可。
之前读取Excel数据的时候并没有加上这个ColorTag,那么现在要加的话,需要对整个list进行一次遍历,识别每一行的特征,加上相应的colorTag
aDiff = [i for i in ['BOM','typeID','description'] if i in self.lists[0].keys()];
colTag = aDiff[0];
for aList in self.lists:
if aList[colTag] == "小计":
aList['colorTag'] = "subtotal";
elif aList[colTag] == "总计":
aList['colorTag'] = "total";
elif aList['ID'] != "":
aList['colorTag'] = 'site';
else:
aList['colorTag'] = "general";
self.lists[0]['colorTag'] = "header"
解释一下代码:
-
首先colTag指的是'BOM','typeID','description'这几个谁存在,则取谁为colTag
-
然后遍历数组的每一行,如果aList[colTag]="小计"或者踪迹的时候,就可以判断出是小计行或者总计行
-
另外我们观察得到ID列除了子标题site对应的行有值,其他的行都是空的,所以可以使用
aList['ID'] != ""
来判断哪些是site行 -
第一行就是header行
-
剩下的自然是general
我们还可以再遍历一次新生成的list,然后把colorTag为site的那些行的序号取出来,这就可以确定每一套设备的起始和截止的位置了。