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

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

nameageisManager
老王18True
Tom28FALSE

参考资料

PYTHON里的反射(自学习)

以上

不秃顶、不猝死,顺顺利利活到100可以吗?

1 声望
2 粉丝
0 条评论
推荐阅读
python日志logging配置
python日志logging配置为了方便ELK收集日志,将日志打印成json格式开发过程中,使用json格式不方便排查问题本文章使用python的logging模块,一步步增加配置,来说明每个组件作用原始日志python可以使用两种方式打...

言午日尧耳总阅读 742

Ubuntu20.04 从源代码编译安装 python3.10
Ubuntu 22.04 Release DateUbuntu 22.04 Jammy Jellyfish is scheduled for release on April 21, 2022If you’re ready to use Ubuntu 22.04 Jammy Jellyfish, you can either upgrade your current Ubuntu syste...

ponponon1阅读 3.9k评论 1

如何写成高性能的代码(三):巧用稀疏矩阵节省内存占用
一般来说,在矩阵中,若数值为0的元素数目远远多于非0元素的数目,并且非0元素分布没有规律时,则称该矩阵为稀疏矩阵;与之相反,若非0元素数目占大多数时,则称该矩阵为稠密矩阵。定义非零元素的总数比上矩阵所...

葡萄城技术团队2阅读 930

日常Python 代码片段整理
1、简单的 HTTP Web 服务器 {代码...} 2、单行循环List {代码...} 3、更新字典 {代码...} 4、拆分多行字符串 {代码...} 5、跟踪列表中元素的频率 {代码...} 6、不使用 Pandas 读取 CSV 文件 {代码...} 7、将列表...

墨城2阅读 291

Unicode 正则表达式(qbit)
前言本文根据《精通正则表达式》和 Unicode Regular Expressions 整理。本文的示例默认以 Python3 为实现语言,用到 Python3 的 re 模块或 regex 库。基本的 Unicode 属性分类 {代码...} 基本的 Unicode 子属性Le...

qbit阅读 4.3k

Python + Sqlalchemy 对数据库的批量插入或更新(Upsert)
由于不同数据库对这种 upsert 的实现机制不同,Sqlalchemy 也就不再试图做一致性的封装了,而是提供了各自的方言 API,具体到 Mysql,就是给 insert statement ,增加了 on_duplicate_key_update 方法。

songofhawk1阅读 1.9k评论 4

封面图
Go for 循环有时候真的很坑。。。
大家好,我是煎鱼。不知道有多少 Go 的面试题和泄露,都和 for 循环有关。今天我在周末认真一看,发现了 redefining for loop variable semantics 。著名的硬核大佬 Russ Cox 表示他一直在研究这个问题,并表示十...

煎鱼阅读 3.4k

不秃顶、不猝死,顺顺利利活到100可以吗?

1 声望
2 粉丝
宣传栏