Python 使用反射实现 Excel 与对象之间的转换

SegmentFault

共 6404字,需浏览 13分钟

 ·

2020-12-08 22:12

作者:万百入年许老

来源:SegmentFault 社区




场景


需要从Excel中加载到内存中,转换为class对象执行操作




环境


  • Python3.8
  • openpyxl==3.0.5




前置知识


反射(仅介绍这个帮助类用到的几个反射方法)


setattr、getattr

class Person():    name = None
def __init__(self, name): self.name = name

p = Person("laowang")n = getattr(p, "name")print(n)# 打印结果:laowang
setattr(p, "name", "laoxu")n2 = getattr(p, "name")print(n2)# 打印结果:laoxu


反射实例化对象


class Person():    name = None
def 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 Workbook

wb = Workbook()ws1 = wb.active
ws1.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 Workbook
def 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 isfile
from openpyxl import load_workbook, Workbook

def _convert_value(value): """ 将单元格中数据,区分基本类型 类似"true"/"false"(不区分大小写)转换为bool值 长得像数字的转换为float类型 其他(空格、空行)转换为None :param value: 单元格的值 :return: 转换后的类型 """ value_str = str(value).lower() if value_str == 'true': return True elif value_str == 'false': return False elif re.match(r"^[+|-]?\d+.?\d*$", value_str): return float(value_str) elif re.match(r"^\s*$", value_str): return None else: return value

class ExcelHelper: """ Excel帮助类 """
@classmethod 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 = False for attr in instance.__dict__: if not attr.startswith("_") and instance.__dict__[attr] is not None: is_valid = True break if is_valid: table.append(instance) result.append(table) return result
@classmethod def save(cls, file_path, tables): if not file_path.endswith(".xlsx"): raise ValueError("文件必须为.xlsx结尾的Excel文件") work_book = Workbook() is_first = True for table in tables: if is_first: ws = work_book.active is_first = False else: 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=None age=None isManager=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

以上




点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流。

- END -

浏览 8
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报