• 用Python做一个文件夹整理工具


    简介

    我们的目的是做一个像下面这样的工具,前面两个输入框,用于输入源路径和目标路径,下面的图片、视频、音乐表示在目标路径中创建的文件夹,后面的文件后缀,表示将这类文件移动到对应的文件夹中,加减号可以新增或删除文件夹。

    在这里插入图片描述

    文件夹对话框

    第一步,实现源路径和目标路径的输入输出,从而需要自定义一个组件,代码如下,其及具体实现逻辑,可以参考:自定义文件选择按钮

    import tkinter as tk
    import tkinter.ttk as ttk
    from tkinter.filedialog import (askopenfilename,
        askopenfilenames, askdirectory, asksaveasfilename)
    from tkinter.colorchooser import askcolor
    
    class DialogButton(ttk.Frame):
        def __init__(self, master, 
            height, widthL, widthR, logtype, label=None, text=None, 
            frmDct={}, btnDct={}, enyDct={}, logDct={}):
            w = widthL + widthR
            super().__init__(master, 
                height=height, width = w, **frmDct)
            self.pack(fill=tk.X)
    
            self.text = tk.StringVar() if not text else text
            ttk.Entry(self, width=widthL, textvariable=self.text, 
                **enyDct).pack(side=tk.LEFT, fill = tk.X, expand=True)
            
            ttk.Button(self, width=widthR, 
                text=self.setLabel(logtype, label),
                command = self.Click, **btnDct).pack(side=tk.RIGHT, padx=5)
            self.logtype = logtype
            self.logDct = logDct
    
        def setLabel(self, key, label=None):
            if label:
                return label
            labelDct = {
                "文件"   : "选择文件",
                "文件夹" : "选择路径",
                "多文件" : "选择多个文件",
                "保存" : "存储路径",
                "颜色"   : "选择颜色",
            }
            return labelDct[key]
    
        def Click(self):
            typeDct = {
                "文件"  : askopenfilename,
                "文件夹": askdirectory,
                "多文件": askopenfilenames,
                "保存"  : asksaveasfilename,
                "颜色"  : askcolor,
            }
            text = typeDct[self.logtype](**self.logDct)
            if self.logtype == "颜色":
                text = text[1]
            self.text.set(text)
    
        def get(self):
            return self.text.get()
    
        def set(self, txt):
            self.text.set(txt)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    文件映射组件

    关于文件夹整理这个功能,此前其实做过无图形界面的版本:
    Python整理下载文件夹,第一步自然是做一个文件映射字典,

    F_DCT = {
        "图片"  : [".jpg", ".png", ".jpeg", ".gif"],
        "视频"  : [".mp4", ".mkv"],
        "音乐"  : [".mp3", ".wav"],
        "文档"  : [".pdf"],  
        "文献"  : [".caj"],
        "文本"  : [".csv", ".txt"],
        "源码"  : [".py", ".c", ".cpp", ".php"],
        "office": [".docx",".xlsx", ".pptx", ".doc", ".ppt", ".xls"],
        "压缩包": [".zip", ".tgz", ".rar", ".tar", ".7z", ".gz"],
        "安装包": [".msi", ".exe"],
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后就是工具的核心内容,文件映射组件,主要由左右两部分组成,左侧是文件夹名,右侧是对应的文件后缀。从这两个子组件出发,可以为其设置初始化参数以及方法。先看源码,再逐段解析

    import tkinter as tk
    import tkinter.ttk as ttk
    from ctrls import DialogButton
    import os
    import shutil
    pJoin = os.path.join
    pExist = os.path.exists
    
    from pathlib import Path
    
    class ExFolder(ttk.Frame):
        def __init__(self, master, 
            folder=None, ex=None, dst=None, **options):
            super().__init__(master, **options)
            self.pack()
    
            self.folder = tk.StringVar()
            if folder : self.setFolder(folder)
            self.ex = tk.StringVar()
            if ex : self.setEx(ex)
            self.setDst(dst)
    
            self.initWidgets()
    
        def initWidgets(self):
            ttk.Entry(self, textvariable=self.folder, 
                width=10).pack(side=tk.LEFT, padx=2)    
            ttk.Entry(self, textvariable=self.ex
                ).pack(side=tk.LEFT, fill=tk.X, expand=True)
    
        def getFolder(self):
            return self.folder.get()
    
        def setFolder(self, f):
            self.folder.set(f)
        
        def getEx(self):
            exs = self.ex.get().split(",")
            for i in range(len(exs)):
                exs[i] = exs[i].strip()
            return exs
        
        def setEx(self, exs):
            if type(exs)==list:
                exs = ', '.join([ex.strip() for ex in exs])
            self.ex.set(exs)
        
        def mvFile(self, src):
            try:
                shutil.move(src, self.dst)
            except Exception as e:
                self.err = print(e)
        
        # 设置目标文件夹
        def setDst(self, dst):
            if not dst:
                self.dst = None
                return
            self.dst = pJoin(dst, self.getFolder())
            if not pExist(self.dst):
                os.makedirs(self.dst)
            self.errs = {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    首先,folder和ex分别是左右两个组件对应的可变文本,为了便于更新和调用,类中设计了两组、四对便携方法getFolder, setFolder和getEx和setEx。由于Folder中的内容就是文件夹本身,所以设置和读取方法仅仅是StringVar的二次封装,而ex中的内容则不然,需要对文本进行分割,并且实现字符串到列表的转换。

    mvFile和setDst是组件的核心功能,分别用于设置目标路径并移动。之所以为目标文件夹设置安排了一个独立方法,是因为目标文件夹不一定存在,可能涉及到递归创建的过程。

    完整组件

    最后,是整个组件的布局,源码如下,其中撤销功能并未实现。

    class FolderTools(ttk.Frame):
        def __init__(self, master, fDct=F_DCT, **options):
            super().__init__(master, **options)
            self.pack()
            self.fDct = fDct
    
            self.initWidgets()
            self.fEnys = []
            self.exEnys = []
    
        def setDct(self, fDct):
            self.fDct = fDct
            self.exDct = {}
            for k,v in self.fDct.items():
                exDct.update({ex:k for ex in v})
    
        def initWidgets(self):
            self.exForders = []
            pDct = dict(side=tk.TOP, expand=True, fill=tk.X)
            self.srcFolder = DialogButton(self, 5, 25, 8, 
                                          "文件夹", label="源路径")
            self.srcFolder.pack(**pDct)
            self.dstFolder = DialogButton(self, 5, 25, 8, 
                                          "文件夹", label="目标路径")
            self.dstFolder.pack(**pDct)
    
            btns = ttk.Frame(self)
            btns.pack(**pDct)
            ttk.Button(btns, text="➕ ", 
                command=self.btnAddFolder).grid(row=0,column=0)
            ttk.Button(btns, text="➖", 
                command=self.btnDelFolder).grid(row=0,column=1)
            ttk.Button(btns, text="移动", 
                command=self.btnMove).grid(row=0,column=2)
            ttk.Button(btns, text="撤销", 
                command=self.btnMove).grid(row=0,column=3)
    
            self.exFrm = ttk.LabelFrame(self, text="文件夹映射表")
            self.exFrm.pack(**pDct)
    
            for k, v in self.fDct.items():
                self.addFolders(self.exFrm, k, v)
    
        def btnAddFolder(self):
            self.addFolders(self.exFrm, None, None)
    
        def addFolders(self, frm, folder, exs):
            ef = ExFolder(frm, folder, exs)
            ef.pack(side=tk.TOP, expand=True, fill=tk.X, padx=2, pady=1)
            self.exForders.append(ef)
    
        def btnDelFolder(self):
            if len(self.exForders) == 0: return
            self.exForders[-1].pack_forget()
            del self.exForders[-1]
        
        def setExDct(self):
            self.exDct = {}
            for ef in self.exForders:
                self.exDct.update({key:ef for key in ef.getEx()})
    
        def btnMove(self):
            src = self.srcFolder.get()
            dst = self.dstFolder.get()
            if dst=="": dst = src
    
            # 设置后缀映射字典 ex:ExFolder
            exDct = {}
            for ef in self.exForders:
                exDct.update({ex:ef for ex in ef.getEx()})
                ef.setDst(dst) # 设置目标文件夹
    
            files = os.listdir(src)
            for f in files:
                p = pJoin(src, f)
                ex = Path(p).suffix.lower()
                if ex in exDct:
                    exDct[ex].mvFile(p)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    三个已经实现的功能按钮,➕用于新增一个文件夹映射组件,➖则移除最后一个映射组件。移除组件时需要注意,不仅仅要把组件解绑,还要把组件对应的实例删掉。

    核心功能被绑定在移动按钮上,首先读取源路径和目标路径,如果目标路径未输入,就在源路径下工作。

    接下来,将文件映射组件重新拆解为映射字典,最后调用对应组件的mvFile方法,实现所有文件的移动。

  • 相关阅读:
    ELK中 Elasticsearch和Logstash内存大小设置的考虑
    【pwn】2022 羊城杯 fakeNoOutput
    为了直播焊接,我准备了这些装备
    AJAX学习日记——Day 4
    【见闻录系列】我所理解的搜索业务二三事
    构建健康游戏环境:DFA算法在敏感词过滤的应用
    【网安大模型专题10.19】论文4:大模型+自动生成代码评估:改进自动化测试方法、创建测试输入生成器、探索新的评估数据集扩充方法,提高编程基准的精度
    RPA要不要学习,真的能解放双手吗?
    《微信小程序-进阶篇》Lin-ui组件库源码分析-列表组件List(二)
    UDP 编程不能太随意
  • 原文地址:https://blog.csdn.net/m0_37816922/article/details/132730450