老师的好帮手——抽象功能

本文介绍了一套全面的文件处理与转换工具类,涵盖文件拷贝、文档格式转换、docx文件分析、Excel数据提取及网络文件传输等功能,旨在简化日常编程任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题背景

在尝试制作实验报告分类器,统计考勤数据等功能的过程中;发现有很多功能是相似的,因此本文将这些相似的功能抽象化,形成了一个拓展类。以方便后续的使用。

代码

文件处理拓展

class file_tools:
    """
    本类是对文件的一些拓展操作
    依赖库为 os, shutil
    目前实现的功能有:
    1. copy             两个文件之间的拷贝, 新文件所在路径可以不存在
    2. copy_fold        一个文件夹内所有文件向另一个文件的拷贝, 新文件路径可以不存在
    3. get_student_id   获取学号信息, 如果失败会返回None, False
    4. get_extend_name  获取文件的拓展名信息, 如果不是文件则返回None
    """
	@staticmethod
    def copy(old_filepath, new_filepath):
        """
        copy函数实现了在一个已有的文件到一个位置路径的拷贝
        如果路径不存在, 则会自动创建一个新的路径(文件夹)
        如果原路径不是一个文件, 或者拷贝过程出现失败, 都会返回False
        正常拷贝结果返回是True
        """
        from shutil import copyfile
        import os

        if os.path.isfile(old_filepath) == False:
            return False

        #获取到新的路径的文件夹路径
        fold_path = os.path.split(new_filepath)[0]
        
        try:
            #如果没有对应的文件夹, 那么创建一个新的
            if os.path.exists(fold_path) == False:
                os.mkdir(fold_path)

            #拷贝文件
            copyfile(old_filepath , new_filepath)
            return True
        except:
            print(r'拷贝文件失败, 请检查!')
            return False
	@staticmethod
    def copy_fold(old_fold, new_fold):
        """
        本函数实现两个文件夹下所有文件的拷贝
        其中不包括子文件夹及其内容的拷贝
        实现细节是枚举每一个文件, 然后进行拷贝
        返回的是 拷贝数量, 是否全部拷贝完成
        """
        import os
        count_copy = 0
        all_flag = True
        #枚举每一个文件
        for path in os.listdir(old_fold):
            if os.path.isfile(path) == False:
                continue
            #构造文件路径, 和目标路径
            old_path = old_fold + '\\' + path
            new_path = new_fold + '\\' + path
            #调用单个文件的拷贝函数, 根据返回结果进行进一步判定
            if copy(old_path, new_path) == False:
                print(r'文件 ',file,r' 拷贝失败')
                all_flag = False
            else:
                count_copy += 1

        return count_copy, all_flag
	@staticmethod
    def get_student_id(filepath,id_list=None):
        """
        完成学号的提取, 要求是学号必须是在文件名的最前面, id_list中最好是字符串类型
        另外还可以根据第二个参数id_list判断学号是不是在名单中, 默认所有学号都在
        返回学号, ok的模式, 其中学号是在list中的学号. 
        如果提取失败或学号不在list中. 返回None, False
        """
        import os
        #首先分割文件名, 如果是文件的话, 提取出文件名来
        basepath = os.path.split(filepath)[1]
        
        #先根据学号是的格式尝试提取学号来判定是不是满足题目条件
        id0 = None
        if len(basepath) >= 9 and basepath[0] == '1':
            id0 = basepath[:9]
        elif len(basepath) >= 10 and basepath[0] == '2':
            id0 = basepath[:10]
        else:
            return None, False

        #根据学号全是数字来判断是不是满足
        if id0.isdigit() == False:
            return None, False

        #根据传入的id_list来判断是不是在list中
        if (id_list == None) or (id_list != None and str(id0) in str(id_list)):
            return id0, True

        return None, False
	@staticmethod
    def get_extend_name(filepath):
        """
        本函数实现了获取文件拓展名的功能, 方便后续进行对应的文件处理
        如果传入不是文件, 或者获取拓展名失败, 则返回None. 否则返回拓展名(str),不带'.'
        """
        import os
        #判断是不是文件
        if os.path.isfile(filepath) == False:
            return None
        #对文件名进行进一步操作
        basename = os.path.split(filepath)[1]
        if basename != None and '.' in basename:
            return (basename.split('.'))[-1]

        return None

文件转化拓展

这部分为后面使用docx的拓展创造条件

class one2other(file_tools):
    """
    one2other提供了文档类型之间相互转化的静态方法
    如doc2pdf,doc2docx,docx2pdf.
    传入参数均为两个(inputfile,outputfile)
    需要精确到 '.doc' / '.docx' / '.pdf' 级别
    需要使用的库是 os , win32com, file_tools
    实现的功能:
    1. doc2pdf  从doc转到pdf, 支持word2007版本, word2013版本打开的所有文件
    2. doc2docx 从doc转到docx, 方便后续使用docx_tools
    3. docx2pdf 从docx转到pdf, 后续导出文件
    """
	@staticmethod
    def doc2pdf(inputfile, outputfile):
        """
        本函数实现了doc到pdf的转换
        传入参数为精确到文件+后缀的 输入文件名, 输出文件名
        返回值为True,表示转化成功, False,表示转化失败
        """
        import os
        from win32com.client import Dispatch, constants, gencache
        #开启库的向下兼容
        gencache.EnsureModule('{00020905-0000-0000-C000-000000000046}', 0, 8, 4)
        w = Dispatch("Word.Application")
        #尝试以下转化
        try:
            doc = w.Documents.Open(inputfile, ReadOnly = 1)
            doc.ExportAsFixedFormat(outputfile, constants.wdExportFormatPDF,\
                Item = constants.wdExportDocumentWithMarkup, CreateBookmarks = constants.wdExportCreateHeadingBookmarks)
            #print(r'转换pdf成功!')
            return True
        #出问题就是转化失败
        except:
            #print(r'转换失败?')
            return False
        finally:
            w.Quit(constants.wdDoNotSaveChanges)
	@staticmethod
    def doc2docx(inputfile,outputfile):
        """
        本函数实现了doc到docx的转换
        传入参数为精确到文件+后缀的 输入文件名, 输出文件名
        返回值为True,表示转化成功, False,表示转化失败
        """
        import os
        from win32com.client import Dispatch, constants, gencache
        w = client.Dispatch('Word.Application')
        #尝试一下进行转换
        try:
            doc = w.Documents.Open(inputfile)  # 目标路径下的文件
            doc.SaveAs(outputfile, 16)  # 转化后路径下的文件
            doc.Close()
            #print(r'转换docx成功!')
            return True
        #出问题就返回失败啊
        except:
            #print(r'转换失败?')
            return False
        finally:
            w.Quit(constants.wdDoNotSaveChanges)
	@staticmethod
    def docx2pdf(input, output):
        """
        本函数实现了doc到docx的转换
        传入参数为精确到文件+后缀的 输入文件名, 输出文件名
        返回值为True,表示转化成功, False,表示转化失败
        """
        import os
        from win32com.client import Dispatch, constants, gencache
        w = Dispatch('Word.Application')
        #尝试一下转换文件
        try:
            # 打开文件
            doc = w.Documents.Open(input, ReadOnly=1)
            # 转换文件
            doc.ExportAsFixedFormat(output, constants.wdExportFormatPDF,\
                                    Item=constants.wdExportDocumentWithMarkup, CreateBookmarks = constants.wdExportCreateHeadingBookmarks)
            #print(r'转换pdf成功!')
            return True
        #有问题就转换不成啊
        except:
            #print(r'转换失败?')
            return False
        finally:
            w.Quit(constants.wdDoNotSaveChanges)

docx文件拓展处理

这部分主要针对实验报告等文件

class docx_tools(file_tools):
    """
    本类提供对docx的拓展处理, 完成docx文件的需求处理
    依赖拓展库:os, docx, file_tools
    实现的功能有: 
    1. check_operational        文档是否为docx文件检测 
    2. get_picture_num          提取文档中的图片个数
    3. get_keyword_line_place   提取文档中关键词的首次出现位置, 返回首次出现的行数列表
    4. get_keyword_line_places  提取文档中关键词群的首次出现位置, 返回首次出现的行数列表
    """
	@staticmethod
    def check_operational(filepath):
        """
        函数功能判断该文件是不是能够被docx库操作
        返回一个文件 代表可以操作,None代表不能操作
        """
        import os, docx
        pathlist = filepath.split('.')
        #print(pathlist)
        if pathlist[-1] != 'docx':
            return None
        #第一部分首先检测后缀名是不是docx,如果不是则判定处理不了
        try:
            docu = docx.Document(filepath)
            return docu
        except:
            print(filepath, r'文件打开错误')
            return None
        #尝试利用docx库打开这个文件, 如果打开成功, 则认为是可以的
	@staticmethod
    def get_picture_num(docu):
        """
        函数功能为返回文档中的图片个数
        传入参数为一个文档docx.Document 的类型
        返回是文档中的图片个数
        """
        import os, docx
        pic_num = 0
        #直接从基层开始的代码搞上去
        for rel in docu.part._rels:
            #获得资源
            rel = docu.part._rels[rel]
            #判断标签内容是不是存在
            if "image" not in rel.target_ref:
                continue
            #统计到结果里面
            pic_num += 1
        #然后返回数目
        return pic_num
	@staticmethod
    def get_keyword_line_place(docu, keyword_list, default = -1, to_str = lambda x : x):
        """
        函数功能:查找keyword_list中每个单词的出现的句子位置
        传入参数: 文档, 关键词列表, 默认返回值, 关键词对应转化函数
        其中句子位置与paragraphs的数组标号对应, -1 为默认值
        返回为一个列表, 与keyword_list相对应
        """
        import os, docx
        place_list = [-1]*len(keyword_list)
        now_num = 0
        for i in range(0,len(docu.paragraphs)):
            #枚举每一个行, 然后检索是不是还有需要检索的单词
            if now_num >= len(keyword_list):
                break
            #枚举需要检索的单词的序号, 然后进行检索
            for index2 in range(0,len(keyword_list)):
                #如果已经找到了, 那就不用找了
                if place_list[index2] != -1:
                    continue
                #然后构造模式串
                head_one = to_str(keyword_list[index2])
                if head_one in docu.paragraphs[i].text:
                    place_list[index2] = i
                    #找到的序号+1
                    now_num += 1

        #构造返回值序列
        return [x if x!=-1 else default for x in place_list]
	@staticmethod
    def get_keyword_line_places(docu, keywords_list, default = -1, to_str = lambda x : x):
        """
        keywords_list 形如 [['A','a'],['B','b'],'Aa']
        实现模式为枚举keywords_list[index1]中的每个元素, 找到一个最小的位置
        函数功能:查找keyword_list中每个单词的出现的句子位置
        传入参数: 文档, 关键词列表, 默认返回值, 关键词对应转化函数
        其中句子位置与paragraphs的数组标号对应, -1 为默认值
        返回为一个列表, 与keyword_list相对应
        """
        import os, docx
        place_list = [-1]*len(keywords_list)
        now_num = 0
        for i in range(0,len(docu.paragraphs)):
            #枚举每一个行, 然后检索是不是还有需要检索的单词
            if now_num >= len(keywords_list):
                break
            #枚举需要检索的单词的序号, 然后进行检索
            for index1 in range(0,len(keywords_list)):
                #如果已经找到了, 那就不用找了
                if place_list[index1] != -1:
                    continue
                
                keyword_list = keywords_list[index1]
                #构造单元序列

                for head0 in keyword_list:
                    head_one = to_str(head0)
                    #然后构造模式串

                    if head_one in docu.paragraphs[i].text:
                        place_list[index2] = i
                        #找到的序号+1
                        now_num += 1
                        break

        #构造返回值序列
        return [x if x!=-1 else default for x in place_list]

excel拓展处理

这部分针对成绩单,考勤记录等excel文件

class excel_tools(file_tools):
    """
    本类实现了excel的处理拓展类, 主要侧重于
    依赖库: pandas, numpy, file_tools
    实现的功能:
    1. get_one2other    抽象提取: 提取一个列到另一个列的映射, 传入为一个文件地址, key列名, value列名
    2. get_id2index     提取学号→序号:提取出成绩单中的学号/序号等对应信息, 返回对应的map
    3. get_id2name      提取学号→姓名:提取出成绩单中的学号/姓名等对应信息, 返回对应的map
    4. print_to_excel   尝试输出excel文件, 如果输出失败, 则会在一定的等待时间后重试
    """
	@staticmethod
    def get_one2other(rankpath, one, other):
        """
        rankpath是excel的地址,one是一个列的名,other是一个列的名,返回one→other的映射
        对一个excel表格,获取one列的值,按照对应关系对应到other
        如果正常提取,则返回一个map,如果出现异常,则返回None
        """
        import pandas as pd
        #读取rank中的名字的顺序,并映射到序号列表中,方便后续进行排序
        rank = None
        #进行一波尝试,尝试读取文件
        try:
            rank = pd.read_excel(rankpath)
        except:
            return None
        if one not in rank.columns.values or other not in rank.columns.values:
            return None
        #列表推导式生成字典
        map_one2other = {str(rank[one][i]):int(rank[other][i]) for i in range(0,len(rank[one]))}

        return map_one2other
	@staticmethod
    def get_id2index(rankpath):
        """
        根据更加抽象的函数扩展出来的特定的
        提取:学号→序号 的特殊函数
        """
        map_id2index = get_one2other(rankpath, r'学号',r'序号')
        return map_id2index
	@staticmethod
    def get_id2name(rankpath):
        """
        根据更加抽象的函数扩展出来的特定的
        提取:学号→姓名 的特殊函数
        """
        map_id2name = get_one2other(rankpath, r'学号',r'姓名')
        return map_id2name
	@staticmethod
    def print_to_excel(df, filepath, errorhint=r'导出失败, 正在重试'):
        """
        本函数实现了导出到excel文件中的功能, 由于可能会出现忘记关闭原文件等情况, 因此加入自动重试机制
        传入参数为 DataFrame, 文件路径, 错误提示语句
        """
        import pandas
        import time
        if get_extend_name(filepath) != '.xlsx':
            return False
        false_count = 0
        while True:
            #最多尝试10次, 如果过多直接返回错误
            if false_count > 10:
                return False
            try:
                #尝试导出到指定文件
                df.to_excel(filepath)
                return True
                break
            except:
                #显示重新尝试
                print(errorhint, r'\n1S后进行重试')
                time.sleep(1)
                false_count += 1
        return False

其他常用

这部分针对一些应用中的功能抽象

class user_tools:
    """
    本类实现了常见功能的模块化, 方便写程序的时候进行调用. 方便使用者的写程序, 写界面的便捷性
    依赖库: tkinter, time, socket, os, sys, struct, threading
    实现的功能:
    1. get_paths            可视化窗口询问文件夹路径
    2. get_path             可视化窗口询问文件路径
    3. deferdo              延迟等待, 会显示剩余时间
    4. socket_client_file   tcp协议的发送文件客户端
    5. socket_client_fold   tcp协议的发送文件夹客户端
    6. socket_service       tcp协议的服务端
    """
	@staticmethod
    def get_paths(inputstr=r'请选择文件夹'):
        """
        本函数实现了可视化窗口询问文件夹路径的功能, 而且不会产生除了询问框外的其他视图
        传入参数inputstr为问询框的标题
        """
        from tkinter import filedialog
        import tkinter as tk
        #设置一个tkinter的窗口,然后设置为隐藏
        root = tk.Tk()
        root.withdraw()
        #隐藏根tk, 免除白框框
        #弹出提示后询问文件夹路径
        folderpath = filedialog.askdirectory(title=inputstr, initialdir=r'./')
        return folderpath
	@staticmethod
    def get_path(inputstr=r'请选择文件'):
        """
        本函数实现了可视化窗口询问文件路径的功能, 而且不会产生除了询问框外的其他视图
        传入参数inputstr为问询框的标题
        """
        from tkinter import filedialog
        import tkinter as tk
        #设置一个tkinter的窗口,然后设置为隐藏
        root = tk.Tk()
        root.withdraw()
        #隐藏根tk, 免除白框框
        #弹出提示之后询问路径
        filepath =  filedialog.askopenfilename(title=inputstr, initialdir=r'./')
        return filepath
	@staticmethod
    def deferdo(defer_second = 5):
        """
        本函数实现了延迟等待的功能, 将多秒的延迟等待可视化了等待时间
        defer_second为整数, 表示需要延时的时长(秒为单位), 默认为5s
        """
        import time
        for i in range(0,int(defer_second)):
            print(defer_second-i,' 秒后返回正常运行!')
            time.sleep(1)
	@staticmethod
    def socket_client_file(host='127.0.0.1',port=888):
        """
        socket TCP传递文件的客户端,传入参数为地址和端口
        默认为: host = '127.0.0.1', port = 888
        """
        import socket
        import os
        import sys
        import struct
    
        #开启tcp连接,并尝试连接
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((host, port))
        except socket.error as msg:
            print (msg)
            sys.exit(1)
        print (s.recv(1024))

        # 需要传输的文件路径
        filepath = get_path('请选择文件:')

        # 判断是否为文件
        if os.path.isfile(filepath):

            # 定义定义文件信息。128s表示文件名为128bytes长,l表示一个int或log文件类型,在此为文件大小
            fileinfo_size = struct.calcsize('128sl')
            # 定义文件头信息,包含文件名和文件大小
            fhead = struct.pack('128sl', os.path.basename(filepath).encode('utf-8'), os.stat(filepath).st_size)
            # 发送文件名称与文件大小
            s.send(fhead)

            # 将传输文件以二进制的形式分多次上传至服务器
            fp = open(filepath, 'rb')
            while 1:
                data = fp.read(1024)
                if not data:
                    print ('{0} file send over...'.format(os.path.basename(filepath)))
                    break
                s.send(data)
            # 关闭当期的套接字对象
            s.close()
	@staticmethod
    def socket_client_fold(host='127.0.0.1',port=888):
        """
        socket TCP传递文件夹的客户端,传入参数为地址和端口
        默认为: host = '127.0.0.1', port = 888
        """
        import socket
        import os
        import sys
        import struct
    
        #开启tcp连接,并尝试连接
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((host, port))
        except socket.error as msg:
            print (msg)
            sys.exit(1)
        print (s.recv(1024))

        # 需要传输的文件路径
        foldpath = get_paths('请选择文件夹:')

        for file in os.listdir(foldpath):
            # 判断是否为文件
            if os.path.isfile(file):
                # 定义定义文件信息。128s表示文件名为128bytes长,l表示一个int或log文件类型,在此为文件大小
                fileinfo_size = struct.calcsize('128sl')
                # 定义文件头信息,包含文件名和文件大小
                fhead = struct.pack('128sl', (os.path.basename(foldpath)+'/'+file).encode('utf-8'), os.stat(flodpath + '/' +file).st_size)
                # 发送文件名称与文件大小
                s.send(fhead)

                # 将传输文件以二进制的形式分多次上传至服务器
                fp = open(filepath, 'rb')
                while 1:
                    data = fp.read(1024)
                    if not data:
                        print ('{0} file send over...'.format(os.path.basename(file)))
                        break
                    s.send(data)
                # 关闭当期的套接字对象
                s.close()

	@staticmethod
    def socket_service(host='127.0.0.1',port=888):
        """
        socket TCP接收文件/文件夹的客户端,传入参数为地址和端口
        默认为: host = '127.0.0.1', port = 888
        """
        import threading
        import socket
        import struct
        #建立socket套接字,等待连接
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            
            s.bind((host, port))
            # 设置监听数
            s.listen(10)
        except socket.error as msg:
            print (msg)
            sys.exit(1)
        print ('Waiting connection...')
 
        #单独的接收数据的一个线程
        def deal_data(conn, addr):
            print ('Accept new connection from {0}'.format(addr))
            # conn.settimeout(500)
            # 收到请求后的回复
            conn.send('Hi, Welcome to the server!'.encode('utf-8'))
 
            while True:
                # 申请相同大小的空间存放发送过来的文件名与文件大小信息
                fileinfo_size = struct.calcsize('128sl')
                # 接收文件名与文件大小信息
                buf = conn.recv(fileinfo_size)
                # 判断是否接收到文件头信息
                if buf:
                    # 获取文件名和文件大小
                    filename, filesize = struct.unpack('128sl', buf)
                    fn = filename.strip('\00')
                    fn = fn.decode('utf-8')
                    print ('file new name is {0}, filesize if {1}'.format(str(fn),filesize))
 
                    recvd_size = 0  # 定义已接收文件的大小
                    # 存储在该脚本所在目录下面
                    fp = open('./' + str(fn), 'wb')
                    print ('start receiving...')
            
                    # 将分批次传输的二进制流依次写入到文件
                    while not recvd_size == filesize:
                        if filesize - recvd_size > 1024:
                            data = conn.recv(1024)
                            recvd_size += len(data)
                        else:
                            data = conn.recv(filesize - recvd_size)
                            recvd_size = filesize
                        fp.write(data)
                    fp.close()
                    print ('end receive...')
                # 传输结束断开连接
                conn.close()
                break

        #无限循环然后等待连接
        while True:
            # 等待请求并接受(程序会停留在这一旦收到连接请求即开启接受数据的线程)
            conn, addr = s.accept()
            # 接收数据
            t = threading.Thread(target=deal_data, args=(conn, addr))
            t.start()

        return
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值