美文网首页
Python使用反射实现Excel与对象之间的转换

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

作者: 言午日尧耳总 | 来源:发表于2020-12-03 22:12 被阅读0次

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

    代码在最下方,伸手党直接滚动到最后

    场景

    需要从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 os
    import re
    from 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)
    
    # 保存为Excel
    a1=A()
    table=[a1]
    ExcelHelper.save("456.xls", [table])
    

    注意

    • 该帮助类均为@classmethod
    • 该帮助类使用反射实现,所以表头名称需要与对象的字段名一一对应(如代码中的class A 与 下表"表1-1")
    • Excel中可以有多张表(sheet tab),所以参数为对象列表的列表,请注意对应关系
    • 当前读取Excel仅能转换为一个class类型,所以多种表结构请使用多张表

    表1-1

    name age isManager
    老王 18 True
    Tom 28 FALSE

    参考资料

    PYTHON里的反射(自学习)

    以上

    相关文章

      网友评论

          本文标题:Python使用反射实现Excel与对象之间的转换

          本文链接:https://www.haomeiwen.com/subject/lcivwktx.html