最近,我在学习Python,并结合工作中的相关问题,发现经常需要合并文件。有的软件要求会员,有的操作起来比较繁琐。而我的需求很简单,只是需要合并文件而已。因此,我决定自己编写一个合并文件的程序。

在编写程序的过程中,我首先想到的是合并PDF文件。因为PDF文件在我们的工作中非常常见,而且有时候我们需要将它们合并在一起。通过Python,我们可以轻松地实现PDF文件的合并。

除了PDF文件,我还考虑到了Word、Txt和Excel文件。这些文件类型在我们的工作中也非常常见,而且有时候我们需要将它们合并在一起。因此,我扩展了我的程序,使其能够支持这些文件类型的合并。

此外,我还添加了一个输出终端,将程序错误及成功相关的输出都将反馈到ui界面上,以便在程序运行时能够及时发现并处理错误。这些功能可以帮助我们更好地了解程序的运行情况,并及时发现并解决问题。

以上是我初学Python时写的一个简单的文件合并程序。如果有任何问题或建议,欢迎随时告诉我。我会根据大家的反馈不断优化程序,使其更加易用、高效和稳定。

# @Author  : D
from PySide6 import QtWidgets, QtGui, QtCore
from PySide6.QtWidgets import QApplication,QMainWindow,QWidget,QVBoxLayout,QMessageBox,QFileDialog
from PySide6.QtCore import QFile
from PySide6.QtUiTools import QUiLoader
import sys
import os
import openpyxl

from PySide6.QtGui import QTextCursor

from pypdf import PdfReader, PdfWriter

from docx import Document
import copy


class StdOutRedirector: # 捕捉控制台标准输出
    def __init__(self, widget):
        self.widget = widget

    def write(self, text):
        cursor = self.widget.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText(text)
        self.widget.setTextCursor(cursor)
        self.widget.ensureCursorVisible()

    def flush(self):
        pass

class StdErrRedirector: # 捕捉控制台错误输出
    def __init__(self, widget):
        self.widget = widget

    def write(self, text):
        cursor = self.widget.textCursor()
        cursor.movePosition(QtGui.QTextCursor.End)
        cursor.insertText(text)
        self.widget.setTextCursor(cursor)
        self.widget.ensureCursorVisible()

    def flush(self):
        pass

class win_Main(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
 
        self.bgo = Bgo(self)  # 创建Bgo实例时传入self
        self.init_ui()

    def init_ui(self):
        self.main_ui = QUiLoader().load('main.ui')
        self.layout = QVBoxLayout(self)   #该函数在Python的Qt库中创建了一个垂直布局(QVBoxLayout),并将此布局应用到self(通常是一个GUI组件如QWidget)上,目的是使self内的控件按照垂直方向进行排列和管理。
        self.layout.addWidget(self.main_ui)  # 添加UI
        self.setLayout(self.layout)  # 设置布局

        self.main_ui.comboBox.currentIndexChanged.connect(self.handleSelectionChange)

        self.stdout_redirector = StdOutRedirector(self.main_ui.plainTextEdit) # 创建一个 StdOutRedirector 实例,并将其与 QPlainTextEdit 关联
        self.stderr_redirector = StdErrRedirector(self.main_ui.plainTextEdit)

        # 重定向 sys.stdout 和 sys.stderr
        sys.stdout = self.stdout_redirector   
        sys.stderr = self.stderr_redirector 
    
        self.main_ui.pushButton3.clicked.connect(self.file_dir) # 点击选择文件按钮选择需要统计的文件
        self.main_ui.pushButton3_4.clicked.connect(self.row_and_column) # 点击确定需要复制的行列
        
        self.main_ui.pushButton3_3.clicked.connect(self.bgo.merge_file)
        
        self.main_ui.pushButton3_5.clicked.connect(self.bgo.openfile)

    def handleSelectionChange(self):
        chage1  =self.main_ui.comboBox.currentText() # 获取选择的合并内容
        print(chage1) # 打印选择的哪项  
        if chage1  != "合并execl表格(xlsx)":
          
            # self.main_ui.label.setHidden(True) 
            self.main_ui.label_2.setHidden(True) 
            self.main_ui.label_3.setHidden(True) # 隐藏
            self.main_ui.label_4.setHidden(True) # 隐藏
            self.main_ui.label_5.setHidden(True) # 隐藏
            self.main_ui.lineEdit.setHidden(True) # 隐藏
            self.main_ui.lineEdit_5.setHidden(True) # 隐藏
            self.main_ui.lineEdit_2.setHidden(True) # 隐藏
            self.main_ui.lineEdit_3.setHidden(True) # 隐藏
            self.main_ui.lineEdit_4.setHidden(True) # 隐藏
            self.main_ui.pushButton3_4.setHidden(True) 
        
        else:
            # self.main_ui.label.setHidden(False) 
            self.main_ui.label_2.setHidden(False) 
            self.main_ui.label_3.setHidden(False) # 隐藏
            self.main_ui.label_4.setHidden(False) # 隐藏
            self.main_ui.label_5.setHidden(False) # 隐藏
            self.main_ui.lineEdit.setHidden(False) # 隐藏
            self.main_ui.lineEdit_5.setHidden(False) # 隐藏
            self.main_ui.lineEdit_2.setHidden(False) # 隐藏
            self.main_ui.lineEdit_3.setHidden(False) # 隐藏
            self.main_ui.lineEdit_4.setHidden(False) # 隐藏
            self.main_ui.pushButton3_4.setHidden(False) 

    def file_dir(self): # 路径文件夹选择
        FolderName = QFileDialog.getExistingDirectory(None, "选择需要合并的目录")
        if FolderName:
            self.main_ui.lineEdit3.setText(FolderName)
            self.bgo.coun_file(FolderName)
        
    def row_and_column(self):
        
        min_row_ui = self.main_ui.lineEdit.text()
        max_row_ui = self.main_ui.lineEdit_2.text()

        min_column_ui = self.main_ui.lineEdit_3.text()
        max_column_ui = self.main_ui.lineEdit_4.text()

        if min_row_ui == "":
            min_row_ui = 1
        if max_row_ui == "":
            max_row_ui = "默认最后一行"
        if min_column_ui == "":
            min_column_ui = 'A'
        if max_column_ui == "":
            max_column_ui = "默认最大列"
        
        print(f"你所确定的行号为{min_row_ui}至{max_row_ui},列号为{min_column_ui}至{max_column_ui}")
        
class Bgo: # 后台操作

    def __init__(self, main_instance):
        self.main_instance = main_instance  # 保存win_Main实例的引用
    
    def coun_file(self,dirname):
        filelist_1 = os.listdir(dirname) # 对目标下的进行列表
        quantity_of_documents = len(filelist_1) # 计数一共有多少个文件
        print(f"选择的文件内一共有“{quantity_of_documents}”个文件,请查看是否正常,如不正常,请查看相关文件的后缀问题。")

    def merge_file(self):
        path_file = self.main_instance.main_ui.lineEdit3.text()
        
        if path_file == "":
            print("请先选择合并的路径")
        else:
            change_file = self.main_instance.main_ui.comboBox.currentText()
            if change_file =="合并execl表格(xlsx)":
                self.exec_merge()
            elif change_file== "合并pdf":
                self.pdf_merge()
            elif change_file == "合并word文档(docx)":
                self.word_merge()
            else:
                self.txt_merge()

    def exec_merge(self):

        pathname = self.main_instance.main_ui.lineEdit3.text()   # 获取文件路径
        sheet_name_ui = self.main_instance.main_ui.lineEdit_5.text()  # 获取sheet表名称
       
        # 如果pathname是一个目录,获取其中的文件列表
        if os.path.isdir(pathname):
            pathname = [os.path.join(pathname, filename) for filename in os.listdir(pathname)]
            
            wb2 =  openpyxl.Workbook()
            ws2 = wb2.active

        # 检查每个文件是否为Excel格式
        valid_files = []  # 都有什么文件
        for i in pathname:
            if os.path.isfile(i) and i.endswith(('.xlsx', '.xlsm', '.xltx', '.xltm')): # 判断是否为文件并且是否是相关后缀名
                valid_files.append(i)   
        if valid_files:
            for i in valid_files:
                wb = openpyxl.load_workbook(i)
                if sheet_name_ui == '':
                    ws = wb.active
                else:
                    ws = wb[sheet_name_ui]
                min_row_ui = int(self.main_instance.main_ui.lineEdit.text()) 
                max_row_ui = int(self.main_instance.main_ui.lineEdit_2.text())

                min_column_ui =int(self.main_instance.main_ui.lineEdit_3.text())
                max_column_ui = int(self.main_instance.main_ui.lineEdit_4.text())
                if min_row_ui == "":
                    min_row_ui = ws.min_row
                if max_row_ui == "":
                    max_row_ui = ws.max_row
                if min_column_ui == "":
                    min_column_ui = ws.min_column
                if max_column_ui == "":
                    max_column_ui = ws.max_column

                for row in ws.iter_rows(min_row=min_row_ui, max_row=max_row_ui, min_col=min_column_ui, max_col=max_column_ui, values_only=True):
                    ws2.append(row)                
                print(f"{i}合并完成")

                wb2.save("统计表.xlsx")
            print("\n完成")
        else:
            print("无文件")

    def pdf_merge(self):
        pathname = self.main_instance.main_ui.lineEdit3.text()   # 获取目录路径

        # 如果pathname是一个目录,获取其中的文件列表
        if os.path.isdir(pathname):
            pathname = [os.path.join(pathname, filename) for filename in os.listdir(pathname)]
            
        # 检查每个文件是否为Excel格式
        valid_files = []  # 都有什么文件
        for i in pathname:
            if os.path.isfile(i) and i.endswith(('.pdf')): # 判断是否为文件并且是否是相关后缀名
                valid_files.append(i)   
        
        pdf_writer = PdfWriter()
        try:
            for path in valid_files:
                pdf_reader = PdfReader(path)
                for page in range(len(pdf_reader.pages)):
                    pdf_writer.add_page(pdf_reader.pages[page]) # 调用add_page()方法,将PDF文件中的每一页添加到pdf_writer
            output = "pdf合并.pdf"
            with open(output, 'wb') as out:
                pdf_writer.write(out)
        except Exception as e:
            print("合并pdf时发生错误", e)              
        
        print("pdf合并完成")

    def word_merge(self):
        master_doc = Document()  # 创建一个新的空白文档
        pathname = self.main_instance.main_ui.lineEdit3.text()   # 获取目录路径

        # 如果pathname是一个目录,获取其中的文件列表
        if os.path.isdir(pathname):
            pathname = [os.path.join(pathname, filename) for filename in os.listdir(pathname)]
            
        # 检查每个文件是否为Excel格式
        valid_files = []  # 都有什么文件
        for i in pathname:
            if os.path.isfile(i) and i.endswith(('.docx')): # 判断是否为文件并且是否是相关后缀名
                valid_files.append(i)   
        
        for doc_path in valid_files:
            print(doc_path)
            sub_doc = Document(doc_path)  # 打开子文档
            
            # 为每个子文档添加一个空行,以区分不同的文档(可选)
            master_doc.add_paragraph("")
            
            # 将每个子文档的内容添加到主文档中
            for element in sub_doc.element.body:
                # 使用deepcopy来复制元素,以保留格式和样式
                cloned_element = copy.deepcopy(element) 
                # 将克隆的元素添加到主文档的_body中
                master_doc.element.body.append(cloned_element)
        master_doc.save("合并word.docx")  # 保存合并后的文档
        print("合并word完成")

    def txt_merge(self):
        pathname = self.main_instance.main_ui.lineEdit3.text()   # 获取目录路径

        # 如果pathname是一个目录,获取其中的文件列表
        if os.path.isdir(pathname):
            pathname = [os.path.join(pathname, filename) for filename in os.listdir(pathname)]
    
            
        # 检查每个文件是否为Excel格式
        valid_files = []  # 都有什么文件
        for i in pathname:
            if os.path.isfile(i) and i.endswith(('.txt')): # 判断是否为文件并且是否是相关后缀名
                valid_files.append(i)   
        # 打开输出文件,使用'a'模式追加内容
        with open("合并txt文件.txt", 'a', encoding='utf-8') as outfile:
            for file_path in valid_files:
                # 打开每个文件,读取内容并写入输出文件
                with open(file_path, 'r', encoding='utf-8') as infile:
                    content = infile.read()
                    outfile.write(content)
                    outfile.write('\n\n')  # 可选:在每个文件内容之间插入空行以区分
        print("合并文本.txt完成")

    def openfile(self):
        change_file = self.main_instance.main_ui.comboBox.currentText()
        if change_file =="合并execl表格(xlsx)":
            outfile = "统计表.xlsx"
            if os.path.exists(outfile):   # 查看路径下文件是否存在
                os.startfile(outfile)
            else:
                print(f"查看{outfile}是否存在")

        elif change_file== "合并pdf":
            outfile = "pdf合并.pdf"
            if os.path.exists(outfile):
                os.startfile(outfile)
            else:
                print(f"查看{outfile}是否存在")

        elif change_file == "合并word文档(docx)":
            outfile = "合并word.docx"
            if os.path.exists(outfile):
                os.startfile(outfile)
            else:
                print(f"查看{outfile}是否存在")
        else:
            outfile = "合并txt文件.txt"
            if os.path.exists(outfile):
                os.startfile(outfile)
            else:
                print(f"查看{outfile}是否存在")
        
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = win_Main()
    window.show()
    sys.exit(app.exec())

** 用到的ui文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>486</width>
    <height>297</height>
   </rect>
  </property>
  <property name="minimumSize">
   <size>
    <width>0</width>
    <height>0</height>
   </size>
  </property>
  <property name="windowTitle">
   <string>数据统计</string>
  </property>
  <property name="windowIcon">
   <iconset>
    <normaloff>登录图标.jpeg</normaloff>登录图标.jpeg</iconset>
  </property>
  <property name="styleSheet">
   <string notr="true">
QPushButton:hover{
	border:3px solid #1d649c;
	color: rgb(255, 255, 255);
	background-color: rgba(85, 170, 127, 150);
}

#label3{
color: rgb(85, 170, 0);
}

</string>
  </property>
  <layout class="QGridLayout" name="gridLayout_2">
   <item row="0" column="0">
    <layout class="QGridLayout" name="gridLayout">
     <item row="0" column="0">
      <widget class="QLabel" name="label_6">
       <property name="minimumSize">
        <size>
         <width>71</width>
         <height>61</height>
        </size>
       </property>
       <property name="maximumSize">
        <size>
         <width>71</width>
         <height>61</height>
        </size>
       </property>
       <property name="text">
        <string/>
       </property>
       <property name="textFormat">
        <enum>Qt::PlainText</enum>
       </property>
       <property name="pixmap">
        <pixmap>登录图标.jpeg</pixmap>
       </property>
       <property name="scaledContents">
        <bool>true</bool>
       </property>
      </widget>
     </item>
     <item row="0" column="1" colspan="2">
      <widget class="QComboBox" name="comboBox">
       <property name="minimumSize">
        <size>
         <width>133</width>
         <height>61</height>
        </size>
       </property>
       <property name="font">
        <font>
         <family>幼圆</family>
         <pointsize>9</pointsize>
         <bold>true</bold>
        </font>
       </property>
       <property name="maxVisibleItems">
        <number>10</number>
       </property>
       <property name="sizeAdjustPolicy">
        <enum>QComboBox::AdjustToContentsOnFirstShow</enum>
       </property>
       <item>
        <property name="text">
         <string>合并execl表格(xlsx)</string>
        </property>
       </item>
       <item>
        <property name="text">
         <string>合并pdf</string>
        </property>
       </item>
       <item>
        <property name="text">
         <string>合并word文档(docx)</string>
        </property>
       </item>
       <item>
        <property name="text">
         <string>合并文本文档(txt)</string>
        </property>
       </item>
      </widget>
     </item>
     <item row="0" column="3" colspan="3">
      <widget class="QLineEdit" name="lineEdit3">
       <property name="placeholderText">
        <string>文件夹路径</string>
       </property>
      </widget>
     </item>
     <item row="0" column="6">
      <widget class="QPushButton" name="pushButton3">
       <property name="text">
        <string>选择文件夹</string>
       </property>
       <property name="autoDefault">
        <bool>false</bool>
       </property>
       <property name="default">
        <bool>true</bool>
       </property>
       <property name="flat">
        <bool>false</bool>
       </property>
      </widget>
     </item>
     <item row="1" column="0">
      <widget class="QLabel" name="label_3">
       <property name="font">
        <font>
         <pointsize>12</pointsize>
        </font>
       </property>
       <property name="text">
        <string>工作表名</string>
       </property>
      </widget>
     </item>
     <item row="1" column="1" colspan="2">
      <widget class="QLineEdit" name="lineEdit_5"/>
     </item>
     <item row="3" column="2" colspan="2">
      <widget class="QLineEdit" name="lineEdit"/>
     </item>
     <item row="3" column="5">
      <widget class="QLineEdit" name="lineEdit_2"/>
     </item>
     <item row="4" column="0" colspan="2">
      <widget class="QLabel" name="label_5">
       <property name="font">
        <font>
         <pointsize>12</pointsize>
        </font>
       </property>
       <property name="text">
        <string>输入需要合并的列号</string>
       </property>
      </widget>
     </item>
     <item row="4" column="2" colspan="2">
      <widget class="QLineEdit" name="lineEdit_3"/>
     </item>
     <item row="4" column="4">
      <widget class="QLabel" name="label_2">
       <property name="text">
        <string>-</string>
       </property>
      </widget>
     </item>
     <item row="4" column="5">
      <widget class="QLineEdit" name="lineEdit_4"/>
     </item>
     <item row="5" column="0" rowspan="2" colspan="6">
      <widget class="QPlainTextEdit" name="plainTextEdit">
       <property name="minimumSize">
        <size>
         <width>0</width>
         <height>0</height>
        </size>
       </property>
       <property name="font">
        <font>
         <pointsize>10</pointsize>
        </font>
       </property>
       <property name="styleSheet">
        <string notr="true"/>
       </property>
       <property name="readOnly">
        <bool>true</bool>
       </property>
       <property name="placeholderText">
        <string>信息展示区域</string>
       </property>
      </widget>
     </item>
     <item row="5" column="6">
      <widget class="QPushButton" name="pushButton3_3">
       <property name="minimumSize">
        <size>
         <width>0</width>
         <height>0</height>
        </size>
       </property>
       <property name="maximumSize">
        <size>
         <width>93</width>
         <height>171</height>
        </size>
       </property>
       <property name="text">
        <string>提交</string>
       </property>
       <property name="autoDefault">
        <bool>false</bool>
       </property>
       <property name="default">
        <bool>true</bool>
       </property>
      </widget>
     </item>
     <item row="6" column="6">
      <widget class="QPushButton" name="pushButton3_5">
       <property name="minimumSize">
        <size>
         <width>0</width>
         <height>0</height>
        </size>
       </property>
       <property name="maximumSize">
        <size>
         <width>93</width>
         <height>171</height>
        </size>
       </property>
       <property name="text">
        <string>打开</string>
       </property>
       <property name="autoDefault">
        <bool>false</bool>
       </property>
       <property name="default">
        <bool>true</bool>
       </property>
      </widget>
     </item>
     <item row="1" column="6" rowspan="4">
      <widget class="QPushButton" name="pushButton3_4">
       <property name="minimumSize">
        <size>
         <width>75</width>
         <height>90</height>
        </size>
       </property>
       <property name="text">
        <string>确认</string>
       </property>
       <property name="autoDefault">
        <bool>false</bool>
       </property>
       <property name="default">
        <bool>true</bool>
       </property>
      </widget>
     </item>
     <item row="3" column="0" colspan="2">
      <widget class="QLabel" name="label_4">
       <property name="font">
        <font>
         <pointsize>12</pointsize>
        </font>
       </property>
       <property name="text">
        <string>输入需要合并的行号</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

可以使用pyinstaller 命令将程序打包exe应用程序,从而可以在windows桌面上使用

123.png

    这样简单的一些合并操作可以正常完成,当然,文件多的时候,工具的作用就能体现出来。这个桌面小工具不仅支持PDF、Word、Txt和Excel文件的合并,而且操作简便,无需复杂的配置。只需要将需要合并的文件放入同一目录下,然后运行程序即可。