基于PyQt5+Python实现Excel内容对比

Excel内容对比

记录一下最近在研究的一个程序,如何实现两个Excel表格之间的内容对比。之前做出来的版本1,版本2都存在较大的逻辑问题,使用的openpyxl第三方库,没有透彻理解到openpyxl的特性,导致程序无法对比多数据量多的文件;在一些不对齐表格的对比上也出现了一些严重问题,出现IndexError、ValueError等一些bug。现在改进了效率,也修复了处理主副文件行数不同列数不同的问题

使用环境:Python3.8 + openpyxl 3.0.9 + PyQt5 5.15.4 + Pandas 1.3.5

界面GUI
基于PyQt5+Python实现Excel内容对比_第1张图片

使用到的一些较核心代码

数据处理

pandas处理DataFrame

def fill_rows(df1, df2, df1_rows, df2_rows):
    """
    解决 dataframe行数不一的问题
    :param df1: 主文件读取的dataframe
    :param df2: 副文件读取的dataframe
    :param df1_rows: 主文件的最大行数
    :param df2_rows: 副文件的最大行数
    :return: 处理完的df1,df2
    """
    df1_cols, df2_cols = df1.shape[1], df2.shape[1]
    if df1_rows > df2_rows:
        n = df1_rows - df2_rows  # 获取缺失行数
        for i in range(n):
            df2.loc[i + 2] = ["~\\" for i in range(df2_cols)] # 列表推导式补全
        return df1, df2
    elif df1_rows < df2_rows:
        n = df2_rows - df1_rows
        for i in range(n):
            df1.loc[i + 2] = ["~\\" for i in range(df1_cols)]
        return df1, df2
    else:
        return df1, df2

效果:
基于PyQt5+Python实现Excel内容对比_第2张图片
处理DataFrame具体行中的列数问题

def fill_cols(lst1, lst2):
    """
    处理读取的列数据,长度不一问题
    :param lst1: 主文件读取的一行数据
    :param lst2: 副文件读取的一行数据
    :return: 
    	lst1:处理后的lst1
    	lst2:处理后的lst2
    	len1/len2:返回最大长度
    """
    len1 = len(lst1)
    len2 = len(lst2)
    if len1 > len2:
        for i in range(len1 - len2):
            lst2.append("~\\")  # 填充特殊字符
        return lst1, lst2, len1
    elif len1 < len2:
        for i in range(len2 - len1):
            lst1.append("~\\")
        return lst1, lst2, len2
    else:
        return lst1, lst2, len1

在最后设置单元格背景时,如果文件打开会产生 PermissionError异常,因此读取数据的时候需要判断文件是否被打开

def check_PermissionError(path):
    """
    通过文件是否可以打开 判断文件是否打开
    :param path: 文件路径
    :return: None
    """
    try:
        f = open(path, "a")
    except OSError as e:
        return False
    else:
        f.close()
        return True

获取单元格范围内的所有单元格坐标

为什么要获取合并单元格范围?在最后获取到 fill_cell中,如果包含合并单元格,在设置背景颜色时不出现效果,
因此需要获取此异同单元格所在的合并单元格范围,然后将整个范围都设置背景颜色
def dudge_merged(cell):
    """
    返回某个合并范围内的所有单元格
    :param cell:合并范围
    :return: cells_list:拆解的合并单元格坐标
    """
	# 百度中提及的判断单元格是否为合并单元格,未实现效果
    # if type(cell).__name__ == 'MergedCell':
    #     print("True")

    cells_list = []  # 存储单元格坐标

    lst = cell.split(':')
    start, end = lst[0], lst[-1]  # 获取头字母,尾字母
    start_letter, start_number = ord(start[0]), int(start[1:])  # 转换为ASCII码
    end_letter, end_number = ord(end[0]), int(end[1:])  # 获取头数字,尾数字
    if start_letter == end_letter:  # 判断是否为横向合并
        for i in range(end_number - start_number + 1):  # 根据头尾数字差 获取列数
            cells_list.append(chr(start_letter) + str(start_number + i))
    else:
        col = column_index_from_string(end[0]) - column_index_from_string(start[0]) + 1  # 计算列数
        row = end_number - start_number + 1  # 计算行数
        col_list = [chr(start_letter + i) for i in range(col)]  # 生成范围内的所有列字母
        row_list = [start_number + i for i in range(row)]  # 生成范围内的所有行号
        for c in col_list:
            for r in row_list:
                cells_list.append(c + str(r))
    return cells_list

def get_merage_list(merage):  # 与dudge_merged()配合使用
    """
    返回所有合并单元格范围内的所有单元格
    :param merage: 合并单元格合集
    :return: 扩充的单元格

    get_merage_list('A1:A3') --> return: [['A1','A2','A3']]
    get_merage_list('A1:B2') --> return: [['A1','A2','B1','B2']]
    """
    merged_cells = []
    if len(merage) > 0:
        for range_cell in merage:
            values = dudge_merged(str(range_cell))
            merged_cells.append(values)
    else:
        values = dudge_merged(str(merage[0]))
        merged_cells.append(values)
    return merged_cells

设置单元格背景颜色

def set_cellcolor(wb, sheet, fill_list, path1, path2, fgColor="FFFF0000"):
    """
    设置一组单元格背景颜色
    :param wb: openpyxl.workbook对象
    :param sheet:Worksheet 对象
    :param fill_list:填充单元格集
    :param path1:主文件路径
    :param path2:副文件路径
    :param fgColor:背景颜色
    :return:None
    """
    fill = PatternFill("solid", fgColor=fgColor)  # 设置样式
    for cell in fill_list:
        sheet[cell].fill = fill  # 遍历集合填充
    if check_PermissionError(path1) and check_PermissionError(path2):  # 判断是否打开
        wb.save(path1)  # 保存文件


def set_acellcolor(wb, sheet, cell, path1, path2, fgColor="FFFF0000"):
    """
    设置单个单元格背景颜色
    :param wb: openpyxl.workbook对象
    :param sheet:Worksheet 对象
    :param cell:准备填充的单个单元格
    :param path1:主文件路径
    :param path2:副文件路径
    :param fgColor:背景颜色
    :return:None
    """
    fill = PatternFill("solid", fgColor=fgColor)  # 设置样式
    sheet[cell].fill = fill
    if check_PermissionError(path1) and check_PermissionError(path2):  # 判断是否打开
        wb.save(path1)  # 保存文件

判断文件内容读取判断

def judege_cols(self, master_cols, other_cols, max_rows, wf1, wf2):
    """
    判断文件内容读取判断
    :param master_cols: 主文件列数
    :param other_cols: 副文件列数
    :param max_rows: 最大列数
    :param wf1: 主文件对象
    :param wf2: 副文件对象
    :return: 异同单元格合集
    """
    fell_list = []  # 异同单元格合集
    for i in range(max_rows):
		
		# 逐行读取数据;如果行数不一会,短的一方会报IndexError
        master_lst, other_lst = [], []
        try: 
            master_lst = list(wf1.values[i])
        except IndexError:
            master_lst = (["~\\" for i in range(other_cols)])
        try:
            other_lst = list(wf2.values[i])
        except IndexError:
            other_lst = (["~\\" for i in range(master_cols)])
        
        # 如果主文件读取的一行与副文件读取的一行,长度不一,则需要补齐,不然会报IndexError
        if len(master_lst) != len(other_lst):
            master_lst, other_lst, lenl = WtoolsFuc.fill_cols(master_lst, other_lst) # 补齐列
            if not eq(master_lst, other_lst): # 具体判断内容
                for n in range(lenl):
                    if not eq(master_lst[n], other_lst[n]):
                        fell_list.append(get_column_letter(n + 1) + str(i + 1)) # 返回坐标
    return fell_list

判断异同单元格是否为合并

"""
判断异同单元格的是否为合并单元格
falsecell_count: 异同单元格数量
merage:全文件中所有单元格合集
merage_count:合并单元格数量
"""
if falsecell_count > 0:
    if len(merage) > 0:
        merged_cells = WtoolsFuc.get_merage_list(merage)  # 返回文件中合并单元格的具体范围
        for merged_cell in merged_cells:
            for fi_cell in fill_cell:
                if fi_cell in merged_cell:
                    merage_count += 1
                    WtoolsFuc.set_cellcolor(master_wb, master_sheet, merged_cell, path1, path2,
                                            fgColor="1874CD") # 异同单元格设置为蓝色
                else:
                    WtoolsFuc.set_acellcolor(master_wb, master_sheet, fi_cell, path1, path2)
    else:
        WtoolsFuc.set_cellcolor(master_wb, master_sheet, fill_cell, path1, path2)

窗体部分

实现button点击打开文件

def open_master_file(self):
    """
    打开主文件
    :return: None
    QFileDialog.getOpenFileName(self, 文件选择框标题, 打开的默认路径, 过滤文件后缀)
    返回:('C:/Users/Meaauf/Desktop/p7.xlsx', 'Excel Files(*.xlsx)')元组
    """
    self.lineEdit.setText(QFileDialog.getOpenFileName(self, "选择主文件", "", "Excel Files(*.xlsx)")[0])

设置下拉列表框

def master_combobox(self):
    """
    设置下拉列表框值
    :return: None
    """
    if self.comboBox.count() > 0:
        self.comboBox.clear()
    wb = openpyxl.load_workbook(self.lineEdit.text(), read_only=True)
    self.comboBox.addItems(wb.sheetnames)
Pyinstaller打包命令

-w:不使用窗口打开,如果不添加-w,会在运行程序时打开控制台,显示print信息
-p:python会指定寻找路径下的第三方库打包
-i:设置exe可执行文件的图标
【在打包后,会在目录下显示一个dist文件夹,将图标文件以及使用的一些图片资源都放在里面】

pyinstaller -w -F -p C:\Users\Meaauf\AppData\Local\Programs\Python\Python38\Lib\site-packages  -i favicon.ico Wtools.py WtoolsFuc.py

基于PyQt5+Python实现Excel内容对比_第3张图片
基于PyQt5+Python实现Excel内容对比_第4张图片
基于PyQt5+Python实现Excel内容对比_第5张图片

相关源码及程序

GitHub:  https://github.com/Meaauff/Wtools.git
CSDN :  https://download.csdn.net/download/weixin_45564816/77696529

你可能感兴趣的:(PyQt5,Python,python,qt,开发语言)