实验室电池参数一键统计工具开发

背景

实验室测试电池时,每块电池都会生成一个txt文件,包含参数。如图所示:

我们想把每一块电池的电压、电流、填充、效率都输入到一个Excel表格中,如下图所示:

实现

用python遍历每一个txt文件,识别参数并输出到Excel表格中,以此思路做成第一版软件:(2020.11.18)

#! python3
"""
------Ke软件电池数据自动填充程序V1.1-------20201026------
------功能说明------
将同一文件夹下的所有测试结果的txt文件名以及对应的电池参数Voc、Jsc、FF、Eff四个参数填充到excel表格中。
------此版本存在的缺陷------
1、嵌套字典未能派上用场,在excel赋值中无法使用。
2、excel表格中的数据皆为文本格式。
3、缺少单元格大小、对齐等美化处理
4、Excel命名加入当前文件夹名字。
"""

import re, os, pyperclip, pprint, openpyxl, sys

# TODO: 建立待寻找参数的正则表达式。中文冒号可以直接匹配。名称、数字部分、单位分成三组。
VocRegex = re.compile(r'(Voc:)([\d\.]{2,})(V)')
JscRegex = re.compile(r'(Jsc:)([\d\.]{2,})(mA/cm\^2)')
FFRegex = re.compile(r'(FF:)([\d\.]{2,})(%)')
EffRegex = re.compile(r'(Eff:)([\d\.]{2,})(%)')
# 用一个嵌套字典存放所有数据,父字典的key是文件名,value是子字典;子字典的key是参数名,value
dataDic = {}

#------V1.2 新增命令行直接输入文件夹路径功能--------------------------
if len(sys.argv) < 1 or len(sys.argv) > 3:
    print("Usage: enter 'finder' + the path of dic." )
else:
    path = sys.argv[1]
#----------------------------------------------------------------

#path = r'F:\0TrueForce\KEEP WORKING\data.dll\JV-DATA\20201025-TB3F'
# os.listdir 遍历文件夹,返回包含所有文件名的列表
pathString = os.listdir(path)
# 统计所有txt文件个数
count = 0

# TODO 遍历文件夹中的txt数据文件,查找参数保存到字典中
for filename in pathString:
    if os.path.exists(os.path.join(path, filename)) and filename.endswith('.txt'):  # 存在并且是txt文件
        testResult = open(os.path.join(path, filename), encoding='gb18030', errors='ignore')
        data = testResult.read()  # 用read函数将文本文件读取为一个字符串
        testResult.close()
        if VocRegex.search(data):
            Voc = VocRegex.search(data).group(2)
        else:
            Voc = ''
        if JscRegex.search(data):
            Jsc = JscRegex.search(data).group(2)
        else:
            Jsc = ''
        if FFRegex.search(data):
            FF = FFRegex.search(data).group(2)
        else:
            FF = ''
        if EffRegex.search(data):
            Eff = EffRegex.search(data).group(2)
        else:
            Eff = ''
        # 字典赋值
        if filename not in dataDic:
            dataDic.setdefault(filename, 0)
            dataDic[filename] = {'Voc':Voc,'Jsc':Jsc,'FF':FF,'Eff':Eff}
        else:
            continue
    else:
        continue
    count += 1

print('所有电池统计数据如下:')
print(pprint.pformat(dataDic))
print('统计的文件个数为:')
print(count)
print('电池测定数据已复制至剪切板。')
pyperclip.copy(pprint.pformat(dataDic))

# TODO: 创建并加载一个excel文件,更改工作表名
os.chdir(path)
excelName = 'cellStats.xlsx'
wb = openpyxl.Workbook()
wb.save(excelName)
wb = openpyxl.load_workbook(excelName)
sheet = wb.active
sheet.title = 'OriginalStats'

# TODO: 输入表头
titles = ['cell_num','Voc','Jsc','FF','Eff']
for i in range(0,5):
    sheet.cell(row = 1, column = i+1).value = titles[i]
wb.save(excelName)

# TODO: 遍历字典和单元格, 输入数据
# 存疑;如果直接遍历父字典的键,对单元格进行赋值,则所有单元格全是最后一个文件名,怎么解决?(此处把文件名抠出来为一个列表解决了赋值问题)
filename = []
Voclist = []
Jsclist = []
FFlist = []
Efflist = []

for key in dataDic.keys():
    filename.append(key)

for para in dataDic.values():
    Voclist.append(para['Voc'])
    Jsclist.append(para['Jsc'])
    FFlist.append(para['FF'])
    Efflist.append(para['Eff'])

for row in range(2, count+2):
    sheet.cell(row=row, column=1).value = filename[row - 2].rstrip('.txt')
    sheet.cell(row=row, column=2).value = Voclist[row - 2]
    sheet.cell(row=row, column=3).value = Jsclist[row - 2]
    sheet.cell(row=row, column=4).value = FFlist[row - 2]
    sheet.cell(row=row, column=5).value = Efflist[row - 2]
wb.save(excelName)
print('电池测定数据已输出至: ' + path + ' 下,文件名为:cellStats.xlsx')

第一版软件存在问题:

1、嵌套字典未能派上用场,在excel赋值中无法使用。
2、excel表格中的数据皆为文本格式。
3、Excel未有规范命名。

4、代码结构混乱,未能模块化

5、没有可视化操作界面

对问题进行改进,优化代码结构,使用嵌套字典存储数据,使用面向对象方法模块化结构,增加可视化界面(2021.10.5)

参考:8000字 | 详解 Tkinter 的 GUI 界面制作! (qq.com)

#! python3
"""
------Ke软件电池数据自动填充程序V4.0------
------功能说明------
将同一文件夹下的所有测试结果的txt文件名以及对应的电池参数Voc、Jsc、FF、Eff四个参数填充到excel表格中。
------V2.0版本改进功能------
1、创建的文件名自动加上当天时间
2、输入的参数已经转换为float格式
(参数识别出来是string,要转换为float要在正则识别出数字之后转换,如果把含数字和空值的放在一起转换,空值无法转换为float,会报错)
3、改进了之前用参数列表赋值的方法,用嵌套字典直接对excel表格进行赋值,降低了空间复杂度。
(用嵌套字典进行赋值时,内部不用循环row,改成row在循环内自增,即可避免循环结束后表格中全都是最后一个文件的数据)
4、将输出数据按照文件名进行排序
(对字典按照键进行排序,用sorted+lambda会将字典变为元组,需要用强制类型转换 dict 将元组变回字典)
------V3.0版本改进功能------
1、添加tk可视化窗口,显示输入文件路径的文本框和一键提取的按钮
------V4.0版本改进功能------
1、优化tk可视化窗口外观
2、添加使用说明
3、重构代码,采用面向对象模块化编程
------此版本存在的缺陷------
1、缺少单元格大小、对齐等美化处理
2、窗口仅满足了基本功能,缺乏设计性
3、未能考虑到错误条件和异常跳出的处理
4、程序结构有问题,下一版本应该用面向对象方法来实现

exe文件组装命令: pyinstaller -F FindertxtPortableV4.py -w
-w 作用是打开exe文件时不弹出命令行窗口
"""

import re  # 正则表达式模块
import os  # 文件夹操作模块
import datetime  # 时间模块
import openpyxl  # excel操作模块
from collections import defaultdict  # 默认字典
# tk窗口模块
from tkinter import *
from tkinter.filedialog import askdirectory
import tkinter.font as tkFont
import tkinter.messagebox

class MY_GUI():
    def __init__(self, windowName):  # 构造函数初始化窗口 和 文件路径
        self.windowName = windowName  # 传入一个窗口对象
        self.path = StringVar()  # 设个函数定义的变量会在entry中输入,一直追踪,可用get方法获取

    def selectPath(self):  # 实现用户选择路径
        path_ = askdirectory()  # 后面加下划线是为了避免与python的自带变量冲突
        self.path.set(path_)  # 在这里更新对象的 路径 变量path

    def error(self):  # 显示错误弹窗
        tkinter.messagebox.showinfo(title="提示", message="未选择路径")

    def setWindow(self):
        # 定制窗口的外观
        self.windowName["bg"] = "LightBlue"  # 窗口背景色
        self.windowName.attributes("-alpha", 0.95)  # 设置窗口不透明度 值越小透明度越高
        self.windowName.title("电池参数快捷统计工具")  # 窗口标题栏
        self.windowName.geometry('450x400+600+300')  # 窗口大小,350x350是窗口的大小,+600是距离左边距的距离,+300是距离上边距的距离
        #self.windowName.resizable(width=FALSE, height=FALSE)  # 拒绝用户调整窗口大小
        # 定制窗口的标签 xy控制标签的位置
        f = tkFont.Font(family='Microsoft YaHei UI', size=9, weight='bold')
        text = Text(self.windowName, width=53, height=6, bg="LightBlue", fg="Black", font=f)
        text.place(x=0, y=10)
        #text.pack()  # 加了pack 文本框就会跑到中间去
        text.insert(INSERT, "使用说明:\n")
        text.insert(INSERT, "1、将Ke2400S测试软件生成的txt文件放到同一文件夹内\n")
        text.insert(INSERT, "2、点击“选择电池文件夹”,选择该路径\n")
        text.insert(INSERT, "3、点击“一键导出”\n")
        text.insert(END, "4、程序将参数提取至该文件夹下自动生成的Excel表内\n")

        f1 = tkFont.Font(family='Microsoft YaHei UI', size=15, weight='bold')
        Button(self.windowName, text="选 择 电 池 文 件 夹",font=f1,command=self.selectPath,
               bg="Ivory").place(width=200,height=50,x=90, y=130)  # 调用选择路径的函数

        #f3 = tkFont.Font(family='Microsoft YaHei UI', size=12)
        f2 = tkFont.Font(family='Microsoft YaHei UI', size=10, weight='bold')
        Label(self.windowName, text="当前路径:",font=f2,bg="LightBlue", fg="DimGray").place(x=23, y=218)
        Entry(self.windowName, textvariable=self.path).place(width=200,height=25, x=108, y=215)  # 输入控件 显示文本内容。此处显示 路径属性 path

        f3 = tkFont.Font(family='Microsoft YaHei UI', size=15, weight='bold')
        Button(self.windowName, text="一 键 导 出",font=f3,
               bg="Ivory", command=self.option).place(width=200,height=50,x=90,y=273)  # 调用执行函数

        f4 = tkFont.Font(family='Microsoft YaHei UI', size=10, weight='bold')
        Label(self.windowName, text="designed by HangXu", font=f4,
              bg="LightBlue", fg="DimGray").place(x=20, y=350)

    def option(self):
        if self.path.get() == "":  # 文件路径为空时,报错
            self.error()
        else:  # 正确时,执行数据处理函数
            self.processPara(self.path.get())

    def getFileNums(self, inputPath):  # 统计该路径下的txt文件个数
        count = 0
        for filename in os.listdir(inputPath):
            if filename.endswith('.txt'):
                count += 1
        return count

    def processPara(self, inputPath):  # 主逻辑 处理数据
        # TODO: 建立待寻找参数的正则表达式。中文冒号可以直接匹配。名称、数字部分、单位分成三组。re.compile生成正则对象。后面再用search
        VocRegex = re.compile(r'(Voc:)([\d\.]{2,})(V)')
        JscRegex = re.compile(r'(Jsc:)([\d\.]{2,})(mA/cm\^2)')
        FFRegex = re.compile(r'(FF:)([\d\.]{2,})(%)')
        EffRegex = re.compile(r'(Eff:)([\d\.]{2,})(%)')

        # TODO: 遍历文件夹中的txt数据文件,查找参数保存到字典中
        dataDic = defaultdict(dict)   # 用一个嵌套字典存放所有数据,父字典的key是文件名,value是子字典;子字典的key是参数名,value
        for filename in os.listdir(inputPath):  # os.listdir(路径) 返回一个列表,包含该路径下所有文件名
            if os.path.exists(os.path.join(inputPath, filename)) and filename.endswith('.txt'):  # 存在并且是txt文件
                testResult = open(os.path.join(inputPath, filename), encoding='gb18030', errors='ignore')
                # data是读取的文件参数数据
                data = testResult.read()
                testResult.close()
                if VocRegex.search(data): # search匹配data中第一个匹配到的
                    Voc = float(VocRegex.search(data).group(2)) # 正则第二组是数据
                else:
                    Voc = ''  # 匹配不到就置空
                if JscRegex.search(data):
                    Jsc = float(JscRegex.search(data).group(2))
                else:
                    Jsc = ''
                if FFRegex.search(data):
                    FF = float(FFRegex.search(data).group(2))
                else:
                    FF = ''
                if EffRegex.search(data):
                    Eff = float(EffRegex.search(data).group(2))
                else:
                    Eff = ''
                dataDic[filename] = {'Voc':Voc, 'Jsc':Jsc, 'FF':FF, 'Eff':Eff}  # 字典赋值

        # TODO: 创建并加载一个excel文件,更改工作表名
        nowTime = datetime.datetime.now().strftime('%Y-%m-%d')  # 获取当前日期
        os.chdir(inputPath)
        excelName = 'cellStats-' + nowTime + '.xlsx'  # 定义excel表格文件名
        wb = openpyxl.Workbook()
        wb.save(excelName)
        wb = openpyxl.load_workbook(excelName)
        sheet = wb.active  # 调用当前工作簿,用sheet.方法;保存工作表用wb.save
        sheet.title = "电池参数"  # 更改工作簿名

        # TODO: 输入表头
        titles = ['cell_num', 'Voc', 'Jsc', 'FF', 'Eff']
        for i in range(0, 5):
            sheet.cell(row=1, column=i+1).value = titles[i]
        wb.save(excelName)

        # TODO: 直接把嵌套字典的内容输出至excel表格
        row = 2
        # 按文件名的第一个数字 也就是电池序号 进行排序
        dataDic = dict(sorted(dataDic.items(), key=lambda x: int(x[0].split('-')[0])))
        for file, para in dataDic.items():
            #  file 是主字典的键,表示当前电池序号。para是嵌套字典,表示当前电池的各项参数
            # 这里让row在循环内自增 即可避免表中所有的数都是最后一个文件的数据
            sheet.cell(row=row, column=1).value = file.rstrip('.txt')
            sheet.cell(row=row, column=2).value = para['Voc']
            sheet.cell(row=row, column=3).value = para['Jsc']
            sheet.cell(row=row, column=4).value = para['FF']
            sheet.cell(row=row, column=5).value = para['Eff']
            row += 1
        wb.save(excelName)
        count = self.getFileNums(inputPath)
        tkinter.messagebox.showinfo(title="提示", message=str(count) + "个文件提取完成")  # 显示提示弹窗


if __name__ == "__main__":
    window1 = Tk()  # 创建一个窗口对象
    window1_GUI = MY_GUI(window1)  # 实例化
    window1_GUI.setWindow()
    window1.mainloop()

软件运行界面如图所示:

posted @ 2021-10-18 22:38  三年二班XH  阅读(206)  评论(0)    收藏  举报