【问题标题】:Why does my loop work correctly on the first iteration but not on the full set I'm looping through?为什么我的循环在第一次迭代时能正常工作,但在我循环的整个集合上却不能?
【发布时间】:2021-10-26 22:05:12
【问题描述】:

我正在尝试根据其中包含的文件夹/文件批量重命名文件夹(然后将每个路径末尾的图像文件移动到它们各自的模型/颜色文件夹目录中)。 每个文件夹/文件都有类似的 MODEL_COLOR 命名约定。

下面的代码有效,但似乎只在第一个文件夹上正常工作,换句话说,文件夹被正确重命名,但最后一段代码似乎正在获取包含图像的文件夹并将其移动到相应的路径,而不是专门将图像移动到相应的路径并删除它们最初所在的文件夹。

在循环迭代的第一个文件夹中,它实际上将图像移动到正确的 Model > Color 目录中,但在之后的所有文件夹中,它似乎将包含图像的 文件夹 移动到正确的Model > Color 目录,而不是仅仅将图片单独移动到对应目录中

查看论坛后,我看到了类似的问题,即在更改目录或删除某些实例时,由于在循环过程中初始设置发生更改(即删除或重命名路径的一部分),循环无法正确迭代迭代时)。我很确定这是一个简单的解决方案,但我似乎找不到最有效的解决方案。

标准文件夹名称:

  • CL4003IN_45F
  • CL4003IN_56F
  • CL40157U_01D
  • CL40157U_52H
import glob, os, shutil

folder = 'C:\\testing'
# create new folder directory based on Model/Color [is working, but moves file_path into base directory]

# arr = []    
for file_path in glob.glob(os.path.join(folder, '*_*')):
    new_dir = file_path.rpartition('_')[0]
    new_subdir = file_path.rpartition('_')[2]
    try:
        os.mkdir(os.path.join(new_dir, new_subdir))
    except WindowsError:
        # Handle the case where the target dir already exist.
        pass
    shutil.move(file_path, os.path.join(new_dir, new_subdir))
    # arr.append(file_path)

【问题讨论】:

  • 为什么每行都多加一层缩进?
  • 这是一个复制/粘贴错误,刚刚修复它。谢谢!

标签: python python-3.x directory glob shutil


【解决方案1】:

您看不到实际错误的原因是您在except: 语句中捕获了太多错误。

你打算抓住FileExistsError,所以你也应该只寻找这个。否则你会注意到代码实际上抛出了FileNotFoundError

原因是os.mkdir 确实不会自动创建父目录。它只创建一层深的目录,但您的代码需要两层新目录。

为此,您必须改用os.makedirs(...)。方便的是,os.makedirs 还接受 exist_ok 标志,它可以摆脱整个 try:-except: 构造。

进一步的注释是你的代码中有很多重复的计算:

  • file_path.rpartition('_') 被计算两次
  • os.path.join(new_dir, new_subdir) 被计算两次

我建议将它们存储在有意义的变量中。这可以加快您的代码速度,使其更具可读性和可维护性。

这是您的代码的修改版本:

import glob
import os
import shutil

folder = 'C:\\testing'

for file_path in glob.glob(os.path.join(folder, '*_*')):
    (new_dir, _, new_subdir) = file_path.rpartition('_')
    target_path = os.path.join(new_dir, new_subdir)

    os.makedirs(target_path, exist_ok=True)

    shutil.move(file_path, target_path)

进一步的改进/修复

你的代码还有一堆错误:

  • 你不检查glob找到的东西是不是文件
  • _ 的拆分不会在目录分隔符处停止。这意味着C:\\my_files\\bla 之类的内容将被拆分为C:\\myfiles\\bla

我认为您并不关心其中任何一个,因为您认为“用户不会使用这样的脚本”。但这实际上是发生的情况:

  • 您有一个文件 C:\\my_files\\CL4003IN_45F,正如预期的那样,它将被移动到 C:\\my_files\\CL4003IN\\45F\\CL4003IN_45F
  • 您再次运行脚本。该脚本将找到C:\\my_files\\CL4003IN。它不检查它是否是一个文件夹,所以无论如何它都会处理它。然后它将其拆分为C:\\myfiles\\CL4003IN
  • 整个文件夹C:\\my_files\\CL4003IN 将移动到C:\\my\\files\\CL4003IN。因此,原始文件 CL4003IN_45FC:\\my\\files\\CL4003IN\\CL4003IN\\45F\\CL4003IN_45F 结尾

解决办法是:

  • 只在文件名上使用rpartition,而不是整个路径
  • 检查它是文件还是目录

使用pathlib 可以更轻松地解决大部分任务。我冒昧地重写了您的代码并修复了这些问题:

from pathlib import Path

folder = Path('C:\\testing')

for file_path in folder.iterdir():
    file_name = file_path.name

    # Ignore directories
    if not file_path.is_file():
        continue

    # Split the filename by '_'
    (target_dir, _, target_subdir) = file_name.rpartition('_')

    # If no '_' found, ignore
    if not target_dir:
        continue

    # Compute the target path and create it if necessary
    target_path = folder / target_dir / target_subdir
    target_path.mkdir(parents=True, exist_ok=True)

    # Move the file to its new position
    target_file_path = target_path / file_name
    file_path.rename(target_file_path)

最后一句话:folder.iterdir() 实际上确实返回了一个迭代器。但这在这种情况下应该不是问题,因为我们明确检查路径是否是现有文件而不是目录或已经被删除的东西。但是,如果您想 100% 安全,请写 list(folder.iterdir())

【讨论】:

  • 感谢您的建议,我试过了,但仍然没有解决问题。我还尝试在循环之前在它自己的变量中定义 glob,但仍然无济于事。结果在第一次迭代正确处理的情况下保持不变,但所有后续迭代都错过了一步。任何其他建议将不胜感激!
  • 既然我理解了你的问题,我想知道为什么它成功处理了第一次迭代......
  • 谢谢你!这是很棒的东西,肯定会让我在我的 python 之旅中走得更远。我应用的修复程序适用于我试图做的事情,但是您的方法涵盖了更多内容,这些内容肯定会在这个项目中派上用场。再次,非常感谢!
【解决方案2】:

通过将glob 存储在list 中来完成循环之前的glob 迭代有助于避免一些不必要的错误。

#...
for file_path in list(glob.glob(os.path.join(folder, '*_*')
...#

但是通过修改我的代码并从循环中删除以下内容:

try:
    os.mkdir(os.path.join(new_dir, new_subdir))
except WindowsError:
    pass

允许代码遍历目录中的所有文件夹,而不将文件之前的文件夹转移到new_dir > new_subdir目录中。

适用于目录中多个文件夹的新代码是:

import glob, os, shutil

folder = 'C:\\testing'

# create new folder directory based on Model > Color

for file_path in list(glob.glob(os.path.join(folder, '*_*'), recursive=True)):
    new_dir = file_path.rpartition('_')[0]
    new_subdir = file_path.rpartition('_')[2]
    shutil.move(file_path, os.path.join(new_dir, new_subdir))

这可能不是最有效的代码(并且可能无法在所有实例中工作,这仍有待确定!),但目前肯定可以按预期工作。

特别感谢那些帮助他们提出建议的人。

【讨论】:

  • 我认为您的回答实际上并没有解决最初的问题。我找到了,请给我一分钟更新我的答案
  • 另外,经过进一步研究,glob.glob 已经返回一个列表而不是一个交互器,因此它在进入循环之前已经完成。因此,在迭代时更改文件系统不是问题。
  • 如果您想将答案标记为问题的解决方案,该网站有一个功能。点击答案左侧的复选标记。
猜你喜欢
  • 2018-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多