使用Python的ezodf库更新ODS文件计数器失效问题排查
问题:ODS文件计数器更新后未保存生效
背景说明
- 有两个ODS文件:
- AppAndroid.ods:包含
appFromAppStore和Others两个工作表,表头为Application、No.、Version、Category、Encryption、Interest、Comments、Counter,用于记录历史应用信息,Counter列统计应用被识别次数。 - UnsupportedApp.ods:包含
appFromAppStore_Result和Others_Result两个工作表,表头无Counter列,是临时文件,每次Python程序执行时更新。
- AppAndroid.ods:包含
- 程序逻辑:将UnsupportedApp.ods的信息同步到AppAndroid.ods,若应用已存在则
Counter加1,不存在则新增行并设Counter=1。 - 问题现象:控制台显示计数器正常递增,但保存后打开ODS文件数据未更新。
原代码
import os import pyexcel as pe import ezodf def increment_counter(app_name, data): app_exists = any(row['Application'] == app_name for row in data) if app_exists: for row in data: if row['Application'] == app_name: row['Counter'] += 1 else: new_row = {'Application': app_name, 'Version': '', 'Category': '', 'Encryption': '', 'Interest': '', 'Comments': '', 'Counter': 1} data.append(new_row) def update_counter(source_file, destination_file, source_sheet, destination_sheet): source_data = pe.get_records(file_name=source_file, sheet_name=source_sheet) destination_data = pe.get_records(file_name=destination_file, sheet_name=destination_sheet) for row in source_data: app_name = row['Application'] increment_counter(app_name, destination_data) destination_book = ezodf.opendoc(destination_file) destination_sheet_index = -1 for i, sheet in enumerate(destination_book.sheets): if sheet.name == destination_sheet: destination_sheet_index = i break destination_book.sheets[destination_sheet_index].data = destination_data destination_book.saveas(destination_file) bak_file = f"{destination_file}.bak" if os.path.exists(bak_file): os.remove(bak_file) source_file = 'UnsupportedApp.ods' source_sheet = 'appFromAppStore_Result' destination_sheet = 'appFromAppStore' update_counter(source_file, filePathAppAndroid, source_sheet, destination_sheet)
核心解决思路
1. 修复数据格式不兼容问题(根本原因)
pyexcel.get_records()返回字典列表,但ezodf工作表的data属性要求的是二维列表(列表的列表),直接赋值字典列表会导致文件无法识别更新内容。修正步骤:
- 先获取目标工作表的表头顺序,确保数据列对应正确;
- 将更新后的字典列表按表头顺序转换为二维列表;
- 清空原有数据行(保留表头),再逐行插入新数据。
2. 完善新增行的字段赋值
原代码新增行时将Version等字段设为空,既不符合需求也可能导致数据结构异常,改为从源数据行中读取对应字段值。
3. 优化文件操作逻辑
- 避免使用
saveas()生成冗余备份文件,改用save()直接覆盖; - 增加文件占用检测,避免因目标文件被其他程序(如LibreOffice、WPS)打开导致保存失败;
- 明确工作表定位逻辑,避免因索引错误导致更新无效。
修正后的代码
import os import pyexcel as pe import ezodf def increment_counter(app_name, data, source_row): app_exists = any(row['Application'] == app_name for row in data) if app_exists: for row in data: if row['Application'] == app_name: row['Counter'] += 1 else: new_row = { 'Application': app_name, 'Version': source_row.get('Version', ''), 'Category': source_row.get('Category', ''), 'Encryption': source_row.get('Encryption', ''), 'Interest': source_row.get('Interest', ''), 'Comments': source_row.get('Comments', ''), 'Counter': 1 } data.append(new_row) def update_counter(source_file, destination_file, source_sheet, destination_sheet): source_data = pe.get_records(file_name=source_file, sheet_name=source_sheet) destination_data = pe.get_records(file_name=destination_file, sheet_name=destination_sheet) for row in source_data: app_name = row['Application'] increment_counter(app_name, destination_data, row) destination_book = ezodf.opendoc(destination_file) # 定位目标工作表 dest_sheet = None for sheet in destination_book.sheets: if sheet.name == destination_sheet: dest_sheet = sheet break if not dest_sheet: print(f"工作表 {destination_sheet} 不存在") return # 获取表头顺序,确保列对应正确 headers = [cell.value for cell in dest_sheet.row(0)] # 将字典列表转换为二维列表 new_data_rows = [] for row_dict in destination_data: row_list = [row_dict.get(header, '') for header in headers] new_data_rows.append(row_list) # 清空原有数据行(保留表头) while len(dest_sheet.rows) > 1: dest_sheet.delete_row(1) # 插入新数据行 for row_idx, row_data in enumerate(new_data_rows, start=1): dest_sheet.insert_row(row_idx) for col_idx, cell_value in enumerate(row_data): dest_sheet[row_idx, col_idx].set_value(cell_value) # 保存文件并处理异常 try: destination_book.save() bak_file = f"{destination_file}.bak" if os.path.exists(bak_file): os.remove(bak_file) print("文件更新保存成功") except PermissionError: print("权限错误:文件可能被其他程序打开,请关闭后重试") source_file = 'UnsupportedApp.ods' source_sheet = 'appFromAppStore_Result' destination_sheet = 'appFromAppStore' filePathAppAndroid = 'AppAndroid.ods' # 补充定义目标文件路径 update_counter(source_file, filePathAppAndroid, source_sheet, destination_sheet)
内容的提问来源于stack exchange,提问作者Louis Chabert
相关产品推荐
相关产品推荐

