Python异步编程|PySimpleGUI界面读取PDF转换Excel

目录

实例要求

原始pdf文件格式

输出xls文件格式

运行界面

完整代码

代码分析

遍历表格

布局界面

控件简介

写入表格

表格排序

事件循环

异步编程


实例要求

使用PySimpleGUI做一个把单位考勤系统导出的pdf文件合并输出Excel的应用,故事出自:

https://hannyang.blog.csdn.net/article/details/135395946

当时时间紧,没有好好做界面且输出csv文件了事。今天趁周六休息,把代码做一下升级处理,使用库PySimpleGUI做了一个稍微漂亮一点的界面;又用pdfplumber直接遍历多个pdf文件,得到数据后输出Excel文件,比我原本先做合并pdf文件再去取数要快,原先的pdf文件合并操作纯粹有点多余。最后,又尝试对pdf文件读取函数的改造,使用了asyncio异步编程效果非常不错。

下面请听我慢慢道来:

原始pdf文件格式

输出xls文件格式

运行界面

完整代码

import xlwt, pyperclip, asyncio, pdfplumber
import os, time, datetime as dt
import PySimpleGUI as sg# 全局变量
table_head = '姓名,部门,应到,实到,出勤率,迟到次数,早退次数,加班(分钟)'
path, font = '', ('宋体',12)
date, data = [], []
DateFormat = '    .  . -    .  .  '
ErrMessage = '错误'
SortedType = ["出勤率排序","加班时长排序","迟到次数排序","早退次数排序"]# 定义布局
layout = [[sg.Text("昆山分行考勤表",font=('',16)),sg.Text(pad=(132,10)),sg.Text("请选择考勤文件:",font=font),sg.Input(key="-FOLDER-", enable_events=True, readonly=True,font=font,size=18),sg.FolderBrowse(button_text='...', enable_events=True, initial_folder='./')],[sg.Text("考勤日期:",font=font),sg.Text(DateFormat,key='-DATE-',font=font)],[sg.Table(values='',headings=table_head.split(','),key='-TABLE-',auto_size_columns=False,justification='left',num_rows=10)],[sg.Button("输出Excel文件",size=(12,1),pad=(15,30)),sg.Button(SortedType[0], enable_events=True,size=(10,1),pad=(15,30)),sg.Button(SortedType[1], enable_events=True,size=(10,1),pad=(15,30)),sg.Button(SortedType[2], enable_events=True,size=(10,1),pad=(15,30)),sg.Button(SortedType[3], enable_events=True,size=(10,1),pad=(15,30)),sg.Button("退出",size=(10,1),pad=(15,30))],[sg.StatusBar('',key="-BAR-",font=font,size=92)]
]# 读取pdf表格
async def read_table(file):dct = dict()with pdfplumber.open(file) as pdf:for page in pdf.pages:tables = page.extract_tables(table_settings = {})for table in tables:for lst in table:tmp = lst[1:]if not any(tmp): continuetmp = [tmp[0]]+tmp[3:8]+[tmp[-1]]tmp[0] = tmp[0].replace('\n','')tmp[0] = tmp[0].split('/')tmp[0] = tmp[0][-1]if lst[0]=='时间':dct[lst[0]] = tmp[0]else:dct[','.join([lst[0],tmp[0]])] = ','.join(tmp[1:])return dct# 写入xls文件
def write_sheet():global data, date, table_head, ErrMessageif ErrMessage[:2] in ('错误','文件'): returnmyxl = xlwt.Workbook()style = xlwt.easyxf('align: wrap yes; align: horiz center; font: bold yes;') sheet = myxl.add_sheet('考勤表')wcol = [20,40,60,30,30,40,40,40,60]for i,w in enumerate(wcol):sheet.col(i).width = w * 80sheet.write_merge(0,0,0,8,'出勤统计报表',style)style = xlwt.easyxf('borders:top thin; borders:bottom thin; borders:left thin; borders:right thin;') sheet.write_merge(1,1,0,2,'考勤日期:'+date[0])for i,head in enumerate(['序号']+table_head.split(',')):sheet.write(2,i,head,style)for i,row in enumerate(data):for j,col in enumerate([str(i+1)]+row):sheet.write(3+i,j,col,style)for i,t in enumerate(SortedType):if t in ErrMessage:tmp = SortedType[i]breakelse: tmp = ""excel_file = f'昆山分行考勤表{date[0]}({tmp}{strDateTime()}).xls'ErrMessage = f'文件输出为:{excel_file}'try:myxl.save(excel_file)except:ErrMessage = '写入excel文件失败!'finally:pyperclip.copy('\\'.join((os.getcwd(),excel_file)))window['-BAR-'].update(ErrMessage)# 获取当前时间
def strDateTime(diff=0):now = dt.datetime.now()time = now + dt.timedelta(days=diff)    return f'{time.year}{time.month:02}{time.day:02}{time.hour:02}{time.minute:02}{time.second:02}'# 选择并处理文件
async def on_text_changed(event, values):global date, data, path, ErrMessagenew_path = values["-FOLDER-"]window["-FOLDER-"].update(new_path.split('/')[-1])if path==new_path: returnelse: path = new_pathpdfs = [f for f in os.listdir(path) if f.endswith('.pdf') and not f.startswith('PDFmerged')]nums = len(pdfs)if nums==0:ErrMessage = '错误:所选文件夹中没有PDF文件!'window['-BAR-'].update(ErrMessage)window['-DATE-'].update(DateFormat)window['-TABLE-'].update(values=[])returndate, data, sheet = [], [], dict()tasks = []for pdf in pdfs:tasks.append(read_table('/'.join([path,pdf])))ErrMessage = f'文件读取中(共{nums}个PDF文件)......'window['-BAR-'].update(ErrMessage)window.refresh()results = await asyncio.gather(*tasks)for r in results:dt = r.get('时间',None)if dt: date.append(dt)sheet.update(r)if date:window['-DATE-'].update(date[-1])for k,v in sheet.items():if k in ('时间','姓名,所属组织','普通班个人出勤统计报表,'): continuedata.append(','.join([k,v]).split(','))window['-TABLE-'].update(values=data)persons = len(data)departments = len(set([d[1] for d in data]))if 0:#len(set(date))!=1:data = []ErrMessage = f'错误:请检查所选文件存在多个时间段:{",".join(set(date))}'else:ErrMessage = f'考勤人数:{persons} / 部门数:{departments}'window['-BAR-'].update(ErrMessage)# 表格排序
def on_table_sorted(event, data):global ErrMessageif not data: returnslist = ['x[-4][:-1]', 'x[-1]', 'x[-3]', 'x[-2]']style = slist[SortedType.index(event)]data = sorted(data, key=lambda x: float(eval(style)), reverse=True)window['-TABLE-'].update(values=data)ErrMessage = f'已按{event}更新!'window['-BAR-'].update(ErrMessage)# 创建窗口
window = sg.Window("考勤表汇总", layout, finalize=True)# 事件循环
while True:event, values = window.read()if event == sg.WINDOW_CLOSED or event == "退出":breakelif event == "-FOLDER-":asyncio.run(on_text_changed(event, values))elif event in SortedType:on_table_sorted(event, data)elif event == "输出Excel文件":write_sheet()# 关闭窗口
window.close()

代码分析

重点代码都用彩色字体加粗标注了:

遍历表格

读取代码如下:

import pdfplumber

......
    with pdfplumber.open(file) as pdf:
        for page in pdf.pages:
            tables = page.extract_tables(table_settings = {})
            for table in tables:
                for lst in table:
                    # 根据表格实际情况来清洗数据
    return dct

布局界面

import PySimpleGUI as pg

layout = [
    [sg.Text("昆山分行考勤表",font=('',16)),
     sg.Text(pad=(132,10)),
     sg.Text("请选择考勤文件:",font=font),
     sg.Input(key="-FOLDER-", enable_events=True, readonly=True,font=font,size=18),
     sg.FolderBrowse(button_text='...', enable_events=True, initial_folder='./')
     ],
    [sg.Text("考勤日期:",font=font),
     sg.Text(DateFormat,key='-DATE-',font=font)
     ],
    [sg.Table(values='',
              headings=table_head.split(','),
              key='-TABLE-',
              auto_size_columns=False,
              justification='left',
              num_rows=10)],
    [sg.Button("输出Excel文件",size=(12,1),pad=(15,30)),
     sg.Button(SortedType[0], enable_events=True,size=(10,1),pad=(15,30)),
     sg.Button(SortedType[1], enable_events=True,size=(10,1),pad=(15,30)),
     sg.Button(SortedType[2], enable_events=True,size=(10,1),pad=(15,30)),
     sg.Button(SortedType[3], enable_events=True,size=(10,1),pad=(15,30)),
     sg.Button("退出",size=(10,1),pad=(15,30))],
    [sg.StatusBar('',key="-BAR-",font=font,size=92)]]

控件简介

除了最常用的Text, Input, Button,使用了 FolderBrowse、Table、StatsBar 三个不是最常用的控件,分别是文件夹打开框、表格和状态栏。

表格最重要的三个参数: values, headings, auto_size_columns

sg.Table(values='', headings=table_head.split(','), auto_size_columns=False)

表格数据values和表头headings都列表(分别是二维和一维的),auto_size_columns=False建议不要缺省,否则列宽不可控,各列都自动缩进紧靠在一起。

表格更新数据的方法:window['-TABLE-'].update(values=data)

写入表格

import xlwt

def write_sheet():
    global data, date, table_head, ErrMessage
    if ErrMessage[:2] in ('错误','输出'): return
    myxl = xlwt.Workbook()
    style = xlwt.easyxf('align: wrap yes; align: horiz center; font: bold yes;') 
    sheet = myxl.add_sheet('考勤表')
    wcol = [20,40,60,30,30,40,40,40,60]
    for i,w in enumerate(wcol):
        sheet.col(i).width = w * 80
    sheet.write_merge(0,0,0,8,'出勤统计报表',style)
    style = xlwt.easyxf('borders:top thin; borders:bottom thin; borders:left thin; borders:right thin;') 
    sheet.write_merge(1,1,0,2,'考勤日期:'+date[0])
    for i,head in enumerate(['序号']+table_head.split(',')):
        sheet.write(2,i,head,style)
    for i,row in enumerate(data):
        for j,col in enumerate([str(i+1)]+row):
            sheet.write(3+i,j,col,style)
    for i,t in enumerate(SortedType):
        if t in ErrMessage:
            tmp = SortedType[i]
            break
    else: tmp = ""
    excel_file = f'昆山分行考勤表{date[0]}({tmp}{strDateTime()}).xls'
    ErrMessage = f'输出文件为:{excel_file}'
    try:
        myxl.save(excel_file)
    except:
        ErrMessage = '写入excel文件失败!'

注意单格和多个单元格的写入区别: sheet.write()  sheet.write_merge()

表格排序

SortedType = ["出勤率排序","加班时长排序","迟到次数排序","早退次数排序"]
def on_table_sorted(event, data):
    global ErrMessage
    if not data: return
    slist = ['x[-4][:-1]', 'x[-1]', 'x[-3]', 'x[-2]']
    style = slist[SortedType.index(event)]
    data = sorted(data, key=lambda x: float(eval(style)), reverse=True)
    window['-TABLE-'].update(values=data)
    ErrMessage = f'已按{event}更新!'
    window['-BAR-'].update(ErrMessage)

虽然经常有人诟病eval()函数的安全性,但这里还是用eval()简化表格排序事件,否则要多写很多代码。

事件循环

while True:
    event, values = window.read()
    if event == sg.WINDOW_CLOSED or event == "退出":
        break
    elif event == "-FOLDER-":
        asyncio.run(on_text_changed(event, values))
    elif event in SortedType:
        on_table_sorted(event, data)
    elif event == "输出Excel文件":
        write_sheet()

异步编程

此时,请出本篇的主角“异步编程”,什么是异步编程呢?就是有点多任务操作的意思。

异步编程是一种编程范式,它允许某些操作在等待结果时不阻塞整个程序。在传统的同步编程中,程序会按照顺序执行,一旦遇到需要等待的操作(如文件I/O或网络请求),整个程序就会被阻塞,等待操作完成。而在异步编程中,程序并不会因为某个耗时的IO操作而停下其他所有任务,而是将这个任务交给系统处理,自身继续执行后续的操作,等到IO操作完成后,系统会通知程序进行下一步的处理。

asyncio

在上一段代码中,响应"-FOLDER-"时使用了asyncio.run()函数:

import asyncio
....... ......

while True:
    event, values = window.read()
    if event == sg.WINDOW_CLOSED or event == "退出":
        break
    elif event == "-FOLDER-":
        asyncio.run(on_text_changed(event, values))

asyncio.run运行的这个是异步编程的主函数,需要用async def来定义:

async def

async def on_text_changed(event, values):
    ......其它代码略......
    tasks = []
    for pdf in pdfs:
        tasks.append(read_table('/'.join([path,pdf])))
    ErrMessage = f'文件读取中(共{nums}个PDF文件)......'
    window['-BAR-'].update(ErrMessage)
    window.refresh()
    results = await asyncio.gather(*tasks)
    for r in results:
       ......遍历取回的被调异步函数返回值的列表......

await

异步主函数中使用 await asyncio.gather(*tasks) 取回被函数的返回结果,返回结果是多个任务的返回值组成的列表;而主函数的任务呢就,是被调函数组成的列表:asks.append(read_table())

同样的,被调函数也需要用async def来定义,它一般都是文件I/O或网络请求等比较耗时的操作:

async def read_table(file):
    dct = dict()
    with pdfplumber.open(file) as pdf:
        # 读取pdf文件 I/O操作
    return dct


源码和2个例表已绑定上传资源,欢迎下载测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/236577.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Playwright 结合 Selenium Grid - 1.windows 环境使用教程

Playwright 可以连接到运行 Selenium 4 的 Selenium Grid Hub 来启动 Google Chrome 或 Microsoft Edge 浏览器,而不是在本地机器上运行浏览器。 下载Selenium Grid 打开selenium官方https://www.selenium.dev/downloads/下载Selenium Server (Grid) 目前最新版本4.16.1 下…

FPGA高端项目:12G-SDI 视频编解码,提供工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的 GT 高速接口解决方案我目前已有的SDI编解码方案 3、详细设计方案设计框图UltraScale GTH 的SDI模式应用UltraScale GTH 基本结构参考时钟的选择和分配UltraScale GTH 发送和接收处理流程UltraScale GTH 发送接口UltraScale G…

73应急响应-Web分析phpjavaWeb自动化工具

我感觉学完渗透自然就会应急响应,之前又发过应急响应的文章 应急响应笔记就开始比较潦草 应急响应基础知识 应急响应流程 保护阶段(断网,避免继续渗透;备份),分析阶段(分析攻击行为&#xf…

C++|44.智能指针

文章目录 智能指针unique_ptr特点一——无法进行复制 shared_ptr特点一——可复制特点二——计数器(用于确定删除的时机) 其他 智能指针 通常的指针是需要特殊地去申请对应的空间,并在不使用的时候还需要人工去销毁。 而智能指针相对普通的指…

用模方软件进行模型的透明贴图,为什么翻出来透明部分是黑的?

答:透贴需要用PNG格式。 模方是一款针对实景三维模型的冗余碎片、水面残缺、道路不平、标牌破损、纹理拉伸模糊等共性问题研发的实景三维模型修复编辑软件。模方4.1新增自动单体化建模功能,支持一键自动提取房屋结构,平均1栋复杂建筑物只需3…

了解ASP.NET Core 中的文件提供程序

写在前面 ASP.NET Core 通过文件提供程序来抽象化文件系统访问。分为物理文件提供程序(PhysicalFileProvider)和清单嵌入的文件提供程序(ManifestEmbeddedFileProvider)还有复合文件提供程序(CompositeFileProvider );其中PhysicalFileProvider 提供对物理文件系统…

1panel中的sftpgo webadmin 更新修改docker容器文件的配置教程

本篇文章主要讲解1panel中的sftpgo webadmin 更新修改docker容器文件的配置教程,适合sftpgo webadmin和1panel系统用户配置时使用。 作者:任聪聪 rccblogs.com 日期:2024年1月8日 sftpgo是无法直接直接更改容器内部的网站目录的,但…

uniapp 如何使用echarts 以及解决tooltip自定义不生效问题

使用的是echarts-for-wx插件&#xff1b; 正常写法案例&#xff1a;给tooltip数值加个% <template><view><uni-ec-canvas class"uni-ec-canvas"id"uni-ec-canvas"ref"canvas"canvas-id"uni-ec-canvas":ec"ec&quo…

使用nginx+HTML2canvas将任意html网页转为png图片自定义张数

文章目录 概述网页的转换html2canvas的使用导入导入HTML2canvas库函数定义 nginx部署编写控制截图网页代码iframe 网页控制代码 测试说明 概述 本文简述如何使用nginxhtml2canvas将任意网页html转为png图片 网页的转换 如果是本地网页&#xff0c;直接进行nginx反向代理就行…

vue3dLoader Cannot read properties of null (reading ‘setCrossOrigin‘)“这个报错怎么解决?

默认情况下crossOrigin默认值是“anonymous” 如果出现报错的情况 请设置crossOrigin为空字符串即可。如&#xff1a; <vue3dLoader crossOrigin""> 相关阅读 推荐&#xff1a;vue-3d-loader支持.dae/.fbx/.gltf/.glb/.obj/.ply/.stl/.json&#xff0c;并支…

5G前装搭载率即将迈过10%大关,车载通讯进入多层次增长通道

对于智能化来说&#xff0c;车载通讯性能的提升&#xff0c;对于相关功能的用户体验优化、进一步减少通讯时延以及打开应用新空间&#xff0c;至关重要。 目前&#xff0c;2G/3G正在进入运营商逐步关闭运营的阶段&#xff0c;4G依然是主力&#xff0c;但5G也在迎来新的增长机会…

vue3 生命周期

与 2.x 版本生命周期相对应的组合式 API beforeCreate -> 使用 setup() created -> 使用 setup() beforeMount -> onBeforeMount mounted -> onMounted beforeUpdate -> onBeforeUpdate updated -> onUpdated beforeDestroy -> onBeforeUnmount destroye…

Fedora Linux 中安装 nginx

Fedora 35 中安装 nginx 的方法非常简单。 运行下面的命令&#xff1a; sudo dnf install nginx 在提示你需要确认的地方&#xff0c;输入 y 后回车即可。 开机自动启动 如果你希望在你的操作系统重启的时候自动启动 nginx&#xff0c;请输入下面的命令&#xff1a; syst…

百度吉利合作造车生态,极越“智价比”能否带来科技平权?

文|AUTO芯球 作者|文泽 临近年关&#xff0c;车企迎来“降价潮”。为了获得更好的年终成绩单&#xff0c;包括上汽大众、比亚迪、长安汽车、智己汽车等20多家品牌推出了购车补贴、限时优惠等措施&#xff0c;优惠幅度最高近20万元。 在此背景下&#xff0c;新车发布一个多月…

双指针问题——求只包含两个元素的最长连续子序列(子数组)

一&#xff0c;题目描述 你正在探访一家农场&#xff0c;农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示&#xff0c;其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多地收集水果。然而&#xff0c;农场的主人设定了一些严格的规矩&#xff0c;你必…

AOT-GAN-for-Inpainting项目解读|使用AOT-GAN进行图像修复

项目地址&#xff1a; https://github.com/researchmm/AOT-GAN-for-Inpainting 基于pytorch实现 论文地址&#xff1a; https://arxiv.org/abs/2104.01431 开源时间&#xff1a; 2021年 项目简介&#xff1a; AOT-GAN-for-Inpainting是一个开源的图像修复项目&#xff0c;其对 …

蚂蚁爱购--靠谱的SpringBoot项目

简介 这是一个靠谱的SpringBoot项目实战&#xff0c;名字叫蚂蚁爱购。从零开发项目&#xff0c;视频加文档&#xff0c;十天就能学会开发JavaWeb项目。 教程路线是&#xff1a;搭建环境> 安装软件> 创建项目> 添加依赖和配置> 通过表生成代码> 编写Java代码&g…

图像融合论文阅读:CrossFuse: 一种基于交叉注意机制的红外与可见光图像融合方法

article{li2024crossfuse, title{CrossFuse: A novel cross attention mechanism based infrared and visible image fusion approach}, author{Li, Hui and Wu, Xiao-Jun}, journal{Information Fusion}, volume{103}, pages{102147}, year{2024}, publisher{Elsevier} } 论文…

redis stream restTemplate消息监听队列框架搭建

整体思路 1. pom增加redis依赖&#xff1b; 2. 消息监听器&#xff0c;实现StreamListener接口&#xff0c;处理消息到达逻辑&#xff1b; 3. 将消息订阅bean及监听器注册到配置中&#xff1b; 1. pom <?xml version"1.0" encoding"UTF-8"?> <…

Go并发快速入门:Goroutine

Go并发&#xff1a;Goroutine 1.并发基础概念&#xff1a;进程、线程、协程 (1) 进程 可以比作食材加工的一系列动作 进程就是程序在操作系统中的一次执行过程&#xff0c;是由系统进行资源分配和调度的基本单位&#xff0c;进程是一个动态概念&#xff0c;是程序在执行过程…