Python 使用反射实现 Excel 与对象之间的转换
作者:万百入年许老
来源:SegmentFault 社区
场景
需要从Excel中加载到内存中,转换为class对象执行操作
环境
Python3.8 openpyxl==3.0.5
前置知识
反射(仅介绍这个帮助类用到的几个反射方法)
setattr、getattr
class Person():name = Nonedef __init__(self, name):self.name = namep = Person("laowang")n = getattr(p, "name")print(n)# 打印结果:laowangsetattr(p, "name", "laoxu")n2 = getattr(p, "name")print(n2)# 打印结果:laoxu
反射实例化对象
class Person():name = Nonedef print_sth(self):print("测试实例化方法", self.name)def test(clazz):"""实例化对象:param clazz: 要实例化的类型"""x = clazz()setattr(x, "name", "老王")x.print_sth()# 打印:测试实例化方法 老王test(Person)
Excel操作类库 - openpyxl
创建Excel
from openpyxl import Workbookwb = Workbook()ws1 = wb.activews1.append(['name', 'age', 'isManager', 'remark'])ws1.append(['', '', '', ' '])ws1.append(['老王', '18', 'false', ' '])ws1.append(['laoxu', '28.4', 'TRUE', 'zaoricaifuziyou'])ws1.append(['', '', '', ' '])ws2 = wb.create_sheet("ws2")ws2.append(['name', 'age', 'isManager', 'remark'])ws2.append(['小李', '50', 'TRuE', 'fly knife'])ws2.append(['', '', '', ' '])ws2.append(['aaa', '11', 'false', 'hehehe'])wb.save("test_convert_2_class.xlsx")
读取Excel
from openpyxl import Workbookdef print_row(arr):"""为了显示方便,打印行"""for item in arr:print(item,end="\t\t|")print()# 读取上一个代码块创建的Excel代码work_book = load_workbook("test_convert_2_class.xlsx")result = []for sheet_name in work_book.sheetnames:print("-----------------",sheet_name,"-----------------")ws = work_book[sheet_name]# 获取表头table_heads = []for title_row in ws.iter_rows(max_row=1):for cell in title_row:table_heads.append(cell.value)print_row(table_heads)# 获取表数据table = []for row in ws.iter_rows(min_row=2):row_data=[]for column_index in range(len(row)):row_data.append(row[column_index].value)print_row(row_data)# 打印结果如下:# ----------------- Sheet -----------------# name |age |isManager |remark |# None |None |None | |# 老王 |18 |false | |# laoxu |28.4 |TRUE |zaoricaifuziyou |# None |None |None | |# ----------------- ws2 -----------------# name |age |isManager |remark |# 小李 |50 |TRuE |fly knife |# None |None |None | |# aaa |11 |false |hehehe |
伸手党代码
excel_helper.py
import osimport refrom os.path import isfilefrom openpyxl import load_workbook, Workbookdef _convert_value(value):"""将单元格中数据,区分基本类型类似"true"/"false"(不区分大小写)转换为bool值长得像数字的转换为float类型其他(空格、空行)转换为None:param value: 单元格的值:return: 转换后的类型"""value_str = str(value).lower()if value_str == 'true':return Trueelif value_str == 'false':return Falseelif re.match(r"^[+|-]?\d+.?\d*$", value_str):return float(value_str)elif re.match(r"^\s*$", value_str):return Noneelse:return valueclass ExcelHelper:"""Excel帮助类"""def convert_2_class(cls, file_path, clazz):"""转换为class,可转换多张sheet表,转换为统一clazz对象过滤掉为空行:param file_path:Excel文件路径:param clazz:结果转换为clazz对象:return: 对象列表的列表,结构为[[clazz(),clazz()],[clazz()]]"""if not file_path.endswith(".xlsx"):raise ValueError("文件必须为.xlsx结尾的Excel文件")if not isfile(file_path):raise FileNotFoundError("文件路径 {0} 不存在".format(file_path))work_book = load_workbook(file_path)result = []for sheet_name in work_book.sheetnames:ws = work_book[sheet_name]# 获取表头table_heads = []for title_row in ws.iter_rows(max_row=1):for cell in title_row:table_heads.append(cell.value)# 获取表数据table = []for row in ws.iter_rows(min_row=2):# 实例化对象instance = clazz()for column_index in range(len(row)):setattr(instance, table_heads[column_index], _convert_value(row[column_index].value))# 过滤空行(所有属性均为None的对象)is_valid = Falsefor attr in instance.__dict__:if not attr.startswith("_") and instance.__dict__[attr] is not None:is_valid = Truebreakif is_valid:table.append(instance)result.append(table)return resultdef save(cls, file_path, tables):if not file_path.endswith(".xlsx"):raise ValueError("文件必须为.xlsx结尾的Excel文件")work_book = Workbook()is_first = Truefor table in tables:if is_first:ws = work_book.activeis_first = Falseelse:ws = work_book.create_sheet()# 添加表头table_heads = []for attr in table[0].__dict__:# 过滤"_"开头的属性if not attr.startswith("_"):table_heads.append(attr)ws.append(table_heads)# 添加数据for row in table:data = []for head in table_heads:data.append(getattr(row, head))ws.append(data)try:# 生成保存文件夹路径folder_index = max(file_path.rfind("\\"), file_path.rfind("/"))if folder_index != -1:folder_path = file_path[0:folder_index]if not os.path.exists(folder_path):os.mkdir(folder_path)work_book.save(file_path)except Exception:raise OSError("创建Excel失败")
使用方法
# 导入类from excel_helper import ExcelHelper# 示例对象class A:name=Noneage=NoneisManager=None# 读取Excel文件,并转换为指定类型对象列表tables = ExcelHelper.convert_2_class("123.xlsx", A)# 保存为Excela1=A()table=[a1]ExcelHelper.save("456.xls", [table])
注意
该帮助类均为@classmethod 该帮助类使用反射实现,所以表头名称需要与对象的字段名一一对应(如代码中的class A 与 下表"表1-1") Excel中可以有多张表(sheet tab),所以参数为对象列表的列表,请注意对应关系 当前读取Excel仅能转换为一个class类型,所以多种表结构请使用多张表
表1-1

参考资料
PYTHON里的反射(自学习):https://www.cnblogs.com/kongk/p/8645202.html
以上

评论
