You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

根本原因分析

  1. 只读模式的文件句柄管理逻辑:

    • 当read_only=True时,openpyxl创建的ReadOnlyWorksheet采用按需加载策略,不会一次性读取所有数据到内存,而是保留与原文件的关联。即使调用wb_source.close(),底层关联的ZipFile句柄可能未被完全释放,多次重复打开关闭操作会累积句柄泄漏,导致文件被持续占用。
    • 非只读模式下,openpyxl会一次性将所有工作表数据加载到内存,关闭工作簿时会彻底释放所有文件句柄,因此无占用问题,但也带来了巨大的性能开销。
  2. MIME类型初始化的次要影响:

    • openpyxl全局初始化MimeTypes对象时,Python标准库会读取系统mime.types文件,虽然这部分代码用with open保证了句柄释放,但如果多次打开xlsx过程中初始化出现异常,可能间接影响资源回收,但这并非核心原因。

验证结论

单次打开文件并缓存所有数据的规避方案有效,证明多次打开关闭只读模式工作簿导致的句柄泄漏是问题核心——单次打开时所有操作复用同一个文件句柄,关闭后能正确释放,因此移动文件不会被占用。

内容的提问来源于stack exchange,提问作者JustBeingHelpful

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 22:46:07