使用 Python 和 PyQt 构建批量文件重命名工具

目录

假设您需要使用特定命名模式重命名个人文件夹中的多个文件。手动执行此操作既耗时又容易出错。因此,您正在考虑通过使用 Python构建自己的批量文件重命名工具来自动化文件重命名过程。如果是这样,那么本教程适合您。

在本教程中,您将学习如何:

  • 使用Qt Designer和 PyQt为批量文件重命名工具创建 GUI
  • 使用PyQt 线程卸载文件重命名过程并防止冻结 GUI
  • 使用pathlib管理系统路径和重命名文件
  • 根据重命名过程更新GUI 状态

通过完成本教程中的项目,您将能够应用与 PyQt、Qt 设计器、PyQt 线程以及使用 Python 的pathlib.

您可以通过单击以下链接下载将在本教程中构建的批量文件重命名工具的最终源代码:

演示:使用 Python 和 PyQt 的批量文件重命名工具

在本教程中,您将构建一个批量文件重命名工具来自动化重命名文件系统中给定目录中的多个文件的过程。要构建此应用程序,您将使用 Pythonpathlib来管理文件重命名过程,并使用 PyQt 来构建应用程序的图形用户界面 (GUI)。

以下是本教程结束后批量文件重命名工具的外观和工作方式:

完成应用程序的构建后,您将能够重命名文件系统中的多个文件,这是组织个人文件和文件夹时的一项常见任务。在此示例中,应用程序侧重于图像和 Python 文件,但您可以随时添加其他文件类型。

项目概况

您将在本教程中构建的项目包含一个 GUI 应用程序,该应用程序从给定目录加载多个文件,并允许您使用预定义的文件名前缀和连续数字一次性重命名所有这些文件。在本节中,您将首先了解问题和可能的解决方案。您还将弄清楚如何布置项目。

布置项目

要构建批量文件重命名工具,您将创建一些模块和包,并将它们组织成一致的 Python应用程序布局。您项目的根目录将如下所示:

./rprename_project/
│
├── rprename/
│   │
│   ├── ui/
│   │   ├── __init__.py
│   │   ├── window.py
│   │   └── window.ui
│   │
│   ├── __init__.py
│   ├── app.py
│   ├── rename.py
│   └── views.py
│
├── README.md
├── requirements.txt
└── rprenamer.py

rprename_project/是项目的根目录,您将在其中创建以下文件:

  • README.md提供有关安装和运行应用程序的一般项目描述和说明。README.md为您的项目创建文件被认为是编程的最佳实践,尤其是当您计划将其作为开源解决方案发布时。
  • requirements.txt 提供项目的外部依赖项列表。
  • rprenamer.py 提供一个入口点脚本来运行应用程序。

然后你的rprename/目录将包含一个包含以下模块的 Python 包:

  • __init__.pyrprename/作为 Python 包启用。
  • app.py 提供 PyQt 骨架应用程序。
  • rename.py 提供文件重命名功能。
  • views.py 提供应用程序的 GUI 和相关功能。

ui/子目录将提供一个包来存储与 GUI 相关的代码。它将包含以下文件和模块:

  • __init__.pyui/作为 Python 包启用。
  • window.py包含应用程序主窗口的 Python 代码。您将看到如何使用pyuic5.
  • window.ui保存一个 Qt Designer 文件,其中包含应用程序主窗口XML格式的代码。

继续并使用除window.ui和之外的所有文件和模块创建此目录结构window.py。您将在本教程的后面以及如何使用Qt Designer创建这两个文件pyuic5

要下载项目的目录结构,请单击以下链接:

概述解决方案

您的批量文件重命名工具将是一个功能齐全的 GUI 应用程序。它将允许您从现有目录加载多个文件,并使用描述性文件名前缀和连续数字重命名它们。要构建应用程序,您需要执行以下步骤:

  1. 创建应用程序的 GUI。
  2. 提供加载和重命名多个文件的功能。
  3. 根据文件重命名过程进度更新应用程序的 GUI。

要创建应用程序的 GUI,您将使用 Qt Designer。该工具提供了一个用户友好的界面,通过在空白表单上拖放图形组件(小部件)来创建 GUI。使用此工具,您的 GUI 创建过程将变得快速而高效。

在 Python 中管理文件和目录时,标准库中有几个选项。例如,您必须os.path使用文件系统路径并os使用操作系统功能,例如使用os.rename(). 但是,在本教程中,您将pathlib用于这两个任务。

注意: Python 3.4 添加pathlib到标准库中。此模块提供表示文件系统路径并允许对其进行操作的类。

通常,您将用于pathlib.Path管理应用程序中的文件和目录路径。一旦您拥有Path指向物理文件或目录的.rename()对象,您就可以调用该对象来重命名关联的文件或目录。

接下来,您需要编写将多个文件加载到应用程序中并一次性重命名它们的功能。根据您需要重命名的文件数量,该操作可能需要相当长的时间。这可能会导致应用程序的 GUI 冻结。为了防止 GUI 冻结问题,您将使用QThread.

文件重命名过程需要与应用程序的 GUI 相关联,以便用户知道在任何给定时间发生了什么。在本例中,您将设置一个进度条来反映操作进度。

您还将编写一些代码以确保 GUI 根据文件重命名进度和状态进行更新。至少有两种管理 GUI 更新的一般策略:

  1. 使用条件语句检查状态并采取相应措施
  2. 根据应用程序的状态启用和禁用小部件。

在本项目中,您将使用第二种方法,它可能更直观和用户友好,提供更好的用户体验。

先决条件

要完成本教程并充分利用它,您应该熟悉以下概念:

  • 使用 Python 和 PyQt 创建 GUI 应用程序
  • 使用 Qt Designer 创建 GUI
  • 使用 PyQtQThread卸载长时间运行的任务并防止冻结 GUI
  • 使用pathlib管理系统路径和重命名文件

如果您不具备所有必需的知识,那也没关系!您可以开始本教程,并在需要时花一些时间查看以下资源:

此外,您可以查看以下资源:

在外部软件依赖方面,您的批量文件重命名工具依赖于PyQt v5.15.12。您可以从安装这个库的PyPI使用pip像往常一样:

$ python -m pip install pyqt5

最后,您应该按照 Python 最佳实践的建议创建一个虚拟环境来隔离项目的依赖项。之后,是时候开始使用您自己的批量文件重命名工具了!

步骤 1:构建批量文件重命名工具的 GUI

在本节中,您将使用 Qt Designer 快速创建批量文件重命名工具的 GUI。在此步骤结束时,您将拥有一个 Qt Designer.ui文件,该文件为以下窗口提供所需的代码:

批量文件重命名工具主窗口 GUI

您还将学习如何将.ui文件自动转换为 Python 代码。您将使用该代码为应用程序的主窗口提供 GUI。

要下载这些文件以及您将在本节中编写的所有代码,请单击以下链接:

使用 Qt Designer 创建 GUI

要开始处理批量文件重命名工具的 GUI,请继续并启动 Qt Designer。然后创建一个基于小部件的表单并运行以下视频中显示的步骤:

以下是上述视频中的步骤:

  1. 使用New Form对话框中的Widget模板创建一个新表单,并将其标题设置为RP Renamer
  2. 添加标签并将其文本设置为Last Source Directory:
  3. 添加行编辑以保存所选目录的路径并将其.readOnly属性设置为True.
  4. 添加一个按钮并将其文本设置为&Load Files
  5. 分别添加带有文本Files to RenameRenamed Files 的两个标签,并将它们的字体样式设置为Bold
  6. 添加两个列表小部件,分别显示要重命名的文件和重命名的文件。
  7. 为标签及其相应的列表小部件设置垂直布局。
  8. 使用拆分器连接两种安排。
  9. 添加标签并将其文本设置为Filename Prefix:
  10. 添加行编辑以从用户获取文件名前缀并将其占位符文本设置为Rename your files to...
  11. 添加标签并将其文本设置为.jpg
  12. 添加一个按钮并将其文本设置为&Rename
  13. 添加进度条并将其值设置为0
  14. 调整标签、行编辑和按钮的minimumSizemaximumSize属性,以在用户调整窗口大小时提供一致的调整大小行为。
  15. 将网格布局设置为表单的顶级布局。

完成后,将表单保存window.uirprename/ui/目录下。不要关闭 Qt Designer。继续并更改.objectName以下对象的属性:

Object Value
Form Window
lineEdit dirEdit
pushButton loadFilesButton
listWdget srcFileList
listWidget_2 dstFileList
lineEdit_2 prefixEdit
label_5 extensionLabel
pushButton_2 renameFilesButton

为此,您可以在对象检查器中选择对象并在属性编辑器中更改属性值:

构建批量文件重命名更改对象名称 Qt 设计器

您需要更改对象名称,因为您将在 Python 代码中使用这些名称,因此它们应该更具描述性和可读性。现在继续并单击Qt Designer 工具栏上的保存以使新对象名称持久存在于您的window.ui文件中。您也可以通过按键盘上的Ctrl+S来保存文件。

完成批量文件重命名工具的 GUI 构建后,您可以关闭 Qt Designer,因为您不再需要它。接下来,您需要将刚刚创建的.ui文件内容翻译成 Python 代码。这就是您将在下一节中执行的操作。

将 Qt Designer 的输出转换为 Python 代码

一旦您的.ui文件具有适合您的应用程序的 GUI,您需要将此文件的内容转换为 Python 代码,以便您可以在最终应用程序中加载 GUI。PyQt 提供了一个名为的命令行工具pyuic5,允许您执行此转换。

注意:您也可以.ui直接使用将文件内容加载到您的应用程序中uic.loadUi()。然而,这种策略主要推荐用于小型对话。

如果您查看window.ui文件的内容,就会看到它包含XML代码。该代码定义了应用程序 GUI 的所有图形组件。这是文件内容的片段:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Window</class>
 <widget class="QWidget" name="Window">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>720</width>
    <height>480</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>RP Renamer</string>
  </property>
  <!-- Snip... -->

在这个小片段中,定义的Window是顶级类。此类代表您的主窗口。然后XML代码定义其他类并设置它们的属性。由于 PyQt 提供pyuic5了自动将此XML代码转换为 Python 代码的功能,因此您无需担心.ui文件的内容。您只需要知道如何使用pyuic5.

现在在您的rprename/ui/目录中打开一个终端并运行以下命令:

$ pyuic5 -o window.py window.ui

此命令生成window.pywindow.ui文件调用的 Python 模块并将其放置在您的rprename/ui/目录中。该模块包含用于批量文件重命名工具 GUI 的 Python 代码。这是代码的一个小示例:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'window.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Window(object):
    def setupUi(self, Window):
        Window.setObjectName("Window")
        Window.resize(720, 480)
        # Snip...

    def retranslateUi(self, Window):
        _translate = QtCore.QCoreApplication.translate
        Window.setWindowTitle(_translate("Window", "RP Renamer"))
        # Snip...

Ui_Window提供用于生成批量文件重命名工具的 GUI 的所有代码。在这种情况下,.setupUi()包含用于创建所有必需小部件并将它们布置在 GUI 上的.retranslateUi()代码,同时包含用于国际化和本地化的代码,这超出了本教程的范围。

由于您使用的pyuic5是从现有.ui文件自动生成 Python 代码,因此您无需担心window.py. 就像WARNING!文件顶部的注释一样,如果您使用 Qt Designer 更新 GUI 并重新生成 Python 代码,您对此文件所做的所有更改都将消失。

具有特定于 GUI 的代码的模块强烈鼓励将 GUI 逻辑与业务逻辑分离,这是模型-视图-控制器模式中的基本原则。

至此,您的项目布局已完成。您拥有创建批量文件重命名工具所需的所有文件。现在是时候使用您刚刚创建的 GUI 来组合骨架 PyQt 应用程序了。

第 2 步:创建 PyQt 骨架应用程序

到目前为止,您已经使用 Qt Designer 为批量文件重命名工具构建了一个 GUI。您还习惯于pyuic5.ui文件内容自动转换为 Python 代码,以便您可以在应用程序中使用它。最后,您保存代码到window.pyrprename/ui/目录。

在本节中,您将创建一个 PyQt 框架应用程序并将其主窗口设置为使用您在 中的 GUI 代码window.py。要下载文件和您将在本节中编写的所有代码,请单击以下链接:

设置批量文件重命名工具的窗口

要为批量文件重命名工具创建骨架 PyQt 应用程序,首先需要创建应用程序的主窗口。首先,启动您最喜欢的代码编辑器或 IDE并打开rprename/__init__.py文件。向其中添加以下内容:

# -*- coding: utf-8 -*-
# rprename/__init__.py

"""This module provides the rprename package."""

__version__ = "0.1.0"

除了一些注释和模块docstring 之外,上面的文件定义了一个顶级常量,__version__用于保存应用程序的版本号。

接下来,打开rprename/views.py并写入以下内容:

# -*- coding: utf-8 -*-
# rprename/views.py

"""This module provides the RP Renamer main window."""

from PyQt5.QtWidgets import QWidget

from .ui.window import Ui_Window

class Window(QWidget, Ui_Window):
    def __init__(self):
        super().__init__()
        self._setupUI()

    def _setupUI(self):
        self.setupUi(self)

在 中views.py,您首先QWidgetPyQt5.QtWidgets. 然后Ui_Windowui.window. 正如您之前看到的,此类为批量文件重命名工具提供了 GUI。

接下来,您创建一个名为 的新类Window。这个类使用多重继承。它继承QWidget并继承自您的 GUI 类Ui_WindowQWidget启用基本 GUI 功能,并Ui_Window为此应用程序提供所需的特定 GUI 排列。

注意:您还可以使用组合为您的Window.

例如,您可以这样定义Window

# rprename/views.py
# Snip...

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Window()
        self._setupUI()

    def _setupUI(self):
        self.ui.setupUi(self)

在这种情况下,您将创建.ui为 的实例Ui_Window。从现在开始,您需要使用.ui来访问应用程序 GUI 上的小部件。

在本教程中,您将使用多重继承方法,因为它使所有用户界面组件无需.ui属性即可直接访问。要更深入地了解该主题,请查看在您的应用程序中使用设计器 UI 文件

的初始化Window程序使用 调用基类初始化程序super()。它还调用._setupUI(),这是一个非公共方法,它将收集生成和设置 GUI 所需的所有代码。到目前为止,._setupUI(self)只调用.setupUi()由第二个父类 提供的Ui_Window

注意: Python 不区分私有属性和公共属性。上一段中的术语非公开是指那些不打算在包含类之外使用的属性。这种属性的 Python命名约定_在名称中使用前导下划线 ( )。

Window准备好使用的初始版本后,您可以继续为批量文件重命名工具创建 PyQt 框架应用程序。

创建 PyQt 骨架应用程序

现在您已经准备好使用应用程序的主窗口,是时候编写所需的样板代码来创建 PyQt 应用程序了。返回代码编辑器并打开rprename/app.py文件。然后添加以下代码:

# -*- coding: utf-8 -*-
# rprename/app.py

"""This module provides the RP Renamer application."""

import sys

from PyQt5.QtWidgets import QApplication

from .views import Window

def main():
    # Create the application
    app = QApplication(sys.argv)
    # Create and show the main window
    win = Window()
    win.show()
    # Run the event loop
    sys.exit(app.exec())

在此模块中,您导入sys访问exit(). 此功能允许您在用户关闭主窗口时干净地退出应用程序。然后你QApplicationPyQt5.QtWidgetsWindowviews. 最后一步是定义main()应用程序的主要功能。

在 中main(),您实例化QApplicationWindow。然后调用.show()Window。最后,运行应用程序的主循环事件循环使用.exec()

编写应用程序的入口点脚本

有了 PyQt 框架应用程序,您可以创建合适的入口点脚本,以便快速运行应用程序。rprenamer.py从根目录打开该文件并在其中键入以下代码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# rprenamer.py

"""This module provides the RP Renamer entry-point script."""

from rprename.app import main

if __name__ == "__main__":
    main()

这个脚本相当小。您首先main()从您的app.py模块导入。然后您使用传统的 Python 条件语句,main()如果用户将模块作为 Python 脚本运行,该语句会调用。

现在您可以打开终端并运行脚本来启动应用程序。您将在屏幕上看到以下窗口:

批量文件重命名工具主窗口 GUI

凉爽的!您的批量文件重命名工具有一个很好的 GUI,它提供了一个按钮来加载您要重命名的文件。顶行编辑将显示源目录的路径。左侧列表小部件将显示要重命名的文件列表,右侧列表小部件将显示重命名的文件。

要启动文件重命名过程,用户需要提供文件名前缀,然后单击Rename。底部的进度条将显示文件重命名进度。

在下一部分中,您将编写所需的代码来提供应用程序的主要功能,一次性重命名多个文件。所以关闭应用程序的窗口以继续添加功能。

第 3 步:使用pathlibPyQt 线程重命名文件

要为批量文件重命名工具实现文件重命名功能,您将使用 Pythonpathlib和 PyQt QThread。使用pathlib,您将管理文件系统路径并重命名文件。随着QThread,在另一方面,你会在一个单独的执行线程重命名文件。为什么?

好吧,根据您要重命名的文件数量,重命名过程可能需要相当长的时间。在应用程序的主线程中启动长时间运行的任务可能会冻结 GUI,进而导致糟糕的用户体验。

为避免 GUI 冻结问题,您可以创建一个工作程序QThread来卸载文件重命名过程并使您的应用程序响应。

同样,您可以通过单击下面的链接下载将在本节中编写的所有代码:

加载和显示目标文件

要开始重命名文件,您首先需要一种将这些文件加载​​到应用程序中的方法。PyQt 提供了一个名为的类QFileDialog,允许您使用预定义的对话框从文件系统中选择文件或目录。选择要重命名的文件后,您需要将它们的路径存储在方便的数据结构中。

返回rprename/views.py并更新代码,如下所示:

# -*- coding: utf-8 -*-
# rprename/views.py

"""This module provides the RP Renamer main window."""

from collections import deque
from pathlib import Path

from PyQt5.QtWidgets import QFileDialog, QWidget

from .ui.window_ui import Ui_Window

FILTERS = ";;".join(
    (
        "PNG Files (*.png)",
        "JPEG Files (*.jpeg)",
        "JPG Files (*.jpg)",
        "GIF Files (*.gif)",
        "Text Files (*.txt)",
        "Python Files (*.py)",
    )
)

class Window(QWidget, Ui_Window):
    # Snip...

在这里,您首先dequecollections. 双端队列队列的概括。它们支持从双端队列的任一侧进行高效的追加和弹出操作。在这种情况下,您将使用双端队列来存储需要重命名的文件的路径。

您还可以Pathpathlib. 此类可以表示文件系统中的具体文件或目录路径。您将使用此类对文件和目录执行不同的操作。在本教程中,您将使用Path.rename()重命名硬盘驱动器中的物理文件。

然后QFileDialogPyQt5.QtWidgets. 此类提供了一个适当的对话框来从给定目录中选择文件。该FILTER常量将不同的文件过滤器指定为字符串。这些过滤器允许您在将文件加载到应用程序时从不同的文件类型中进行选择。

保持views.py打开状态并Window像这样更新:

 1# rprename/views.py
 2# Snip...
 3
 4class Window(QWidget, Ui_Window):
 5    def __init__(self):
 6        super().__init__()
 7        self._files = deque()
 8        self._filesCount = len(self._files)
 9        self._setupUI()
10        self._connectSignalsSlots()
11
12    def _setupUI(self):
13        self.setupUi(self)
14
15    def _connectSignalsSlots(self):
16        self.loadFilesButton.clicked.connect(self.loadFiles)
17
18    def loadFiles(self):
19        self.dstFileList.clear()
20        if self.dirEdit.text():
21            initDir = self.dirEdit.text()
22        else:
23            initDir = str(Path.home())
24        files, filter = QFileDialog.getOpenFileNames(
25            self, "Choose Files to Rename", initDir, filter=FILTERS
26        )
27        if len(files) > 0:
28            fileExtension = filter[filter.index("*") : -1]
29            self.extensionLabel.setText(fileExtension)
30            srcDirName = str(Path(files[0]).parent)
31            self.dirEdit.setText(srcDirName)
32            for file in files:
33                self._files.append(Path(file))
34                self.srcFileList.addItem(file)
35            self._filesCount = len(self._files)

这是新添加的代码的作用:

  • 第 7 行创建._files一个双端队列对象。此属性将存储您要重命名的文件的路径。

  • 第 8 行定义._filesCount存储要重命名的文件数。

  • 10号线电话._connectSignalsSlots()

  • 第 15 行定义了._connectSignalsSlots(). 此方法将在一个地方收集多个信号和插槽连接。到目前为止,该方法将Load Files按钮的.clicked()信号与.loadFiles()插槽连接起来。这使得.loadFiles()每次用户单击按钮时都可以触发。

然后定义.loadFiles()加载要重命名的文件。这是它的作用:

  • .dstFileList每次用户单击Load Files时,第 19 行都会清除列表小部件。

  • 第 20 到 23 行定义了一个条件语句,用于检查Last Source Directory行编辑当前是否正在显示任何目录路径。如果是,则if代码块设置初始目录initDir, 以保存该路径。否则,初始目录设置为Path.home(),返回当前用户主文件夹的路径。您将用于initDirQFileOpen对象提供一个初始化目录。

  • 线24至26的呼叫.getOpenFileNames()QFileDialog。此静态方法接受多个参数,创建一个对话框以允许用户选择一个或多个文件,并返回所选文件的基于字符串的路径列表。它还返回当前使用的文件过滤器。在这种情况下,您使用以下参数:

    • parent持有拥有对话框的小部件,在这种情况下是self或当前Window对象。

    • caption保存对话框的标题或标题。您"Choose Files to Rename"在此示例中使用字符串。

    • dir保存初始化目录的路径。换句话说,就是打开对话框的目录的路径。在这个例子中,你initDir用来初始化对话框。

    • filter保存文件类型过滤器,以便仅在对话框中显示与过滤器匹配的文件。例如,如果您将过滤器设置为"*.jpg",则对话框会显示与此格式匹配的文件。

  • 第 27 行定义了一个条件语句,如果用户从QFileDialog.

  • 第 28 行对当前过滤器字符串进行切片以提取文件扩展名。

  • 第 29.extensionLabel行将对象的文本设置为在第 28 行提取的文件扩展名。

  • 第 30 行检索包含所选文件的目录的路径。为此,您可以Path使用所选文件列表中第一个文件的路径创建一个对象。该.parent属性保存包含目录的路径。

  • 第 31.dirEdit行将行编辑的文本设置为您在第 30 行获得的目录路径。

  • 第 32 到 34 行定义了一个for循环,该循环遍历所选文件的列表,Path为每个文件创建一个对象,并将其附加到._files. 第 34 行将每个文件添加到.srcFileList列表小部件,以便用户可以在应用程序的 GUI 上看到当前选择的文件。

  • 第 35 行更新._filesCount列表中的当前文件数。

如果您现在运行该应用程序,那么您将获得以下行为:

现在,您可以通过单击加载文件将多个文件加载到批量文件重命名工具中。请注意,您可以通过从“选择要重命名的文件”对话框中的多个文件过滤器中进行选择来指定要加载到应用程序中的文件类型。

注意:上述代码和本教程中其余代码示例中的行号旨在方便解释。它们与最终模块或脚本中的行顺序不匹配。

凉爽的!您的项目已经支持加载不同类型的文件。继续并关闭应用程序以继续编码。在下一部分中,您将实现文件重命名功能。

在 Worker 中重命名多个文件 QThread

要执行文件重命名过程,您将使用一个QThread对象。创建和设置工作线程允许您从应用程序的主线程卸载文件重命名过程。这样可以防止在选择大量文件进行重命名时可能出现的 GUI 冻结问题。

工作线程将使用pathlib.rename(). 它还将发出自定义信号以与主线程通信并更新应用程序的 GUI。继续并rprename/rename.py在您的代码编辑器中打开该文件。输入以下代码:

 1# -*- coding: utf-8 -*-
 2# rprename/rename.py
 3
 4"""This module provides the Renamer class to rename multiple files."""
 5
 6import time
 7from pathlib import Path
 8
 9from PyQt5.QtCore import QObject, pyqtSignal
10
11class Renamer(QObject):
12    # Define custom signals
13    progressed = pyqtSignal(int)
14    renamedFile = pyqtSignal(Path)
15    finished = pyqtSignal()
16
17    def __init__(self, files, prefix):
18        super().__init__()
19        self._files = files
20        self._prefix = prefix
21
22    def renameFiles(self):
23        for fileNumber, file in enumerate(self._files, 1):
24            newFile = file.parent.joinpath(
25                f"{self._prefix}{str(fileNumber)}{file.suffix}"
26            )
27            file.rename(newFile)
28            time.sleep(0.1)  # Comment this line to rename files faster.
29            self.progressed.emit(fileNumber)
30            self.renamedFile.emit(newFile)
31        self.progressed.emit(0)  # Reset the progress
32        self.finished.emit()

下面是这段代码的作用:

  • 9吨线的进口QObjectpyqtSignal()PyQt5.QtCoreQObject允许您创建具有自定义信号和功能的子类。使用pyqtSignal(),您可以创建自定义信号,以便在发生给定事件时发出它们。

  • 第 11 行定义了QObjectcalled的子类Renamer

  • 第 13 到 15 行定义了三个自定义信号:

    1. .progressed()每次类重命名新文件时都会发出。它返回一个整数,表示当前重命名的文件的编号。您将使用此数字来更新应用程序 GUI 中的进度条。

    2. .renamedFile()每当类重命名文件时都会发出。在这种情况下,信号返回重命名文件的路径。您将使用此路径来更新应用程序 GUI 中的重命名文件列表。

    3. .finished() 将在文件重命名过程完成时发出。

第 17 行的类初始化器接受两个必需的参数:

  1. files保存所选文件的列表。每个文件由其对应的Path.

  2. prefix 保存您将用于重命名文件的文件名前缀。

然后定义执行文件重命名过程的方法,.renameFiles(). 以下是其工作原理的摘要:

  • 第 23 行定义了一个for循环来迭代所选文件的列表。循环用于在循环enumerate()进行时生成文件编号。

  • 第 24 行使用文件名前缀fileNumber和文件扩展名构建新文件名.suffix。然后它将新文件名与父目录路径连接起来以创建手头文件的路径newFile.

  • 第 27 行通过调用.rename()当前filewithnewFile作为参数来重命名当前文件。

  • 第 28 行是一个可选调用,time.sleep()它会减慢文件重命名过程,以便您查看它是如何进行的。随意删除此行以以其自然速度运行应用程序。

  • 第 29 和 30 行发出.progressed().renamedFile()信号。在下一部分中,您将使用这些信号来更新应用程序的 GUI。

  • 第 31 行发出.progressed()using0作为参数。完成文件重命名过程后,您将使用此值重置进度条。

  • .finished()当文件重命名过程完成时,第 32 行发出信号。

编码完成后Renamer,您就可以开始重命名文件了。为此,您需要创建并设置一个工作线程。但首先,返回rprename/views.py并更新其导入部分,如下所示:

# rprename/views.py
# Snip...

from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QFileDialog, QWidget

from .rename import Renamer
from .ui.window import Ui_Window
# Snip...

在突出显示的第一行中,您QThreadPyQt5.QtCore. 此类允许您在 PyQt 应用程序中创建和管理工作线程。在突出显示的第二行中,您Renamerrename模块导入。

现在在您的views.py文件中向下滚动一点并Window像这样更新:

 1# rprename/views.py
 2# Snip...
 3
 4class Window(QWidget, Ui_Window):
 5    # Snip...
 6    def _connectSignalsSlots(self):
 7        self.loadFilesButton.clicked.connect(self.loadFiles)
 8        self.renameFilesButton.clicked.connect(self.renameFiles)
 9
10    def loadFiles(self):
11        # Snip..
12
13    def renameFiles(self):
14        self._runRenamerThread()
15
16    def _runRenamerThread(self):
17        prefix = self.prefixEdit.text()
18        self._thread = QThread()
19        self._renamer = Renamer(
20            files=tuple(self._files),
21            prefix=prefix,
22        )
23        self._renamer.moveToThread(self._thread)
24        # Rename
25        self._thread.started.connect(self._renamer.renameFiles)
26        # Update state
27        self._renamer.renamedFile.connect(self._updateStateWhenFileRenamed)
28        # Clean up
29        self._renamer.finished.connect(self._thread.quit)
30        self._renamer.finished.connect(self._renamer.deleteLater)
31        self._thread.finished.connect(self._thread.deleteLater)
32        # Run the thread
33        self._thread.start()
34
35    def _updateStateWhenFileRenamed(self, newFile):
36        self._files.popleft()
37        self.srcFileList.takeItem(0)
38        self.dstFileList.addItem(str(newFile))

在这里,您首先将重命名按钮连接.clicked().renameFiles()。此方法调用._runRenamerThread()以创建、设置和运行工作线程。这是它的工作原理:

  • 第 17 行检索Filename Prefix行编辑中的文本。用户需要提供此文件名前缀。

  • 第 18 行创建了一个新QThread对象来卸载文件重命名过程。

  • 第 19 到 22 行实例化Renamer,将文件列表和文件名前缀作为参数传递给类构造函数。在这种情况下,你变成._files了一个元组,以防止线程修改主线程上的底层双端队列。

  • 第23所调用.moveToThread()Renamer实例。顾名思义,此方法将给定对象移动到不同的执行线程。在这种情况下,它._thread用作目标线程。

  • 第 25行将线程的.started()信号连接.renameFiles()Renamer实例上。这使得可以在线程启动时启动文件重命名过程。

  • 第 27行将Renamer实例的.renamedFile()信号与连接起来._updateStateWhenFileRenamed()。您将在一分钟内看到此方法的作用。

  • 第 29 行和第 30行将Renamer实例的.finished()信号与两个插槽连接起来:

    1. 线程的.quit()槽,一旦文件重命名过程完成就退出线程

    2. Renamer实例的.deleteLater()插槽,这时间表以后删除对象

  • 第 31行将线程的.finished()信号与.deleteLater(). 这使得删除线程成为可能,但只有在它完成其工作后才能删除。

  • 第 33 行启动工作线程。

最后一段代码定义了._updateStateWhenFileRenamed(). 当文件被重命名时,该方法从要重命名的文件列表中删除该文件。然后该方法更新要重命名的文件列表以及应用程序 GUI 上的重命名文件列表。

以下是该应用程序现在的工作方式:

就是这样!您的批量文件重命名工具已经完成了它的工作。它允许您加载多个文件,提供新的文件名前缀,并重命名所有选定的文件。很好!

现在您可以关闭应用程序以继续开发过程。在下一节中,您将学习如何根据文件重命名进度更新应用程序的 GUI。

步骤 4:根据重命名进度更新 GUI 状态

到目前为止,您的批量文件重命名工具提供了它的主要功能。您已经可以使用该工具重命名文件系统中的多个文件。但是,如果用户在重命名过程中单击重命名会发生什么?另外,如果用户忘记提供适当的文件名前缀怎么办?

另一个更明显的问题是为什么应用程序的进度条没有反映文件重命名进度。

所有这些问题都与在任何给定时间根据应用程序的状态更新 GUI 相关。在本节中,您将编写所需的代码来修复或防止上述问题。您将从进度条开始。

您可以通过单击下面的链接下载将在本节中编写的代码:

更新进度条

在 GUI 应用程序中,您通常使用进度条来通知用户有关长时间运行任务的进度。如果用户没有得到关于应用程序当前正在做什么的反馈,那么他们可能认为应用程序被冻结、卡住或存在一些内部问题。

在此项目中,您将使用进度条提供有关文件重命名过程在任何给定时间的进展情况的反馈。为此,请返回rprename/views.py代码编辑器中的 并像这样更新它:

 1# rprename/views.py
 2# Snip...
 3
 4class Window(QWidget, Ui_Window):
 5    # Snip...
 6    def _runRenamerThread(self):
 7        # Update state
 8        self._renamer.renamedFile.connect(self._updateStateWhenFileRenamed)
 9        self._renamer.progressed.connect(self._updateProgressBar)
10        # Snip...
11
12    def _updateStateWhenFileRenamed(self, newFile):
13        # Snip...
14
15    def _updateProgressBar(self, fileNumber):
16        progressPercent = int(fileNumber / self._filesCount * 100)
17        self.progressBar.setValue(progressPercent)

这是新添加的代码的作用:

  • 第 9行将Renamer实例的.progressed()信号与连接起来._updateProgressBar()
  • 第 15 行定义了._updateProgressBar(). 该方法将 afileNumber作为参数。请注意,.progressed()每次重命名文件时都会提供此文件编号。
  • 第 16 行计算文件重命名进度占文件总数的百分比,在._filesCount.
  • 第 17 行.value使用.setValue()withprogressPercent作为参数更新进度条上的属性。

就是这样!继续并再次运行您的应用程序。它会像这样工作:

对 进行这些添加后Window,您的批量文件重命名工具的进度条会反映文件重命名操作的进度,这很好,可以改善您的用户体验。

启用和禁用 GUI 组件

当您构建 GUI 应用程序时,您会意识到 GUI 上的某些操作仅在某些情况下可用。例如,在您的批量文件重命名工具中,如果没有文件已加载到应用程序中或者用户没有提供文件名前缀,则允许用户单击重命名是没有意义的。

至少有两种方法可以处理这种情况:

  1. 始终启用所有小部件,并确保它们不会触发任何在上下文中没有意义的操作。
  2. 根据应用程序的状态启用和禁用小部件。

要实现第一种方法,您可以使用条件语句来确保给定的操作在给定的时刻有意义。另一方面,要实现第二种方法,您需要找出应用程序可能遇到的所有可能状态,并提供相应地更新 GUI 的方法。

注意:当您禁用 PyQt 小部件或图形组件时,库将手头的小部件变灰并使其对用户事件(例如单击和按键)无响应。

在本教程中,您将使用第二种方法,它可能更直观和用户友好。为此,您需要弄清楚应用程序可能遇到的状态。这是解决此问题的第一种方法:

State Description Method
No files loaded The application is running, and the list of Files to Rename is empty. ._updateStateWhenNoFiles()
Files loaded The list of Files to Rename contains one or more files. ._updateStateWhenFilesLoaded()
Ready to rename files The list of Files to Rename contains one or several files, and the user has provided a filename prefix. ._updateStateWhenReady()
Renaming files The user has clicked Rename, and the file renaming process has started. ._updateStateWhileRenaming()
Renaming done The file renaming process has finished, and there are no left files to rename. ._updateStateWhenNoFiles()

由于上表中的第一个和最后一个状态非常相似,因此您将使用相同的方法来更新 GUI。这意味着您只需要实现四种方法。

要开始编写这些方法,请返回views.py并将以下代码添加到Window

 1# rprename/views.py
 2# Snip...
 3
 4class Window(QWidget, Ui_Window):
 5    # Snip...
 6    def _setupUI(self):
 7        self.setupUi(self)
 8        self._updateStateWhenNoFiles()
 9
10    def _updateStateWhenNoFiles(self):
11        self._filesCount = len(self._files)
12        self.loadFilesButton.setEnabled(True)
13        self.loadFilesButton.setFocus(True)
14        self.renameFilesButton.setEnabled(False)
15        self.prefixEdit.clear()
16        self.prefixEdit.setEnabled(False)
17
18   # Snip...
19   def _runRenamerThread(self):
20        # Snip...
21        self._renamer.progressed.connect(self._updateProgressBar)
22        self._renamer.finished.connect(self._updateStateWhenNoFiles)
23        # Snip...

以下是此更新的工作原理:

  • 8号线._updateStateWhenNoFiles()从里面呼叫._setupUI()。这会在您启动应用程序时更新 GUI。
  • 第 10 行定义了._updateStateWhenNoFiles().
  • 第 11 行更新文件总数。在这种状态下,len(self._files)返回0
  • 第 12 行启用Load Files按钮,以便它接受用户的事件。
  • 第 13行将焦点移至Load Files按钮。这样,用户可以按下Space键盘将文件加载到应用程序中。
  • 第 14 行禁用重命名按钮。该按钮呈灰色且无响应。
  • 第 15 行清除Filename Prefix行编辑。这将删除任何先前提供的文件名前缀。
  • 第 16 行禁用文件名前缀行编辑。这样,如果应用程序中没有文件,用户将无法输入文件名前缀。
  • 第 22行将Renamer实例的.finished()信号与连接起来._updateStateWhenNoFiles()。一旦文件重命名过程完成,这将更新 GUI。

运行应用程序时,您可以按Space键盘上的 启动对话框并选择要重命名的文件。此外,文件名前缀行编辑和重命名按钮被禁用,因此您无法对它们执行操作。

现在,您可以编写代码以在将文件加载到应用程序时更新 GUI。将以下代码添加到Window

# rprename/views.py
# Snip...

class Window(QWidget, Ui_Window):
    # Snip...
    def loadFiles(self):
        if len(files) > 0:
            # Snip...
            self._updateStateWhenFilesLoaded()

    def _updateStateWhenFilesLoaded(self):
        self.prefixEdit.setEnabled(True)
        self.prefixEdit.setFocus(True)

当您将一个或多个文件加载到应用程序中时,.loadFiles()调用._updateStateWhenFilesLoaded(). 此方法启用文件名前缀行编辑,以便您可以输入用于重命名文件的前缀。它还将焦点移到该小部件,以便您可以立即提供文件名前缀。

接下来,您可以编写方法来在应用程序准备好重命名一堆文件时处理 GUI 更新。将以下代码添加到Window

# rprename/views.py
# Snip...

class Window(QWidget, Ui_Window):
    # Snip...
    def _connectSignalsSlots(self):
        # Snip...
        self.prefixEdit.textChanged.connect(self._updateStateWhenReady)

    def _updateStateWhenReady(self):
        if self.prefixEdit.text():
            self.renameFilesButton.setEnabled(True)
        else:
            self.renameFilesButton.setEnabled(False)

在这里,您首先将Filename Prefix行编辑的.textChanged()信号与._updateStateWhenReady(). 此方法根据文件名前缀行编辑的内容启用或禁用重命名按钮。这样,当行编辑为空时,按钮被禁用,否则它被启用。

最后,当应用程序重命名文件时,您需要编写处理 GUI 更新的方法。继续并将以下代码添加到Window

class Window(QWidget, Ui_Window):
    # Snip...
    def renameFiles(self):
        self._runRenamerThread()
        self._updateStateWhileRenaming()

    def _updateStateWhileRenaming(self):
        self.loadFilesButton.setEnabled(False)
        self.renameFilesButton.setEnabled(False)

当您的用户单击Rename 时,应用程序将启动文件重命名过程并相应地调用._updateStateWhileRenaming()以更新 GUI。该方法禁用“加载文件”和“重命名”按钮,因此用户在重命名过程运行时无法单击它们。

就是这样!如果您现在运行该应用程序,那么您将获得以下行为:

您的批量文件重命名工具的 GUI 现在可以在任何给定时间反映应用程序的状态。这使您可以为用户提供直观、无挫折的体验。很好!

结论

组织个人文件和文件夹时,自动重命名多个文件是一个常见问题。在本教程中,您构建了一个真实的 GUI 应用程序来快速有效地执行此任务。通过构建此工具,您应用了与使用 PyQt 和 Qt Designer 创建 GUI 应用程序相关的广泛技能。您还使用 Python 的pathlib.

在本教程中,您学习了如何:

  • 使用Qt Designer构建批量文件重命名工具的 GUI
  • 使用PyQt 线程卸载批量文件重命名过程
  • 管理系统路径并重命名文件 pathlib
  • 根据文件重命名过程更新GUI 状态

您可以通过单击以下链接下载批量文件重命名项目的最终源代码:

下一步

到目前为止,您已经构建了一个真实世界的应用程序来自动化重命名多个文件的过程。尽管该应用程序提供了最少的功能集,但它是您继续添加功能并在此过程中学习的一个很好的起点。这将帮助您将 Python 和 PyQt GUI 应用程序的技能提升到一个新的水平。

以下是您可以实施以继续改进项目的一些想法:

  • 增加支持更多的文件类型:您可以添加新的文件类型,如支持.bmp.docx.xlsx,或者您需要与工作的任何其他文件类型。为此,您可以扩展FILTERS常量。

  • 生成基于日期或随机的文件名后缀:您还可以更改生成文件名后缀的方式。您可以使用 提供基于日期的后缀datetime,而不是使用连续整数,也可以提供随机后缀。

  • 为应用程序生成可执行文件:您可以使用PyInstaller或任何其他工具为批量文件重命名工具生成可执行文件。这样,您就可以与您的朋友和同事共享应用程序。

这些只是关于如何继续向批量文件重命名工具添加功能的一些想法。接受挑战,打造令人惊叹的东西!

(完)