文件夹太乱?Python小程序瞬间帮你整理到位

作者:小小明、黄同学

来源:数据分析与统计学之美

我承认我不是一个爱整理桌面的人,因为我觉得乱糟糟的桌面,反而容易找到文件。
哈哈,可是最近桌面实在是太乱了,自己都看不下去了,几乎占满了整个屏幕。虽然一键整理桌面的软件很多,但是对于其他路径下的文件,我同样需要整理,于是我想到使用Python,完成这个需求。

效果展示

我一共为将文件分为9个大类,分别是图片视频音频文档压缩文件常用格式程序脚本可执行程序字体文件
# 不同文件组成的嵌套字典
file_dict = {
            '图片': ['jpg','png','gif','webp'],
            '视频': ['rmvb','mp4','avi','mkv','flv'],
            '音频': ['cd','wave','aiff','mpeg','mp3','mpeg-4'],
            '文档': ['xls','xlsx','csv','doc','docx','ppt','pptx','pdf','txt'],
            '压缩文件': ['7z','ace','bz','jar','rar','tar','zip','gz'],
            '常用格式': ['json','xml','md','ximd'],
            '程序脚本': ['py','java','html','sql','r','css','cpp','c','sas','js','go'], 
            '可执行程序': ['exe','bat','lnk','sys','com'],
            '字体文件': ['eot','otf','fon','font','ttf','ttc','woff','woff2']
        }
file_dict 是自己定义的一个字典,里面包含了我们学习、工作中常用的格式。常用格式需要为大家解释一下,对于平时经常使用,但是又不知道放在哪一类的文件,都存放在这里。
注意: 如果你的电脑中,有着其它更多的文件格式,只需要修改上述的file_dict字典即可。
在正式讲述知识点之前,我们直接看看效果吧,我特意录制了一个短视频!

开发思路

开发这样一个小工具,一共涉及到三个Python库,分别是os模块shutil模块glob模块,它们搭配使用,用来处理文件和文件夹,简直超给力!
整个开发步骤,大致思路是这样的:
  • ① 任意给定一个文件路径;
  • ② 获取当前文件路径下的所有文件,并取得每个文件对应的后缀;
  • ③ 判断每个文件,是否在指定的嵌套字典中,并返回对应的文件分类;
  • ④ 判断每个文件分类的文件夹是否存在。因为需要创建新的文件夹,用于分类存放文件;
  • ⑤ 将每个文件,复制到对应的分类中;
完整代码如下(说明见注释)
# 导入相关库
import os
import glob
import shutil

# 采用input()函数,动态输入要处理的文件路径。
path = input('请输入要清理的文件路径:')

# 定义一个文件字典,不同的文件类型,属于不同的文件夹,一共9个大类。
file_dict = {
            '图片': ['jpg','png','gif','webp'],
            '视频': ['rmvb','mp4','avi','mkv','flv'],
            '音频': ['cd','wave','aiff','mpeg','mp3','mpeg-4'],
            '文档': ['xls','xlsx','csv','doc','docx','ppt','pptx','pdf','txt'],
            '压缩文件': ['7z','ace','bz','jar','rar','tar','zip','gz'],
            '常用格式': ['json','xml','md','ximd'],
            '程序脚本': ['py','java','html','sql','r','css','cpp','c','sas','js','go'], 
            '可执行程序': ['exe','bat','lnk','sys','com'],
            '字体文件': ['eot','otf','fon','font','ttf','ttc','woff','woff2']
        }

# 定义一个函数,传入每个文件对应的后缀。判断文件是否存在于字典file_dict中;
# 如果存在,返回对应的文件夹名;如果不存在,将该文件夹命名为'未知分类';
def func(suffix):
    for name, type_list in file_dict.items():
        if suffix.lower() in type_list:
            return name
    return '未知分类'

# 递归获取 '待处理文件路径' 下的所有文件和文件夹。
for file in glob.glob(f'{path}/**/*',recursive=True):
 # 由于我们是对文件分类,这里需要挑选出文件来。
    if os.path.isfile(file):
     # 由于isfile()函数,获取的是每个文件的全路径。这里再调用basename()函数,直接获取文件名;
        file_name = os.path.basename(file)
        suffix = file_name.split('.')[-1]
        # 判断 '文件名' 是否在字典中。
        name = func(suffix)
        #print(func(suffix))
        # 根据每个文件分类,创建各自对应的文件夹。
        if not os.path.exists(f'{path}\\{name}'):
            os.mkdir(f'{path}\\{name}')
        # 将文件复制到各自对应的文件夹中。
        shutil.copy(file,f'{path}\\{name}')

结果如下:

窗口界面

上面的代码已经实现了功能,但没有一个可视化界面,也没有将程序打包,所以我自己用没问题,要是发给别人就不太好使了。
于是,我们再来将程序进一步完善,做成一个方便使用的“小工具”。

小工具效果展示

根据实际需求,需要被整理的文件夹往往都是单层的,例如桌面,所以程序并不支持去对子文件夹去递归处理。已经在子文件夹中的文件,都会被认为是已经被整理过了。
我们先来看看最终呈现的效果:
下面,我们来讲述一下它的功能介绍。小工具的主界面是这样的:
点击开始整理,选择待整理的文件夹后,就可以进行整理了,下面是整理前后的效果图。
界面还支持回退功能和删除空文件夹功能。回退功能就是当你将文件夹整理完毕后,点击回退,又可以恢复到文件夹原始模样。
如果你觉得我对格式的分类,不满足你的需求,你还可以私人定制,只需要去修改同一目录下的config.json配置文件就行。
点击重载配置,即可在不重启程序的情况下生效。也可以直接通过程序本身提供的编辑框修改配置,点击保存修改即可。

小工具功能开发流程

首先定义分类字典:
file_dict = {
    '图片': ['jpeg', 'jpg', 'png', 'gif', 'webp', 'bmp', 'bpg', 'svg', 'heif', 'psd'],
    '视频': ['rmvb', 'mp4', 'avi', 'mkv', 'flv', 'wmv', 'mov', 'mpg', 'mpeg', '3gp'],
    '音频': ['m4a', 'aac', 'ogg', 'oga', 'mp3', 'wma', 'wav'],
    '电子书': ['pdf', 'epub', 'mobi', 'azw3', 'chm', 'txt'],
    '数据与表格': ['xls', 'xlsx', 'xlsm', 'csv', 'json', 'xml'],
    '文档': ['doc', 'docx', 'ppt', 'pptx', 'md', '.txt'],
    '思维导图': ['emmx', 'mmap', 'xmind'],
    '程序脚本': ['py', 'java', 'html', 'sql', 'r', 'css', 'cpp', 'c', 'js', 'go'],
    '压缩文件': ['tar', 'gz', 'rz', '7z', 'dmg', 'rar', 'xar', 'zip', 'iso'],
    '可执行程序': ['exe', 'bat', 'sys', 'com'],
    '字体文件': ['eot', 'otf', 'fon', 'font', 'ttf', 'ttc', 'woff', 'woff2']
}
然后定义一个函数,用于获取文件所属的类型:
def get_file_type(filename):
    '传入文件名,读取file_dict配置,根据后缀判断文件类型'
    for file_type, suffixs in file_dict.items():
        for suffix in suffixs:
            if filename.endswith('.'+suffix.lstrip('.')):
                return file_type
    return '未知类型'
调用方式:
get_file_type(r'D:\360安全浏览器下载\document.pdf')
# 结果如下:电子书
下面使用pathlib库保存移动信息:
from pathlib import Path

def mkdirAndGetChange(path):
    path = Path(path)
    result = []
    for file in path.glob('*'):
        if file.is_dir():
            continue
        src_path = file.absolute()
        dest_dir = get_file_type(file.name)
        dest_path = path/dest_dir/file.name
        dest_dir = dest_path.parent
        if not dest_dir.exists():
            dest_dir.mkdir()
        result.append((src_path, dest_path))
    return result

调用方式:
path = r'D:\360安全浏览器下载'
file_changes = mkdirAndGetChange(path)
print(file_changes)
结果如下:
[(WindowsPath('D:/360安全浏览器下载/9种常用的数据分析方法.pdf'),
  WindowsPath('D:/360安全浏览器下载/电子书/9种常用的数据分析方法.pdf')),
...
 (WindowsPath('D:/360安全浏览器下载/金融时间序列分析讲义.pdf'),
  WindowsPath('D:/360安全浏览器下载/电子书/金融时间序列分析讲义.pdf'))]
然后需要改名时:
for src_path, dest_path in file_changes:
    src_path.rename(dest_path)
还可以再还原回来:
for src_path, dest_path in file_changes:
    dest_path.rename(src_path)
最后写一个方法用于清空空白文件夹:
def clear_black_dir(path):
    path = Path(path)
    for file in path.glob('*'):
        if not file.is_dir():
            continue
        if not os.listdir(file):
            file.rmdir()

path = r'D:\360安全浏览器下载'
clear_black_dir(path)

小工具GUI开发流程

为了方便修改配置,将配置文件写到单独的文件中。在封装上述核心逻辑,命名为auto_organize.py。
'''python
小小明的代码
CSDN主页:https://blog.csdn.net/as604049322
'''
__author__ = '小小明'
__time__ = '2021/8/11'

import json
import os
from pathlib import Path

def load_config_json():
    with open('config.json', encoding='u8') as f:
        config_json = f.read()
        return config_json

def save_config(config):
    with open('config.json', 'w', encoding='u8') as f:
        f.write(config)

config_json = load_config_json()
file_dict = json.loads(config_json)

def get_file_type(filename):
    '传入文件名,读取file_dict配置,根据后缀判断文件类型'
    for file_type, suffixs in file_dict.items():
        for suffix in suffixs:
            if filename.endswith('.' + suffix.lstrip('.')):
                return file_type
    return '未知类型'

def mkdirAndGetChange(path):
    path = Path(path)
    result = []
    for file in path.glob('*'):
        if file.is_dir():
            continue
        src_path = file.absolute()
        dest_dir = get_file_type(file.name)
        dest_path = path / dest_dir / file.name
        dest_dir = dest_path.parent
        if not dest_dir.exists():
            dest_dir.mkdir()
        result.append((src_path, dest_path))
    return result

def clear_black_dir(path):
    path = Path(path)
    num = 0
    for file in path.glob('*'):
        if not file.is_dir():
            continue
        if not os.listdir(file):
            file.rmdir()
            num += 1
    return num

配置文件config.json的内容是:
{
    '图片': ['jpeg', 'jpg', 'png', 'gif', 'webp', 'bmp', 'bpg', 'svg', 'heif', 'psd'],
    '视频': ['rmvb', 'mp4', 'avi', 'mkv', 'flv', 'wmv', 'mov', 'mpg', 'mpeg', '3gp'],
    '音频': ['m4a', 'aac', 'ogg', 'oga', 'mp3', 'wma', 'wav'],
    '电子书': ['pdf', 'epub', 'mobi', 'azw3', 'chm', 'txt'],
    '数据与表格': ['xls', 'xlsx', 'xlsm', 'csv', 'json', 'xml'],
    '文档': ['doc', 'docx', 'ppt', 'pptx', 'md', '.txt'],
    '思维导图': ['emmx', 'mmap', 'xmind'],
    '程序脚本': ['py', 'java', 'html', 'sql', 'r', 'css', 'cpp', 'c', 'js', 'go'],
    '压缩文件': ['tar', 'gz', 'rz', '7z', 'dmg', 'rar', 'xar', 'zip', 'iso'],
    '可执行程序': ['exe', 'bat', 'sys', 'com'],
    '字体文件': ['eot', 'otf', 'fon', 'font', 'ttf', 'ttc', 'woff', 'woff2']
}
GUI程序开发代码:
'''
小小明的代码
CSDN主页:https://blog.csdn.net/as604049322
'''
__author__ = '小小明'
__time__ = '2021/8/11'

import json
import os
import sys

import PySimpleGUI as sg
import auto_organize

sg.change_look_and_feel('LightBlue')
layout = [
    [sg.Text('被处理的文件夹路径(默认为当前路径):')],
    [sg.In(key='path'),
     sg.FolderBrowse('...', target='path')],
    [
        sg.Button('开始整理', enable_events=True, key='auto_organize', font=('楷体', 15)),
        sg.Button('回退', enable_events=True, key='back_before', pad=(20, 0), font=('楷体', 15)),
        sg.Button('删除空文件夹', enable_events=True, key='del_black', pad=(10, 0), font=('楷体', 15))
    ],
    [sg.Text('改名配置:'),
     sg.Button('重载配置', enable_events=True, key='reload_config'),
     sg.Button('保存修改', enable_events=True, key='save_config')
     ],
    [sg.Multiline(size=(46, 12), key='out')],
    [sg.Text('@小小明:https://blog.csdn.net/as604049322'), ],
]

def resource_path(relative_path):
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)

window = sg.Window('文件夹整理工具 by 小小明', layout, icon=resource_path('h.ico'))
window.finalize()
window['out'].update(auto_organize.config_json)

file_changes = None
while True:
    event, values = window.read()
    # print(event, values)
    if event in (None,):
        break  # 相当于关闭界面
    elif event == 'auto_organize':
        path = values['path']
        if os.path.abspath(path) == os.path.abspath('.'):
            sg.popup('未选择路径或输入的路径为当前目录,\n不允许选择程序所在的路径!', title='提示')
            continue
        file_changes = auto_organize.mkdirAndGetChange(path)
        for src_path, dest_path in file_changes:
            src_path.rename(dest_path)
        sg.popup('整理完成,允许一次回退重置!', title='提示')
    elif event == 'back_before':
        if not file_changes:
            sg.popup('未执行过整理操作!', title='提示')
            continue
        for src_path, dest_path in file_changes:
            dest_path.rename(src_path)
        auto_organize.clear_black_dir(values['path'])
        file_changes = None
        sg.popup('回退完成并删除了全部的空文件夹!', title='提示')
    elif event == 'del_black':
        n = auto_organize.clear_black_dir(values['path'])
        sg.popup(f'共删除了{n}个空文件夹!', title='提示')
    elif event == 'reload_config':
        auto_organize.config_json = auto_organize.load_config_json()
        auto_organize.file_dict = json.loads(auto_organize.config_json)
        window['out'].update(auto_organize.config_json)
    elif event == 'save_config':
        auto_organize.save_config(values['out'])

程序打包

这里我使用的打包命令是:
pyinstaller -Fw --icon=h.ico auto_organize_gui.py --add-data='h.ico;/'
h.ico是程序的图标文件。打包完成后,我们就可以愉快的使用我们的小工具啦。
关于这一部分,有两个问题需要说明一下。

① 关于图标资源打包的问题

这次在我使用PySimpleGUI开发中,与以往的主要区别是给程序主界面增加了图标,这个使用window窗口函数的第三个参数传入图标路径即可实现,示例:
sg.Window('PySimpleGUI',layout,icon='ico.ico')
但是问题来了,如何将图标文件打包到exe中,并能够顺利被程序读取到呢?
当然解决这个问题另一个麻烦的方法是,是将图标的base64编码硬写到代码中再程序解码,显然这种方案并不太好,修改图标不方便。
若最终将所有文件到打包到一个exe时,运行环境就会有所变化,运行时会临时进行解压,解压的目录为:C:\Users\用户名\AppData\Local\Temp\随机目录名,sys._MEIPASS则存储这个目录的位置。我们可以根据sys模块是否存在_MEIPASS属性来判断是直接运行,还是解压到临时目录再运行。最终定义了如下方法:
def resource_path(relative_path):
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)
打包时使用–add-data参数,添加用 ; 分隔的两个路径。--add-data='h.ico;/'表示将h.ico文件打包进去,运行时解压到根目录下。

② 如何制作ico图标

这里有3种办法,分别介绍如下:
  • 使用ico生成的在线网站;
  • 使用本地软件imagine另存图片为ico;
  • 使用python库PythonMagick;
第一种方法任何人都可以直接用,百度一下,一大堆相关的网站。第二种方法该软件可以通过百度Imagine 看图软件下载到。
可惜PythonMagick库,并不是直接使用pip进行安装。需要到下方网站,下载对应自己的python版本。
https://www.lfd.uci.edu/~gohlke/pythonlibs/#pythonmagick
下载后直接安装whl文件,例如:
pip install PythonMagick-0.9.19-cp37-cp37m-win_amd64.whl
在代码中的调用示例为:
import PythonMagick

img = PythonMagick.Image('D:\h.jpg')
img.sample('128x128')
img.write('h.ico')

以上就是整个小工具的开发流程,完整代码:
https://www.aliyundrive.com/s/Y71N5mauJsh
(0)

相关推荐