欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python實(shí)現(xiàn)智能合并多個(gè)Excel

 更新時(shí)間:2025年04月24日 08:27:38   作者:創(chuàng)客白澤  
在日常辦公場景中,Excel文件合并是數(shù)據(jù)分析人員,財(cái)務(wù)人員和行政人員經(jīng)常面臨的高頻痛點(diǎn),下面我們來看看如何使用Python實(shí)現(xiàn)智能合并多個(gè)Excel吧

一、痛點(diǎn)與解決方案:為什么需要這個(gè)工具?

在日常辦公場景中,Excel文件合并是數(shù)據(jù)分析人員、財(cái)務(wù)人員和行政人員經(jīng)常面臨的高頻痛點(diǎn)。傳統(tǒng)手工合并方式存在三大致命缺陷:

  • 效率低下:需要逐個(gè)打開文件復(fù)制粘貼,合并100個(gè)文件可能需要數(shù)小時(shí)
  • 錯(cuò)誤率高:人工操作容易遺漏工作表或復(fù)制錯(cuò)誤數(shù)據(jù)
  • 格式丟失:直接復(fù)制可能導(dǎo)致單元格樣式、公式等格式信息丟失

本文介紹的Excel多合一合并工具采用PyQt5+openpyxl技術(shù)棧,實(shí)現(xiàn)了三大突破性功能:

  • 智能遞歸掃描:自動(dòng)遍歷選定文件夾及其所有子目錄
  • 樣式無損合并:完美保留原文件的字體、邊框、對(duì)齊等格式
  • 靈活命名規(guī)則:支持按文件名或工作表名兩種命名方式

根據(jù)微軟官方統(tǒng)計(jì),85%的Excel用戶每周至少需要進(jìn)行一次文件合并操作,而使用專業(yè)工具可提升效率300%以上

二、技術(shù)架構(gòu)深度解析

2.1 核心類結(jié)構(gòu)設(shè)計(jì)

class MergeWorker(QThread):  # 后臺(tái)工作線程
    progress_updated = pyqtSignal(int)  # 進(jìn)度信號(hào)
    merge_complete = pyqtSignal(str)    # 完成信號(hào)
    error_occurred = pyqtSignal(str)    # 錯(cuò)誤信號(hào)

class DropLabel(QLabel):    # 支持拖放的標(biāo)簽控件
    def dragEnterEvent(self, event): ...
    def dropEvent(self, event): ...

class ExcelMergerApp(QWidget):  # 主界面
    def __init__(self): ...     # UI初始化
    def mergeExcelFiles(self): ... # 合并邏輯

2.2 關(guān)鍵技術(shù)實(shí)現(xiàn)

1. 多線程處理機(jī)制

采用生產(chǎn)者-消費(fèi)者模式設(shè)計(jì):

  • 主線程:負(fù)責(zé)UI交互和狀態(tài)管理
  • 工作線程:執(zhí)行實(shí)際的文件合并操作
  • 通過信號(hào)槽機(jī)制實(shí)現(xiàn)線程間通信
# 創(chuàng)建工作線程示例
self.merge_worker = MergeWorker(folder_path, output_file, naming_rule)
self.merge_worker.progress_updated.connect(self.updateProgress)
self.merge_worker.start()

2. 樣式無損復(fù)制技術(shù)

使用openpyxl的樣式深拷貝方法,確保所有格式屬性完整保留:

# 單元格樣式復(fù)制實(shí)現(xiàn)
new_cell.font = copy(cell.font)
new_cell.border = copy(cell.border)
new_cell.fill = copy(cell.fill)

3. 智能命名沖突解決算法

# 名稱處理邏輯
while new_sheet_name in used_sheet_names or len(new_sheet_name) > 31:
    new_sheet_name = f"{original_name[:28]}_{counter}"
    counter += 1

三、工具使用全指南

3.1 運(yùn)行環(huán)境配置

基礎(chǔ)環(huán)境要求:

  • Python 3.7+
  • PyQt5 5.15+
  • openpyxl 3.0+

安裝依賴:

pip install PyQt5 openpyxl

3.2 操作流程詳解

1.選擇文件夾(三種方式任選)

  • 點(diǎn)擊"選擇文件夾"按鈕
  • 直接拖拽文件夾到界面
  • 粘貼文件夾路徑到輸入框

2.設(shè)置命名規(guī)則

  • 按文件名命名:適合每個(gè)文件包含單工作表
  • 按工作表名命名:適合多文件包含同名工作表

3.執(zhí)行合并操作

  • 實(shí)時(shí)進(jìn)度條顯示處理進(jìn)度
  • 支持中途取消操作
  • 完成后自動(dòng)彈出保存路徑

3.3 高級(jí)功能技巧

批量處理模式:

# 可通過命令行參數(shù)指定文件夾路徑
python excel_merger.py --path /your/folder/path

自定義輸出位置:

修改源碼中的output_file生成邏輯,實(shí)現(xiàn)靈活保存路徑:

# 修改輸出路徑生成邏輯
output_file = os.path.join(os.path.expanduser("~"), "Desktop", "合并結(jié)果.xlsx")

四、性能優(yōu)化實(shí)踐

4.1 內(nèi)存管理策略

針對(duì)大文件處理的內(nèi)存優(yōu)化方案:

分塊讀取:使用openpyxl的read_only模式

   wb = load_workbook(file, read_only=True)

延遲加載:僅在需要時(shí)處理工作表

進(jìn)度反饋:每處理完一個(gè)文件立即釋放內(nèi)存

4.2 異常處理機(jī)制

完善的錯(cuò)誤處理體系包括:

  • 文件權(quán)限檢測
  • 損壞文件跳過
  • 無效路徑提示
  • 線程安全退出
try:
    # 嘗試寫入測試文件
    with open(output_file, 'w') as f:
        pass
except PermissionError:
    QMessageBox.warning(self, "權(quán)限錯(cuò)誤", "沒有寫入權(quán)限")

五、擴(kuò)展開發(fā)方向

5.1 功能增強(qiáng)建議

格式轉(zhuǎn)換支持:增加CSV/XLS等其他格式轉(zhuǎn)換

數(shù)據(jù)清洗功能:合并前自動(dòng)去除空行/重復(fù)值

云存儲(chǔ)集成:支持直接處理網(wǎng)盤中的文件

5.2 企業(yè)級(jí)改造方案

如需部署到企業(yè)環(huán)境,建議:

打包為EXE可執(zhí)行文件

pyinstaller --onefile --windowed excel_merger.py

添加用戶權(quán)限管理系統(tǒng)

集成到辦公系統(tǒng)作為插件

六、效果展示

七、相關(guān)源碼

import os
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
                             QPushButton, QFileDialog, QProgressBar, QLabel,
                             QMessageBox, QFrame, QRadioButton, QButtonGroup)
from PyQt5.QtCore import Qt, QMimeData, QThread, pyqtSignal
from PyQt5.QtGui import QIcon, QFont, QPixmap, QDragEnterEvent, QDropEvent
from openpyxl import load_workbook, Workbook
from openpyxl.utils import get_column_letter
from copy import copy
import time

def resource_path(relative_path):
    """獲取資源文件的絕對(duì)路徑,處理打包后和開發(fā)環(huán)境兩種情況"""
    try:
        # PyInstaller創(chuàng)建的臨時(shí)文件夾路徑
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)


class MergeWorker(QThread):
    """后臺(tái)合并工作線程"""
    progress_updated = pyqtSignal(int)
    merge_complete = pyqtSignal(str)
    error_occurred = pyqtSignal(str)

    def __init__(self, folder_path, output_file, naming_rule):
        super().__init__()
        self.folder_path = folder_path
        self.output_file = output_file
        self.naming_rule = naming_rule
        self._is_running = True

    def run(self):
        try:
            excel_files = []
            for root, dirs, files in os.walk(self.folder_path):
                for file in files:
                    if file.endswith('.xlsx') and not file.startswith('~$'):
                        excel_files.append(os.path.join(root, file))

            total_files = len(excel_files)

            if total_files == 0:
                self.error_occurred.emit("所選文件夾及其子文件夾中沒有找到 Excel 文件 (.xlsx)")
                return

            progress_step = 100 / total_files if total_files > 0 else 0

            summary_wb = Workbook()
            summary_wb.remove(summary_wb.active)

            # 用于跟蹤已使用的工作表名稱
            used_sheet_names = set()

            for i, file in enumerate(excel_files, 1):
                if not self._is_running:
                    break

                try:
                    wb = load_workbook(file, read_only=True)
                    file_base = os.path.splitext(os.path.basename(file))[0]

                    for sheet_name in wb.sheetnames:
                        if not self._is_running:
                            break

                        ws = wb[sheet_name]

                        # 根據(jù)命名規(guī)則確定工作表名稱
                        if self.naming_rule == "filename":
                            # 使用文件名命名,如果多工作表則添加工作表名
                            if len(wb.sheetnames) > 1:
                                new_sheet_name = f"{file_base}_{sheet_name}"
                            else:
                                new_sheet_name = file_base
                        else:  # sheetname命名規(guī)則
                            # 使用工作表名命名,如果重名則添加文件名
                            if sheet_name in used_sheet_names:
                                new_sheet_name = f"{file_base}_{sheet_name}"
                            else:
                                new_sheet_name = sheet_name

                        # 確保名稱唯一且不超過31個(gè)字符
                        original_name = new_sheet_name
                        counter = 1
                        while new_sheet_name in used_sheet_names or len(new_sheet_name) > 31:
                            new_sheet_name = f"{original_name[:28]}_{counter}" if len(
                                original_name) > 28 else f"{original_name}_{counter}"
                            counter += 1

                        used_sheet_names.add(new_sheet_name)

                        # 創(chuàng)建工作表并復(fù)制內(nèi)容
                        summary_ws = summary_wb.create_sheet(title=new_sheet_name)
                        
                        # 復(fù)制數(shù)據(jù)
                        for row in ws.iter_rows(values_only=False):
                            if not self._is_running:
                                break
                            for cell in row:
                                new_cell = summary_ws.cell(row=cell.row, column=cell.column, value=cell.value)
                                if cell.has_style:
                                    new_cell.font = copy(cell.font)
                                    new_cell.border = copy(cell.border)
                                    new_cell.fill = copy(cell.fill)
                                    new_cell.number_format = copy(cell.number_format)
                                    new_cell.protection = copy(cell.protection)
                                    new_cell.alignment = copy(cell.alignment)

                    # 更新進(jìn)度
                    progress_value = int(i * progress_step)
                    self.progress_updated.emit(min(progress_value, 100))
                    
                except Exception as e:
                    print(f"處理文件 {file} 時(shí)出錯(cuò): {e}")
                    continue

            if self._is_running:
                # 確保進(jìn)度條最終顯示為100%
                self.progress_updated.emit(100)
                summary_wb.save(self.output_file)
                self.merge_complete.emit(self.output_file)

        except Exception as e:
            self.error_occurred.emit(str(e))

    def stop(self):
        self._is_running = False


class DropLabel(QLabel):
    """自定義支持拖拽的QLabel"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.main_window = parent  # 保存對(duì)主窗口的引用
        self.setAcceptDrops(True)
        self.setAlignment(Qt.AlignCenter)
        self.normal_style = """
            color: #666;
            font-size: 13px;
            padding: 8px;
            background: #f9f9f9;
            border-radius: 4px;
            border: 1px solid #eee;
        """
        self.drag_style = """
            color: #666;
            font-size: 13px;
            padding: 8px;
            background: #f0fff0;
            border-radius: 4px;
            border: 2px dashed #4CAF50;
        """
        self.selected_style = """
            color: #27ae60;
            font-size: 13px;
            padding: 8px;
            background: #f0f8f0;
            border-radius: 4px;
            border: 1px solid #d0e8d0;
        """
        self.setStyleSheet(self.normal_style)

    def dragEnterEvent(self, event: QDragEnterEvent):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
            self.setStyleSheet(self.drag_style)
        else:
            event.ignore()

    def dragLeaveEvent(self, event):
        self.setStyleSheet(self.normal_style if not self.text().startswith("已選擇文件夾")
                           else self.selected_style)

    def dropEvent(self, event: QDropEvent):
        self.setStyleSheet(self.normal_style)

        if event.mimeData().hasUrls():
            urls = event.mimeData().urls()
            if urls:
                path = urls[0].toLocalFile()
                if os.path.isdir(path):
                    self.main_window.handle_folder_selection(path)  # 調(diào)用主窗口的方法
                    self.setStyleSheet(self.selected_style)
                else:
                    QMessageBox.warning(self.main_window, "警告", "請(qǐng)拖拽有效的文件夾路徑")
            else:
                QMessageBox.warning(self.main_window, "警告", "未獲取到有效路徑")
        else:
            QMessageBox.warning(self.main_window, "警告", "請(qǐng)拖拽文件夾路徑")


class ExcelMergerApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.merge_worker = None

        # 設(shè)置窗口圖標(biāo) - 使用resource_path處理圖標(biāo)路徑
        icon_path = resource_path("logo.ico")
        if os.path.exists(icon_path):
            self.setWindowIcon(QIcon(icon_path))
        else:
            # 如果找不到圖標(biāo)文件,使用默認(rèn)圖標(biāo)
            self.setWindowIcon(QIcon.fromTheme("document-merge"))

    def initUI(self):
        self.setWindowTitle("Excel 多合一文件合并工具")
        self.setFixedSize(1024, 768)  # 固定窗口大小

        # 設(shè)置主窗口樣式
        self.setStyleSheet("""
            QWidget {
                background-color: #f5f7fa;
                font-family: 'Microsoft YaHei';
            }
            QPushButton {
                background-color: #4CAF50;
                color: white;
                border: none;
                padding: 10px 20px;
                text-align: center;
                text-decoration: none;
                font-size: 14px;
                margin: 4px 2px;
                border-radius: 6px;
                min-width: 120px;
            }
            QPushButton:hover {
                background-color: #45a049;
                border: 1px solid #3d8b40;
            }
            QPushButton:pressed {
                background-color: #3d8b40;
            }
            QPushButton:disabled {
                background-color: #cccccc;
                color: #666666;
            }
            QProgressBar {
                border: 1px solid #ddd;
                border-radius: 6px;
                text-align: center;
                height: 24px;
                background: white;
            }
            QProgressBar::chunk {
                background-color: #4CAF50;
                border-radius: 5px;
                width: 10px;
            }
            QLabel {
                font-size: 14px;
                color: #333;
            }
            QRadioButton {
                font-size: 14px;
                padding: 5px;
            }
            #header {
                font-size: 22px;
                font-weight: bold;
                color: #2c3e50;
                padding: 10px;
            }
            #frame {
                background-color: white;
                border-radius: 10px;
                padding: 20px;
                border: 1px solid #e0e0e0;
                box-shadow: 0 2px 5px rgba(0,0,0,0.05);
            }
            #footer {
                color: #7f8c8d;
                font-size: 12px;
                padding-top: 10px;
                border-top: 1px solid #eee;
            }
            .option-group {
                background: #f9f9f9;
                border-radius: 6px;
                padding: 15px;
                border: 1px solid #e0e0e0;
            }
            #cancelButton {
                background-color: #e74c3c;
            }
            #cancelButton:hover {
                background-color: #c0392b;
            }
        """)

        # 主布局
        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(25, 25, 25, 25)
        main_layout.setSpacing(20)

        # 標(biāo)題區(qū)域
        title_layout = QHBoxLayout()

        # 如果有圖標(biāo),可以在這里添加
        if hasattr(self, 'windowIcon') and not self.windowIcon().isNull():
            icon_label = QLabel()
            icon_label.setPixmap(self.windowIcon().pixmap(32, 32))
            title_layout.addWidget(icon_label, 0, Qt.AlignLeft)

        title = QLabel("Excel 多合一文件合并工具")
        title.setObjectName("header")
        title_layout.addWidget(title, 1, Qt.AlignCenter)

        # 添加一個(gè)空的占位部件使標(biāo)題居中
        if hasattr(self, 'windowIcon') and not self.windowIcon().isNull():
            title_layout.addWidget(QLabel(), 0, Qt.AlignRight)

        main_layout.addLayout(title_layout)

        # 添加分隔線
        line = QFrame()
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)
        line.setStyleSheet("border-color: #e0e0e0;")
        main_layout.addWidget(line)

        # 內(nèi)容框架
        frame = QFrame()
        frame.setObjectName("frame")
        frame_layout = QVBoxLayout()
        frame_layout.setContentsMargins(20, 20, 20, 20)
        frame_layout.setSpacing(20)

        # 文件夾選擇部分
        folder_layout = QVBoxLayout()
        folder_layout.setSpacing(10)

        self.folder_label = QLabel("請(qǐng)選擇包含 Excel 文件的文件夾 (或拖拽文件夾到這里):")
        self.folder_label.setStyleSheet("font-weight: bold;")
        folder_layout.addWidget(self.folder_label)

        self.selected_folder_label = DropLabel(self)
        self.selected_folder_label.setText("未選擇文件夾")
        self.selected_folder_label.setWordWrap(True)
        folder_layout.addWidget(self.selected_folder_label)

        btn_layout = QHBoxLayout()
        self.folderButton = QPushButton("選擇文件夾")
        self.folderButton.setIcon(QIcon.fromTheme("folder"))
        self.folderButton.setCursor(Qt.PointingHandCursor)
        self.folderButton.clicked.connect(self.selectFolder)
        btn_layout.addWidget(self.folderButton)
        btn_layout.addStretch()
        folder_layout.addLayout(btn_layout)

        frame_layout.addLayout(folder_layout)

        # 命名選項(xiàng)部分
        naming_layout = QVBoxLayout()
        naming_layout.setSpacing(10)

        naming_label = QLabel("合并后工作表命名規(guī)則:")
        naming_label.setStyleSheet("font-weight: bold;")
        naming_layout.addWidget(naming_label)

        # 選項(xiàng)組容器
        option_group = QWidget()
        option_group.setObjectName("option-group")
        option_group.setStyleSheet(".option-group {background: #f9f9f9;}")
        option_layout = QVBoxLayout()
        option_layout.setContentsMargins(10, 10, 10, 10)

        # 創(chuàng)建單選按鈕組
        self.naming_option = QButtonGroup(self)

        self.option_filename = QRadioButton("使用原文件名命名 (如果多工作表則使用 原文件名_原工作表名)")
        self.option_filename.setChecked(True)
        self.naming_option.addButton(self.option_filename, 1)

        self.option_sheetname = QRadioButton("使用原工作表名命名 (如果多工作表則使用 原工作表名)")
        self.naming_option.addButton(self.option_sheetname, 2)

        option_layout.addWidget(self.option_filename)
        option_layout.addWidget(self.option_sheetname)
        option_group.setLayout(option_layout)

        naming_layout.addWidget(option_group)
        frame_layout.addLayout(naming_layout)

        # 進(jìn)度條部分
        progress_layout = QVBoxLayout()
        progress_layout.setSpacing(10)

        progress_label = QLabel("合并進(jìn)度:")
        progress_label.setStyleSheet("font-weight: bold;")
        progress_layout.addWidget(progress_label)

        self.progressBar = QProgressBar(self)
        self.progressBar.setValue(0)
        self.progressBar.setFormat("當(dāng)前進(jìn)度: %p%")
        progress_layout.addWidget(self.progressBar)

        frame_layout.addLayout(progress_layout)

        # 按鈕布局
        button_layout = QHBoxLayout()
        button_layout.setSpacing(20)

        # 合并按鈕
        self.mergeButton = QPushButton("開始合并")
        self.mergeButton.setIcon(QIcon.fromTheme("document-save"))
        self.mergeButton.setStyleSheet("""
            background-color: #3498db;
            padding: 12px 24px;
            font-size: 15px;
        """)
        self.mergeButton.setCursor(Qt.PointingHandCursor)
        self.mergeButton.clicked.connect(self.mergeExcelFiles)
        button_layout.addWidget(self.mergeButton, 1)

        # 取消按鈕
        self.cancelButton = QPushButton("取消合并")
        self.cancelButton.setObjectName("cancelButton")
        self.cancelButton.setIcon(QIcon.fromTheme("process-stop"))
        self.cancelButton.setStyleSheet("""
            padding: 12px 24px;
            font-size: 15px;
        """)
        self.cancelButton.setCursor(Qt.PointingHandCursor)
        self.cancelButton.clicked.connect(self.cancelMerge)
        self.cancelButton.setEnabled(False)
        button_layout.addWidget(self.cancelButton, 1)

        frame_layout.addLayout(button_layout)

        frame.setLayout(frame_layout)
        main_layout.addWidget(frame, 1)  # 添加伸縮因子使框架占據(jù)更多空間

        # 底部信息
        footer = QLabel("——————————————————————Excel多合一文件合并工具—————————————————————— ")
        footer.setObjectName("footer")
        footer.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(footer)

        self.setLayout(main_layout)

    def selectFolder(self):
        folder_path = QFileDialog.getExistingDirectory(
            self,
            "請(qǐng)選擇包含 Excel 文件的文件夾",
            os.path.expanduser("~"),
            QFileDialog.ShowDirsOnly
        )
        if folder_path:
            self.handle_folder_selection(folder_path)

    def handle_folder_selection(self, folder_path):
        """處理文件夾選擇后的邏輯"""
        try:
            if os.path.isdir(folder_path):
                self.folder_path = folder_path
                self.selected_folder_label.setText(f"已選擇文件夾:\n{folder_path}")
                self.selected_folder_label.setStyleSheet(self.selected_folder_label.selected_style)
            else:
                QMessageBox.warning(self, "警告", "路徑不是有效的文件夾")
        except Exception as e:
            QMessageBox.warning(self, "錯(cuò)誤", f"處理路徑時(shí)出錯(cuò): {str(e)}")

    def mergeExcelFiles(self):
        if not hasattr(self, 'folder_path'):
            QMessageBox.warning(self, "警告", "請(qǐng)先選擇包含 Excel 文件的文件夾")
            return

        # 檢查是否有寫入權(quán)限
        output_file = os.path.join(self.folder_path, "匯總文件.xlsx")
        try:
            # 測試是否有寫入權(quán)限
            with open(output_file, 'w') as f:
                pass
            os.remove(output_file)
        except PermissionError:
            QMessageBox.warning(self, "警告", "沒有寫入權(quán)限,請(qǐng)選擇其他文件夾或檢查權(quán)限")
            return
        except Exception as e:
            QMessageBox.warning(self, "錯(cuò)誤", f"檢查文件權(quán)限時(shí)出錯(cuò): {str(e)}")
            return

        self.mergeButton.setEnabled(False)
        self.folderButton.setEnabled(False)
        self.cancelButton.setEnabled(True)
        self.progressBar.setValue(0)

        naming_rule = "filename" if self.option_filename.isChecked() else "sheetname"

        # 創(chuàng)建并啟動(dòng)工作線程
        self.merge_worker = MergeWorker(self.folder_path, output_file, naming_rule)
        self.merge_worker.progress_updated.connect(self.updateProgress)
        self.merge_worker.merge_complete.connect(self.mergeComplete)
        self.merge_worker.error_occurred.connect(self.mergeError)
        self.merge_worker.start()

    def cancelMerge(self):
        if self.merge_worker and self.merge_worker.isRunning():
            self.merge_worker.stop()
            self.merge_worker.quit()
            self.merge_worker.wait()
            
        self.mergeButton.setEnabled(True)
        self.folderButton.setEnabled(True)
        self.cancelButton.setEnabled(False)
        self.progressBar.setValue(0)
        
        QMessageBox.information(self, "已取消", "合并操作已取消")

    def updateProgress(self, value):
        self.progressBar.setValue(value)

    def mergeComplete(self, output_file):
        self.mergeButton.setEnabled(True)
        self.folderButton.setEnabled(True)
        self.cancelButton.setEnabled(False)
        
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Information)
        msg.setWindowTitle("完成")
        msg.setText(f"所有文件已成功合并到:\n{output_file}")
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def mergeError(self, error_msg):
        self.mergeButton.setEnabled(True)
        self.folderButton.setEnabled(True)
        self.cancelButton.setEnabled(False)
        
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Critical)
        msg.setWindowTitle("錯(cuò)誤")
        msg.setText(f"處理文件時(shí)出錯(cuò):\n{error_msg}")
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def closeEvent(self, event):
        """窗口關(guān)閉事件處理"""
        if hasattr(self, 'merge_worker') and self.merge_worker and self.merge_worker.isRunning():
            reply = QMessageBox.question(
                self, '確認(rèn)退出',
                '合并操作正在進(jìn)行中,確定要退出嗎?',
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No
            )
            
            if reply == QMessageBox.Yes:
                self.merge_worker.stop()
                self.merge_worker.quit()
                self.merge_worker.wait()
                event.accept()
            else:
                event.ignore()
        else:
            event.accept()


if __name__ == "__main__":
    def handle_exception(exc_type, exc_value, exc_traceback):
        import traceback
        error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
        print(f"Unhandled exception: {error_msg}")
        QMessageBox.critical(None, "未處理的異常", f"程序發(fā)生錯(cuò)誤:\n{error_msg}")


    sys.excepthook = handle_exception

    app = QApplication(sys.argv)
    font = QFont("Microsoft YaHei", 12)
    app.setFont(font)
    ex = ExcelMergerApp()
    ex.show()
    sys.exit(app.exec_())

八、總結(jié)與資源

本文詳細(xì)解析了一個(gè)生產(chǎn)級(jí)Excel合并工具的開發(fā)全過程,關(guān)鍵技術(shù)點(diǎn)包括:

  • PyQt5的現(xiàn)代化界面開發(fā)
  • 多線程任務(wù)處理模式
  • Excel樣式深度復(fù)制技術(shù)
  • 健壯的錯(cuò)誤處理體系

附錄:常見問題解答

Q: 處理超大型Excel文件時(shí)卡頓怎么辦?

A: 建議啟用read_only模式并增加內(nèi)存檢測功能

Q: 如何合并特定名稱的工作表?

A: 可修改代碼添加工作表名稱過濾邏輯

Q: 能否保留原文件的VBA宏?

A: 當(dāng)前openpyxl不支持宏處理,需改用win32com方案

到此這篇關(guān)于Python實(shí)現(xiàn)智能合并多個(gè)Excel的文章就介紹到這了,更多相關(guān)Python合并Excel內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用 PyTorch 實(shí)現(xiàn) MLP 并在 MNIST 數(shù)據(jù)集上驗(yàn)證方式

    使用 PyTorch 實(shí)現(xiàn) MLP 并在 MNIST 數(shù)據(jù)集上驗(yàn)證方式

    今天小編就為大家分享一篇使用 PyTorch 實(shí)現(xiàn) MLP 并在 MNIST 數(shù)據(jù)集上驗(yàn)證方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-01-01
  • 使用matplotlib畫散點(diǎn)圖的方法

    使用matplotlib畫散點(diǎn)圖的方法

    今天小編就為大家分享一篇使用matplotlib畫散點(diǎn)圖的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • 利用Python實(shí)現(xiàn)在PDF文檔中插入文字水印

    利用Python實(shí)現(xiàn)在PDF文檔中插入文字水印

    在傳播PDF文檔的過程中,如何有效地保護(hù)文檔的版權(quán)和所有權(quán),防止非法復(fù)制和濫用,成為了一個(gè)不可忽視的問題,所以給PDF文檔添加水印便成了一種行之有效的保護(hù)手,本文將展示如何使用Python在PDF文檔中插入文字水印,實(shí)現(xiàn)高效的PDF文檔處理,需要的朋友可以參考下
    2024-04-04
  • 聊聊Python中的@符號(hào)是什么意思

    聊聊Python中的@符號(hào)是什么意思

    @符號(hào)用做函數(shù)的修飾符,可以在模塊或者類的定義層內(nèi)對(duì)函數(shù)進(jìn)行修飾,下面這篇文章主要給大家介紹了關(guān)于Python中@符號(hào)是什么意思的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • python使用Random隨機(jī)生成列表的方法實(shí)例

    python使用Random隨機(jī)生成列表的方法實(shí)例

    在日常的生活工作和系統(tǒng)游戲等設(shè)計(jì)和制作時(shí),經(jīng)常會(huì)碰到產(chǎn)生隨機(jī)數(shù),用來解決問題,下面這篇文章主要給大家介紹了關(guān)于python使用Random隨機(jī)生成列表的相關(guān)資料,需要的朋友可以參考下
    2022-04-04
  • Python中asyncore的用法實(shí)例

    Python中asyncore的用法實(shí)例

    這篇文章主要介紹了Python中asyncore的用法,asyncore提供了方便的網(wǎng)絡(luò)操作方法,本文以連接并解析www.python.org主頁為例加以說明,需要的朋友可以參考下
    2014-09-09
  • Python封裝adb命令的操作詳解

    Python封裝adb命令的操作詳解

    在日常的 Android 項(xiàng)目開發(fā)中,我們通常會(huì)使用 adb 命令來獲取連接設(shè)備的內(nèi)存、屏幕、CPU等信息,這些信息的獲取,每次都在command 中輸入相關(guān)命令進(jìn)行重復(fù)的操作讓人感到厭倦和疲乏,現(xiàn)在,可以嘗試使用 python 來簡化這一部分工作,所以本文介紹了Python封裝adb命令的操作
    2024-01-01
  • Python中IO多路復(fù)用模塊selector的用法詳解

    Python中IO多路復(fù)用模塊selector的用法詳解

    selector?是一個(gè)實(shí)現(xiàn)了IO復(fù)用模型的python包,實(shí)現(xiàn)了IO多路復(fù)用模型的?select、poll?和?epoll?等函數(shù),下面就跟隨小編一起來學(xué)習(xí)一下它的具體使用吧
    2024-02-02
  • Pygame Rect區(qū)域位置的使用(圖文)

    Pygame Rect區(qū)域位置的使用(圖文)

    在 Pygame 中我們使用 Rect() 方法來創(chuàng)建一個(gè)指定位置,大小的矩形區(qū)域。本文主要就來介紹一下如何使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2021-11-11
  • python OpenCV的imread不能讀取中文路徑問題及解決

    python OpenCV的imread不能讀取中文路徑問題及解決

    這篇文章主要介紹了python OpenCV的imread不能讀取中文路徑問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07

最新評(píng)論