Python编程:Excel操作

Python实现Excel操作及PyQt应用
该文章已生成可运行项目,

Python提供了多个强大的库来操作Excel文件,主要包括openpyxlxlrd/xlwtpandas。下面将介绍这些库的基本用法。

1. 使用openpyxl操作Excel

openpyxl是一个流行的库,用于读写Excel 2010 xlsx/xlsm/xltx/xltm文件。

安装

pip install openpyxl

基本操作

from openpyxl import Workbook, load_workbook

# 创建新工作簿
wb = Workbook()
ws = wb.active  # 获取活动工作表
ws.title = "Sheet1"  # 设置工作表名称

# 写入数据
ws['A1'] = "姓名"
ws['B1'] = "年龄"
ws['A2'] = "张三"
ws['B2'] = 25

# 保存文件
wb.save("example.xlsx")

# 读取已有文件
wb = load_workbook("example.xlsx")
ws = wb.active

# 读取数据
for row in ws.iter_rows(values_only=True):
    print(row)

# 修改数据
ws['B2'] = 26
wb.save("example_modified.xlsx")

2. 使用pandas操作Excel

pandas是数据分析的强大工具,也提供了方便的Excel读写功能。

安装

pip install pandas openpyxl

基本操作

import pandas as pd

# 创建DataFrame
data = {
    '姓名': ['张三', '李四', '王五'],
    '年龄': [25, 30, 35],
    '城市': ['北京', '上海', '广州']
}
df = pd.DataFrame(data)

# 写入Excel
df.to_excel("pandas_example.xlsx", index=False, sheet_name='员工信息')

# 读取Excel
df_read = pd.read_excel("pandas_example.xlsx", sheet_name='员工信息')
print(df_read)

# 修改并保存
df_read['年龄'] = df_read['年龄'] + 1
df_read.to_excel("pandas_example_modified.xlsx", index=False)

3. 使用xlrd/xlwt操作旧版Excel

xlrd用于读取xls文件,xlwt用于写入xls文件(适用于Excel 2003及更早版本)。

安装

pip install xlrd xlwt

基本操作

import xlrd
import xlwt

# 写入xls文件
workbook = xlwt.Workbook()
sheet = workbook.add_sheet('Sheet1')

sheet.write(0, 0, '姓名')
sheet.write(0, 1, '年龄')
sheet.write(1, 0, '张三')
sheet.write(1, 1, 25)

workbook.save('old_example.xls')

# 读取xls文件
book = xlrd.open_workbook('old_example.xls')
sheet = book.sheet_by_index(0)

for row in range(sheet.nrows):
    print(sheet.row_values(row))

4. 高级操作

使用openpyxl处理样式

from openpyxl.styles import Font, Alignment, Border, Side

wb = Workbook()
ws = wb.active

# 设置单元格样式
ws['A1'].font = Font(bold=True, color="FF0000")
ws['A1'].alignment = Alignment(horizontal="center")
ws['A1'].border = Border(left=Side(style='thin'), 
                        right=Side(style='thin'), 
                        top=Side(style='thin'), 
                        bottom=Side(style='thin'))

wb.save("styled_example.xlsx")

使用pandas处理多个sheet

with pd.ExcelWriter('multi_sheet.xlsx') as writer:
    df1.to_excel(writer, sheet_name='Sheet1')
    df2.to_excel(writer, sheet_name='Sheet2')

5. 性能考虑

对于大型Excel文件:

  • 使用openpyxlread_only模式快速读取

  • 使用write_only模式快速写入

  • 考虑使用pandas进行批量操作

# 高性能读取
from openpyxl import load_workbook
wb = load_workbook(filename='large_file.xlsx', read_only=True)
ws = wb.active
for row in ws.iter_rows(values_only=True):
    process(row)
    
# 高性能写入
from openpyxl import Workbook
wb = Workbook(write_only=True)
ws = wb.create_sheet()
for row in data:
    ws.append(row)
wb.save('large_output.xlsx')

总结

  • 对于简单的Excel操作,pandas提供了最简洁的API

  • 对于需要精细控制的Excel文件,openpyxl是更好的选择

  • 对于旧版Excel文件(xls),可以使用xlrd/xlwt

基于PyQt实现Excel相关功能应用

下面将介绍如何使用PyQt5创建一个具有Excel操作功能的桌面应用程序,包括文件打开、数据展示、编辑和保存等功能。

环境准备

首先安装必要的库:

pip install PyQt5 pandas openpyxl xlrd

基本Excel查看器

import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableView, 
                             QFileDialog, QVBoxLayout, QWidget, 
                             QPushButton, QMessageBox)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
import pandas as pd

class ExcelViewer(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Excel查看器")
        self.setGeometry(100, 100, 800, 600)
        
        self.initUI()
        self.current_file = None
        
    def initUI(self):
        # 主窗口部件
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        
        # 布局
        self.layout = QVBoxLayout()
        
        # 按钮
        self.btn_open = QPushButton("打开Excel文件")
        self.btn_open.clicked.connect(self.open_file)
        self.btn_save = QPushButton("保存修改")
        self.btn_save.clicked.connect(self.save_file)
        self.btn_save.setEnabled(False)
        
        # 表格视图
        self.table_view = QTableView()
        self.model = QStandardItemModel()
        self.table_view.setModel(self.model)
        
        # 添加到布局
        self.layout.addWidget(self.btn_open)
        self.layout.addWidget(self.btn_save)
        self.layout.addWidget(self.table_view)
        
        self.central_widget.setLayout(self.layout)
    
    def open_file(self):
        # 打开文件对话框
        file_path, _ = QFileDialog.getOpenFileName(
            self, "打开Excel文件", "", 
            "Excel Files (*.xlsx *.xls);;All Files (*)"
        )
        
        if file_path:
            try:
                # 使用pandas读取Excel文件
                self.df = pd.read_excel(file_path)
                self.current_file = file_path
                
                # 清除旧数据
                self.model.clear()
                
                # 设置表头
                self.model.setHorizontalHeaderLabels(self.df.columns)
                
                # 填充数据
                for row in range(self.df.shape[0]):
                    for col in range(self.df.shape[1]):
                        item = QStandardItem(str(self.df.iloc[row, col]))
                        item.setFlags(item.flags() ^ Qt.ItemIsEditable)  # 默认不可编辑
                        self.model.setItem(row, col, item)
                
                self.btn_save.setEnabled(True)
                
            except Exception as e:
                QMessageBox.critical(self, "错误", f"无法打开文件:\n{str(e)}")
    
    def save_file(self):
        if not self.current_file:
            return
            
        try:
            # 从模型中获取修改后的数据
            rows = self.model.rowCount()
            cols = self.model.columnCount()
            
            data = []
            for row in range(rows):
                row_data = []
                for col in range(cols):
                    item = self.model.item(row, col)
                    row_data.append(item.text())
                data.append(row_data)
            
            # 创建DataFrame并保存
            new_df = pd.DataFrame(data, columns=self.df.columns)
            new_df.to_excel(self.current_file, index=False)
            
            QMessageBox.information(self, "成功", "文件保存成功!")
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"保存失败:\n{str(e)}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    viewer = ExcelViewer()
    viewer.show()
    sys.exit(app.exec_())

增强版Excel编辑器

下面是一个功能更全面的Excel编辑器实现:

import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableView, QFileDialog, 
                             QVBoxLayout, QHBoxLayout, QWidget, QPushButton, 
                             QMessageBox, QLabel, QLineEdit, QComboBox, QMenu, 
                             QAction, QInputDialog)
from PyQt5.QtCore import Qt, QModelIndex
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QKeySequence
import pandas as pd

class ExcelEditor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Excel编辑器")
        self.setGeometry(100, 100, 1000, 700)
        
        self.current_file = None
        self.df = None
        self.initUI()
        
    def initUI(self):
        # 主窗口部件
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        
        # 主布局
        self.main_layout = QVBoxLayout()
        
        # 顶部工具栏
        self.toolbar_layout = QHBoxLayout()
        
        self.btn_open = QPushButton("打开")
        self.btn_open.setToolTip("打开Excel文件 (Ctrl+O)")
        self.btn_open.clicked.connect(self.open_file)
        
        self.btn_save = QPushButton("保存")
        self.btn_save.setToolTip("保存当前文件 (Ctrl+S)")
        self.btn_save.clicked.connect(self.save_file)
        self.btn_save.setEnabled(False)
        
        self.btn_save_as = QPushButton("另存为")
        self.btn_save_as.setToolTip("另存为新的Excel文件")
        self.btn_save_as.clicked.connect(self.save_file_as)
        self.btn_save_as.setEnabled(False)
        
        self.btn_add_row = QPushButton("添加行")
        self.btn_add_row.setToolTip("在表格末尾添加新行")
        self.btn_add_row.clicked.connect(self.add_row)
        self.btn_add_row.setEnabled(False)
        
        self.btn_delete_row = QPushButton("删除行")
        self.btn_delete_row.setToolTip("删除选中的行")
        self.btn_delete_row.clicked.connect(self.delete_row)
        self.btn_delete_row.setEnabled(False)
        
        self.file_label = QLabel("未打开文件")
        
        self.toolbar_layout.addWidget(self.btn_open)
        self.toolbar_layout.addWidget(self.btn_save)
        self.toolbar_layout.addWidget(self.btn_save_as)
        self.toolbar_layout.addWidget(self.btn_add_row)
        self.toolbar_layout.addWidget(self.btn_delete_row)
        self.toolbar_layout.addStretch()
        self.toolbar_layout.addWidget(self.file_label)
        
        # 表格视图
        self.table_view = QTableView()
        self.table_view.setSelectionBehavior(QTableView.SelectRows)
        self.table_view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.table_view.customContextMenuRequested.connect(self.show_context_menu)
        
        self.model = QStandardItemModel()
        self.table_view.setModel(self.model)
        
        # 搜索栏
        self.search_layout = QHBoxLayout()
        self.search_label = QLabel("搜索:")
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("输入搜索内容...")
        self.search_column = QComboBox()
        self.search_column.addItem("所有列")
        self.search_btn = QPushButton("搜索")
        self.search_btn.clicked.connect(self.search_data)
        
        self.search_layout.addWidget(self.search_label)
        self.search_layout.addWidget(self.search_input)
        self.search_layout.addWidget(self.search_column)
        self.search_layout.addWidget(self.search_btn)
        
        # 添加到主布局
        self.main_layout.addLayout(self.toolbar_layout)
        self.main_layout.addLayout(self.search_layout)
        self.main_layout.addWidget(self.table_view)
        
        self.central_widget.setLayout(self.main_layout)
        
        # 添加快捷键
        self.shortcut_open = QAction(self)
        self.shortcut_open.setShortcut(QKeySequence("Ctrl+O"))
        self.shortcut_open.triggered.connect(self.open_file)
        self.addAction(self.shortcut_open)
        
        self.shortcut_save = QAction(self)
        self.shortcut_save.setShortcut(QKeySequence("Ctrl+S"))
        self.shortcut_save.triggered.connect(self.save_file)
        self.addAction(self.shortcut_save)
        
        # 状态栏
        self.statusBar().showMessage("就绪")
    
    def open_file(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "打开Excel文件", "", 
            "Excel Files (*.xlsx *.xls *.csv);;All Files (*)"
        )
        
        if file_path:
            try:
                # 根据文件扩展名选择读取方式
                if file_path.endswith('.csv'):
                    self.df = pd.read_csv(file_path)
                else:
                    self.df = pd.read_excel(file_path)
                
                self.current_file = file_path
                self.file_label.setText(f"当前文件: {file_path}")
                
                # 更新搜索列下拉框
                self.search_column.clear()
                self.search_column.addItem("所有列")
                self.search_column.addItems(self.df.columns.tolist())
                
                # 加载数据到表格
                self.load_data_to_table()
                
                self.btn_save.setEnabled(True)
                self.btn_save_as.setEnabled(True)
                self.btn_add_row.setEnabled(True)
                self.btn_delete_row.setEnabled(True)
                
                self.statusBar().showMessage(f"成功加载文件: {file_path}", 3000)
                
            except Exception as e:
                QMessageBox.critical(self, "错误", f"无法打开文件:\n{str(e)}")
                self.statusBar().showMessage(f"打开文件失败: {str(e)}", 5000)
    
    def load_data_to_table(self):
        # 清除旧数据
        self.model.clear()
        
        # 设置表头
        self.model.setHorizontalHeaderLabels(self.df.columns)
        
        # 填充数据
        for row in range(self.df.shape[0]):
            row_items = []
            for col in range(self.df.shape[1]):
                value = self.df.iloc[row, col]
                item = QStandardItem(str(value) if not pd.isna(value) else "")
                item.setEditable(True)
                row_items.append(item)
            self.model.appendRow(row_items)
        
        # 调整列宽
        self.table_view.resizeColumnsToContents()
    
    def save_file(self):
        if not self.current_file:
            return
            
        try:
            self.update_data_from_table()
            
            # 根据文件扩展名选择保存方式
            if self.current_file.endswith('.csv'):
                self.df.to_csv(self.current_file, index=False)
            else:
                self.df.to_excel(self.current_file, index=False)
            
            QMessageBox.information(self, "成功", "文件保存成功!")
            self.statusBar().showMessage("文件保存成功", 3000)
            
        except Exception as e:
            QMessageBox.critical(self, "错误", f"保存失败:\n{str(e)}")
            self.statusBar().showMessage(f"保存失败: {str(e)}", 5000)
    
    def save_file_as(self):
        if self.df is None:
            return
            
        file_path, _ = QFileDialog.getSaveFileName(
            self, "另存为", "", 
            "Excel Files (*.xlsx);;CSV Files (*.csv);;All Files (*)"
        )
        
        if file_path:
            try:
                self.update_data_from_table()
                
                if file_path.endswith('.csv'):
                    self.df.to_csv(file_path, index=False)
                else:
                    # 确保扩展名是.xlsx
                    if not file_path.endswith('.xlsx'):
                        file_path += '.xlsx'
                    self.df.to_excel(file_path, index=False)
                
                self.current_file = file_path
                self.file_label.setText(f"当前文件: {file_path}")
                
                QMessageBox.information(self, "成功", f"文件已保存到:\n{file_path}")
                self.statusBar().showMessage(f"文件已保存到: {file_path}", 3000)
                
            except Exception as e:
                QMessageBox.critical(self, "错误", f"保存失败:\n{str(e)}")
                self.statusBar().showMessage(f"保存失败: {str(e)}", 5000)
    
    def update_data_from_table(self):
        # 从表格模型获取数据并更新DataFrame
        rows = self.model.rowCount()
        cols = self.model.columnCount()
        
        data = []
        for row in range(rows):
            row_data = []
            for col in range(cols):
                item = self.model.item(row, col)
                row_data.append(item.text() if item else "")
            data.append(row_data)
        
        self.df = pd.DataFrame(data, columns=self.df.columns)
    
    def add_row(self):
        if self.df is None:
            return
            
        # 在末尾添加空行
        empty_row = [""] * len(self.df.columns)
        self.model.appendRow([QStandardItem(item) for item in empty_row])
        
        # 滚动到最后一行
        self.table_view.scrollToBottom()
        self.statusBar().showMessage("已添加新行", 2000)
    
    def delete_row(self):
        selected = self.table_view.selectionModel().selectedRows()
        if not selected:
            QMessageBox.warning(self, "警告", "请先选择要删除的行")
            return
            
        # 从后往前删除以避免索引问题
        for index in sorted(selected, reverse=True):
            self.model.removeRow(index.row())
        
        self.statusBar().showMessage(f"已删除 {len(selected)} 行", 3000)
    
    def search_data(self):
        search_text = self.search_input.text().strip().lower()
        if not search_text:
            return
            
        column = self.search_column.currentText()
        column_idx = -1 if column == "所有列" else self.search_column.currentIndex() - 1
        
        # 清除之前的高亮
        for row in range(self.model.rowCount()):
            for col in range(self.model.columnCount()):
                item = self.model.item(row, col)
                if item:
                    item.setBackground(Qt.white)
        
        found = False
        for row in range(self.model.rowCount()):
            for col in range(self.model.columnCount()):
                if column_idx != -1 and col != column_idx:
                    continue
                    
                item = self.model.item(row, col)
                if item and search_text in item.text().lower():
                    item.setBackground(Qt.yellow)
                    found = True
                    # 滚动到第一个匹配项
                    if not found:
                        self.table_view.scrollTo(self.model.index(row, col))
        
        if found:
            self.statusBar().showMessage(f"找到匹配 '{search_text}' 的内容", 3000)
        else:
            self.statusBar().showMessage(f"未找到匹配 '{search_text}' 的内容", 3000)
    
    def show_context_menu(self, position):
        if self.df is None:
            return
            
        menu = QMenu()
        
        edit_action = QAction("编辑单元格", self)
        edit_action.triggered.connect(self.edit_current_cell)
        menu.addAction(edit_action)
        
        add_row_action = QAction("添加行", self)
        add_row_action.triggered.connect(self.add_row)
        menu.addAction(add_row_action)
        
        delete_row_action = QAction("删除行", self)
        delete_row_action.triggered.connect(self.delete_row)
        menu.addAction(delete_row_action)
        
        menu.addSeparator()
        
        copy_action = QAction("复制", self)
        copy_action.triggered.connect(self.copy_selection)
        menu.addAction(copy_action)
        
        menu.exec_(self.table_view.viewport().mapToGlobal(position))
    
    def edit_current_cell(self):
        index = self.table_view.currentIndex()
        if not index.isValid():
            return
            
        old_value = self.model.itemFromIndex(index).text()
        new_value, ok = QInputDialog.getText(
            self, "编辑单元格", 
            f"输入新值 (原值: {old_value}):", 
            text=old_value
        )
        
        if ok and new_value != old_value:
            self.model.itemFromIndex(index).setText(new_value)
    
    def copy_selection(self):
        selected = self.table_view.selectionModel().selectedIndexes()
        if not selected:
            return
            
        # 获取选中的最小和最大行列
        min_row = min(index.row() for index in selected)
        max_row = max(index.row() for index in selected)
        min_col = min(index.column() for index in selected)
        max_col = max(index.column() for index in selected)
        
        # 构建文本
        text = ""
        for row in range(min_row, max_row + 1):
            row_text = []
            for col in range(min_col, max_col + 1):
                index = self.model.index(row, col)
                item = self.model.itemFromIndex(index)
                row_text.append(item.text() if item else "")
            text += "\t".join(row_text) + "\n"
        
        # 复制到剪贴板
        clipboard = QApplication.clipboard()
        clipboard.setText(text.strip())
        
        self.statusBar().showMessage("已复制选中内容到剪贴板", 2000)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    editor = ExcelEditor()
    editor.show()
    sys.exit(app.exec_())

功能扩展

  1. 数据筛选和排序:添加对表格数据的筛选和排序功能

  2. 图表生成:集成matplotlib,从Excel数据生成图表

  3. 公式支持:实现简单的Excel公式计算

  4. 多工作表支持:处理包含多个工作表的Excel文件

  5. 数据验证:添加输入数据验证功能

  6. 样式设置:允许设置单元格字体、颜色、对齐方式等

  7. 导入/导出:支持更多格式的导入导出,如JSON、SQL等

  8. 宏录制:实现简单的操作记录和回放功能

程序发布

可以使用PyInstaller将应用打包为独立可执行文件:

pip install pyinstaller
pyinstaller --onefile --windowed excel_editor.py

这将在dist目录下生成可执行文件,可以在没有Python环境的电脑上运行。

这个实现提供了一个功能完整的Excel编辑器基础,您可以根据需要进一步扩展和完善功能。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值