我想并行重命名文件,这里我选择使用 Pool 来做到这一点。
def rename_files(folder, prefix, n):
files = os.listdir(folder)
n = 4
tasks = [
(
os.path.join(folder, file),
os.path.join(
folder,
f"{prefix}{i+1:0{n}}{Path(file).suffix}",
),
)
for i, file in enumerate(files)
]
with Pool(os.cpu_count() // 2) as pool:
pool.starmap(rename_file, tasks)
当我多次执行它时,我的文件数量不知何故减少,有人可以解释一下吗?
操作系统:Linux 22.04
python:3.12.3
你的代码中存在一个潜在的竞争条件,这可能会导致文件丢失:
问题:
你正在枚举文件并同时创建重命名任务。如果在创建所有任务之前文件夹中的文件发生变化(例如,添加或删除文件),则 enumerate(files)
的结果与 pool.starmap
开始处理任务时的实际文件列表可能不同步。
示例:
- 假设你的文件夹中有
file1.txt
,file2.txt
和file3.txt
。 - 代码枚举文件并创建了三个重命名任务。
- 在
pool.starmap
开始处理任务 之前 ,另一个进程或程序删除了file2.txt
。 pool.starmap
开始处理任务,并成功重命名了file1.txt
和file3.txt
。- 但是,由于
file2.txt
不再存在,因此与其对应的重命名任务将会失败。更糟糕的是,由于文件列表的偏移,file3.txt
的重命名目标会被错误地应用于原本应该用于file2.txt
的新文件名。
结果: 你丢失了一个文件 ( file2.txt
),并且另一个文件 ( file3.txt
) 被错误地重命名。
解决方案:
为了避免此问题,你应该在创建任务 之前 收集所有文件名,并确保在处理过程中文件列表不会更改:
import os
from multiprocessing import Pool
from pathlib import Path
def rename_files(folder, prefix, n):
# 在创建任务之前获取所有文件名
files = os.listdir(folder)
n = 4
tasks = [
(
os.path.join(folder, file),
os.path.join(
folder,
f"{prefix}{i+1:0{n}}{Path(file).suffix}",
),
)
for i, file in enumerate(files)
]
with Pool(os.cpu_count() // 2) as pool:
pool.starmap(rename_file, tasks)
其他建议:
- 你还可以考虑使用
os.rename(src, dst)
来代替rename_file
函数,因为它是由操作系统直接提供的原子操作,可以避免一些潜在的错误。 - 为了更好地处理错误,你可以让
rename_file
函数返回一个布尔值或错误信息,然后在主进程中检查结果。