openpyxl 3.0.7只读模式下多次操作后文件句柄未释放问题求助
openpyxl 3.0.7 只读模式下文件句柄未释放导致移动文件失败问题排查
问题现象
- 使用
openpyxl.load_workbook(file_path, read_only=True)多次打开并关闭同一xlsx文件后,执行shutil.move会抛出PermissionError(提示文件被其他进程占用)。 - 若将
read_only设为False则无此问题,但性能大幅下降,运行耗时增加1小时以上。 - 已通过单次打开文件并缓存23个工作表所有数据的方式规避问题,需明确根本原因。
代码示例
import openpyxl # openpyxl 3.0.7 # 重复执行多次打开/关闭操作 wb_source = openpyxl.load_workbook(file_path, read_only=True) ws_source = wb_source[worksheet_name] for row in ws_source.rows: for cell in row: # 处理单元格数据 pass wb_source.close() shutil.move(file_path, file_path_archive)
异常信息
Traceback (most recent call last): File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.7_3.7.2544.0_x64__qbz5n2kfra8p0\lib\shutil.py", line 566, in move os.rename(src, real_dst) PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:\\Python\\...file.xlsx' -> 'C:\\Python\\...file.xlsx'
排查到的相关源码片段
1. openpyxl\reader\excel.py
# Python stdlib imports from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile from sys import exc_info from io import BytesIO import os.path import warnings # ... if self.read_only: ws = ReadOnlyWorksheet(self.wb, sheet.name, rel.target, self.shared_strings) ws.sheet_state = sheet.state self.wb._sheets.append(ws) continue else: fh = self.archive.open(rel.target) ws = self.wb.create_sheet(sheet.name) ws._rels = rels ws_parser = WorksheetReader(ws, fh, self.shared_strings, self.data_only) ws_parser.bind_all()
2. openpyxl\packaging\manifest.py
mimetypes = MimeTypes()
3. Python标准库mimetypes.py
class MimeTypes: def init(files=None): global suffix_map, types_map, encodings_map, common_types global inited, _db inited = True # 避免MimeTypes.__init__再次调用此方法 if files is None or _db is None: db = MimeTypes() if _winreg: db.read_windows_registry() if files is None: files = knownfiles else: files = knownfiles + list(files) else: db = _db for file in files: if os.path.isfile(file): db.read(file) # <-------------------------------------- 读取系统mime文件 encodings_map = db.encodings_map suffix_map = db.suffix_map types_map = db.types_map[True] common_types = db.types_map[False] # 初始化完成后将DB设为全局变量 _db = db
def read(self, filename, strict=True): """ 读取单个mime.types格式文件,由路径指定。 如果strict为True,信息将添加到标准类型列表,否则添加到非标准类型列表。 """ with open(filename, encoding='utf-8') as fp: self.readfp(fp, strict)
def readfp(self, fp, strict=True): """ 读取单个mime.types格式文件。 如果strict为True,信息将添加到标准类型列表,否则添加到非标准类型列表。 """ while 1: line = fp.readline() if not line: break words = line.split() for i in range(len(words)): if words[i][0] == '#': del words[i:] break if not words: continue type, suffixes = words[0], words[1:] for suff in suffixes: self.add_type(type, '.' + suff, strict)
根本原因分析
只读模式的文件句柄管理逻辑:
- 当
read_only=True时,openpyxl创建的ReadOnlyWorksheet采用按需加载策略,不会一次性读取所有数据到内存,而是保留与原文件的关联。即使调用wb_source.close(),底层关联的ZipFile句柄可能未被完全释放,多次重复打开关闭操作会累积句柄泄漏,导致文件被持续占用。 - 非只读模式下,openpyxl会一次性将所有工作表数据加载到内存,关闭工作簿时会彻底释放所有文件句柄,因此无占用问题,但也带来了巨大的性能开销。
- 当
MIME类型初始化的次要影响:
- openpyxl全局初始化
MimeTypes对象时,Python标准库会读取系统mime.types文件,虽然这部分代码用with open保证了句柄释放,但如果多次打开xlsx过程中初始化出现异常,可能间接影响资源回收,但这并非核心原因。
- openpyxl全局初始化
验证结论
单次打开文件并缓存所有数据的规避方案有效,证明多次打开关闭只读模式工作簿导致的句柄泄漏是问题核心——单次打开时所有操作复用同一个文件句柄,关闭后能正确释放,因此移动文件不会被占用。
内容的提问来源于stack exchange,提问作者JustBeingHelpful
相关产品推荐
相关产品推荐

