Python 批量文件处理小练习

需求

假设我们需要从一堆文本文件中提取对应行的信息,并作一定的处理之后集中到一个文本文件中,该如何用Python 快速处理呢?举个实例:

  • 问题描述:有11个 lvm 文件,需要从每个 lvm 文件中提取出最后十行数据的列均值,并存放在一个文件中,使用Python快速批处理。

思路

是否可被处理

看到上述问题,首先需要立即搞清楚,是否能使用 Python 来处理,其关键点在哪呢?当然是 lvm 格式的文件能否被 Python 处理,换句话说这个格式是否支持被记事本读取。

  • 关键信息一:经过实验,该 lvm 格式可被记事本当成txt处理,且信息无损。

接下来就可以尝试开干了,但 Python 干活经常需要导入相关的包,这里根据题意中的关键词基本可以推断出我们需要用到的包。

所需要的包

首先是 lvm 到 txt 的转变,改名后被 Python 读取,同时涉及到当前文件夹的跳转,这是与系统交互的IO操作,导入 OS 包;其次,进行数据处理,求均值(也可以不使用numpy,比如 math 也可以完成数学运算操作),这是 numpy 包比较强大的点。

  • 关键信息二:用到的包,OS、numpy

于是第一部分的源代码搭建起来了,包的导入:


import os
import numpy as np

做个小解释,这里的 as np 是给包起的别名,而np基本是通识的别名,如果在别人代码中看到 np.sum() 这类,需要明白 np 这个 指的是 numpy 。

文件更名

接着,我们需要知道如何获取当前文件所在路径,并且知道该如何改名


# 给文件改名
os.rename(oldName, newName)
# 获取当前工作文件夹
currentDir = os.getcwd()
# 前往新的文件夹路径
os.chdir(newDir)

于是改名部分的代码可以封装成为一个函数:


# 改名函数,待处理文件放入同级文件夹内
# 三个参数分别为,文件存放的文件夹名称 filesDir ,文件原格式 fileFormat ,文件新格式 newFileFormat
# 若待处理的批量文件与 代码源文件处于同一位置,则传入参数filesDir = None,也即调用 changeName(None,fileFormat,newFileFormat)
def changeName(filesDir,fileFormat,newFileFormat):

    # 传入参数 None ,则不对路径进行操作,否则进行拼接
    if filesDir != None:
        filesDir = '\\' + filesDir
        filesDir = str(os.getcwd()) + filesDir
    else:
        filesDir = os.getcwd()

    # 切换工作路径到文件夹,注意这里需要拼接得到的绝对路径
    # 如 C:\Users\ZekShawn\Desktop\data
    # 而不是 \data
    os.chdir(filesDir)

    # 获取源文件夹下的所有文件名称
    # os.listdir()
    fileList = os.listdir(filesDir)

    # 对文件列表进行遍历,只要以指定格式结尾,则进行更名操作
    # 更名时注意,这里的名称不带路径,因工作文件夹处于当前路径,因此无需拼接路径
    for file in fileList:
        if file.endswith(fileFormat)::
            fileOldName = file[:(-1*len(fileFormat))]
            fileNewName = fileOldName + newFileFormat
            os.rename(file,fileNewName)

    # 记得及时将路径切换回原工作路径,否则对其他部分处理时会出现非预期的bug
    os.chdir("..")

处理数据

做完小函数更名之后,我们接下来就是读取文件并处理数据了。因为是批量处理数据,并且每一个文件都有相同的处理程序,所以这里可千万不要偷懒。

对这类情况的最好处理方式是建立两个函数,一个函数针对单个 txt 文件进行处理,并返回单个 txt 文件处理后的结果。一个函数则负责调用它,并整合相关的数据。

这样操作的好处是,结构清晰错误调试简便,推荐像我一样的初学者使用。

单个 txt 处理函数


# 单个Txt读取,并返回指定行的数据
# 因为封装成了函数,那么这里涉及到的处理参数也需要转换成参数传入,比如需要处理的行数,开始行,以及文件的位置(带有绝对路径的文件名)
# 三个参数分别为,带有绝对路径的文件名,提取的总行数,提取数据的开始行
# 这里如果取倒数N行,则后两个传入参数可以为N,-N,Python 中以负数表示倒数也为基本常识
def singleTxtEdit(fileDir, rows, rowBegin):

    # 设定传出参数
    array = []
    time = ''

    # 以可读模式打开 txt ,这里的 "r" 表示可读模式
    with open(fileDir, "r") as oldTxt:

        # 对 txt 读取出的数据按行遍历
        for line in oldTxt:

            # 若没有初始化时间特征,则进一步判断该行是否存在时间信息可被提取
            # 本题中,样例数据内的时间信息提取较为简单,格式如下
            # Date	2020/09/28
            # Time	10:01:04.3906251499427322498
            # 因 Date 在所有的实验中均相同,因此不对其进行提取,而 Time 行最大的特征为存在 "Time" 与 ":",依靠这两点可确定该信息所在行
            if time == '':
                if 'Time' in line and ':' in line:
                    for strIndex in range(len(line)):
                        if line[strIndex] == ':':

                            # 提取该行 ":" 字符所在前 2 与(包括自己)后 6 个字符信息
                            # 需要注意的是,Python 的切片包括起始值不包括末尾值
                            time = line[strIndex-2:strIndex+6]
                            break;

            # 将所有的行信息增加到 array 数组中
            array.append(line)
    
    # 判断是否是负数开始行,若是,需要将其转换成为正数下标
    if rowBegin < 0:
        rowBegin += len(array)
    array = array[rowBegin:rowBegin+rows]

    # 利用切片的特性,去掉每行信息中的 "\n" 换行符
    for i in range(len(array)):
        array[i] = array[i][:-1]

    # 得到每一个 txt 文件的时间与指定行的原始数据
    return array,time

多个 txt 处理函数


# 参数分别为,文件夹路径,新 txt 文件名称,每个txt文件提取总行数,数据开始行数
def multiTxtEdit(filesDir, fileName, rows, rowBegin):
    # 判断是否为 None,是则代表在同一文件夹下,不是则拼接路径
    if filesDir != None:
        filesDir = '\\'+filesDir
        filesDir = str(os.getcwd()) + filesDir
    if not fileName.endswith('.txt'):
        fileName += '.txt'
    
    # 新建 Txt文件
    newTxt = open(fileName, 'a')

    # 对文件夹下的每一个文件进行遍历
    for fileItem in os.listdir(filesDir):

        # 文件名称加上绝对值
        fileItem = '\\' + fileItem
        fileItem = filesDir + fileItem

        # 处理单个Txt文件,得到最后十行数据,与实验时间信息
        array,time = singleTxtEdit(fileItem, rows, rowBegin)
        
        # 对数据进行处理
        tempArray = []
        for numItem in range(len(array)):
            # 替换每行数据字符串中的 "\t" 制表符为 ","
            array[numItem] = array[numItem].replace('\t',',')
            # 以 "," 为标准,分割成为 List 列表
            tempNum = array[numItem].split(',')
            tempNum = list(map(float,tempNum))
            # 添加到临时列表中
            tempArray.append(tempNum)
        
        # 对临时列表中的数据求解均值
        meanNum = list(np.mean(tempArray,axis = 0))
        
        # 写入数据
        # 以 "a" 追加模式打开 txt 文件
        newTxt = open(fileName, 'a')
        # 加上提示语
        fileItem = f"原始数据文件:{fileItem}"
        # 换行符易于阅读
        fileItem += '\n'
        # 写入文件名操作
        newTxt.write(fileItem)

        # 类同上述操作
        time = f"实验时间:{time}"
        time += '\n'
        newTxt.write(time)

        # 类同上述操作
        writeData = f"xx = {meanNum[1]}, yy = {meanNum[2]}"
        writeData = f"计算得到数据均值为:{writeData}"
        writeData += '\n\n'
        newTxt.writelines(writeData)
        
        # 需要保持一个良好的习惯,随开随关,不然可能出现程序关闭了,文件仍处于被占用的状态
        # 并且只有关闭后,才将数据写入到文件中,否则一直处于缓冲区内
        newTxt.close()

调用

接下来就是简单的调用了。


# 文件位于 data 子文件夹下,原始文件扩展名为 lvm ,更改为 txt 
changeName('data', 'lvm', 'txt')
# 设置数据不以 科学计数法 显示
np.set_printoptions(suppress=True)
# 对 data 子文件夹下的 txt 文件进行提取数据,新 txt 名称为 "time.txt" ,并且取10行数据,从倒数第10行开始
multiTxtEdit('data','time',10,-10)
# 处理完成,查看 time.txt 是否正确提取到了数据

交流

欢迎大家对该代码提出建议,或者进行讨论,一起学习进步呀~

数据部分打码,要是斌斌看到了觉得不妥可要联系我撤图呀。

赞赏