Windows下最直接的方式是用os.open()以os.O_EXCL | os.O_RDWR模式尝试打开文件,若抛PermissionError或BlockingIOError则说明被独占锁定;需先验证路径存在且为文件,捕获PermissionError及其子类AccessDeniedError,并注意该方法仅适用于Windows。

用 os.open() 尝试独占打开文件
Windows 下最直接的方式是尝试以 os.O_EXCL | os.O_RDWR 模式打开文件——如果失败且报 PermissionError 或 BlockingIOError,通常说明文件正被其他进程以独占方式锁定(比如 Excel 正在编辑 .xlsx,或记事本正打开 .txt)。
注意:这个方法只对「真正加锁」的文件有效;如果只是被读打开(如用 open(..., 'r')),多数情况下不会阻塞,所以检测不到。
- 必须用
os.open(),不能用内置open(),后者不暴露底层锁语义 - 记得
os.close()成功打开后的 fd,否则会泄漏句柄 - Linux/macOS 上该方法基本无效(系统默认不强制文件锁),仅适用于 Windows
捕获 PermissionError 和 AccessDeniedError
在 Windows 上,很多锁定行为会直接抛出 PermissionError: [Errno 13] Permission denied。但别只 catch PermissionError —— 某些 Python 版本(尤其 3.12+)把这类错误细化为 AccessDeniedError(继承自 PermissionError),所以建议统一 catch 父类或两者都写。
典型误判场景:文件权限不足(非锁定)、路径不存在、目录被占用等,都会触发同类异常。需结合 os.path.exists() 和 os.path.isfile() 排除基础问题。
立即学习“Python免费学习笔记(深入)”;
- 先确认路径存在且是文件:
os.path.isfile(path) - 再尝试打开,捕获
(PermissionError, OSError)更稳妥(因为某些底层错误可能不精确抛PermissionError) - 不要依赖错误信息字符串匹配(如
"denied"),不同语言环境/系统版本返回内容可能不同
用 mmap 触发共享冲突(进阶技巧)
当目标文件已被其他进程以 FILE_SHARE_NONE 打开时,Python 的 mmap.mmap() 在 Windows 上会立即失败并抛 PermissionError。这比单纯 os.open() 更敏感,能检测到部分 os.open() 漏掉的锁定场景(例如某些数据库或备份工具的只读锁定)。
但代价是:它会实际映射内存页,若文件极大,可能引发短暂内存压力;且同样仅限 Windows。
- 使用
mmap.mmap(-1, 0, access=mmap.ACCESS_READ)创建匿名映射,再用mmap.PAGESIZE大小尝试映射目标文件 - 失败时 close 映射对象,避免资源残留
- Linux/macOS 不支持此行为,调用会直接抛
OSError: [Errno 22] Invalid argument
跨平台方案只能靠「间接试探」
没有真正的跨平台文件锁定检测机制。Linux/macOS 默认允许并发读写,所谓“占用”更多是应用层约定(如 .lock 文件、进程 pid 记录),而非内核级强制锁。因此通用做法是:检查是否有同名 .lock 文件、读取 /proc/*/fd/(Linux)或 lsof -Fn | grep(macOS)——但这些需要 shell 权限和额外依赖,无法纯 Python 实现。
生产环境建议放弃“绝对检测”,转为设计容错逻辑:比如写入前重试 3 次,每次间隔 100ms;或用临时文件 + os.replace() 原子替换,避开直接操作原文件。
-
os.replace()在同一文件系统上是原子的,可规避大部分“正在写入”的竞争 - 不要依赖第三方包如
psutil查进程句柄——它在容器或受限环境中常不可用,且性能开销大 - 记住:你不是在检测“是否被锁”,而是在判断“此刻能否安全操作”。后者更务实


















