【问题标题】:Updating os.walk file lists in real-time [python]实时更新 os.walk 文件列表 [python]
【发布时间】:2017-09-11 17:58:10
【问题描述】:

我有一个函数,我想枚举目标文件夹中的所有文件和文件夹。当/如果它找到 rar 文件,我希望它提取它们然后删除它们。在多部分存档的情况下,它还将检查并删除剩余的文件(已与第一卷一起提取)。

我在 for 循环中使用 os.listdir,但这种方法的问题是: a)我认为它不会在不为它们编写递归循环的情况下处理子文件夹(我不想这样做,因为递归伤害了我的头)。 b) 因为 for 循环仅在开始时创建项目的字典(?),所以当它循环到已在先前迭代中删除的文件名时,我将无法找到该文件。

似乎 os.walk 对于上面的“a)”可能更好,我迄今为止的研究表明,我应该能够在每次迭代时实时更新 os.walk。但是我不知道该怎么做。

我有这样的东西:

for root, dirs, files in os.walk('d:\\test'):
    for file in files:
        print 'files (before remove): ', file, files
        # This is where I would do some operation that deletes one or more files.
        files.remove(file)
        print 'files (after remove): ', file, files

但是输出是这样的:

D:\test>d:\Python27\python.exe d:\file.py
files (before remove):  Crystal.part01.rar ['Crystal.part01.rar', 'Crystal.part02.rar', 'Crystal.part03.rar', 'Crystal.part04.rar', 'Crystal.part05.rar', 'Crystal.part06.rar']
files (after remove):  Crystal.part01.rar ['Crystal.part02.rar', 'Crystal.part03.rar', 'Crystal.part04.rar', 'Crystal.part05.rar', 'Crystal.part06.rar']
files (before remove):  Crystal.part03.rar ['Crystal.part02.rar', 'Crystal.part03.rar', 'Crystal.part04.rar', 'Crystal.part05.rar', 'Crystal.part06.rar']
files (after remove):  Crystal.part03.rar ['Crystal.part02.rar', 'Crystal.part04.rar', 'Crystal.part05.rar', 'Crystal.part06.rar']
files (before remove):  Crystal.part05.rar ['Crystal.part02.rar', 'Crystal.part04.rar', 'Crystal.part05.rar', 'Crystal.part06.rar']
files (after remove):  Crystal.part05.rar ['Crystal.part02.rar', 'Crystal.part04.rar', 'Crystal.part06.rar']

我认为这是有道理的...我们可以看到列表正在更新,但是因为我已经卡在(第二个)For 语句中,该语句创建了一个文件列表,它继续尝试循环遍历原始列表现在偏移一的顺序,产生“跳过”效果。

除了让调用循环知道跳过已删除的项目外,如何实现对目录中每个文件的操作?

更新 - 我假设可以做到这一点可能是不正确的。给我这个想法的原因是从 python 文档中截取的:

当topdown为True时,调用者可以就地修改目录名列表 (可能使用del 或切片赋值),而walk() 只会递归 进入名称保留在 dirnames 中的子目录;这可以是 用于修剪搜索,强加特定的访问顺序,甚至 通知walk() 调用者创建或重命名的目录 在它再次恢复 walk() 之前。自顶向下时修改目录名 False 对 walk 的行为没有影响,因为在自下而上 模式 dirnames 中的目录在 dirpath 本身之前生成 已生成。

再次阅读时,我发现它只提到了目录名而不是文件名 - 所以虽然我仍然不明白实现这一点的确切方法,但看起来你可能只能在适当的位置操作目录名。

【问题讨论】:

  • 不要从 files 中删除任何内容。
  • 除了将其作为标准的 os.walk 循环遍历文件外,什么都不做。如果 Crystal.part01.rar(循环中的第一个)的处理还包括删除第 2-5 部分,则脚本将以The system cannot find the file specified: 错误终止。我需要让调用 loop/walk/whatever 知道某些文件已被删除,它不应该尝试处理它们。
  • 补充一下,因为你可能会因为简洁而在我头上说话 - 我知道我可以简单地允许脚本在这里出错并捕获异常 - 假设我们应该期望文件是如果我们只是删除它们就丢失了。但是我不确定这是否真的是最佳实践(?),因为我们会期望/继续任何可能过于宽泛的 IOError...
  • 如果您想使用结果来修改底层文件系统,您可以循环访问 os 调用,将它们存储在一个列表中,然后在列表上执行您的操作,然后完成。完成修改后,您可以随时再次调用 os.walk。
  • 我更新了 OP 以更正/指定我所指的内容。仍然希望看到一个符合我用例的可行代码示例。谢谢

标签: python os.walk


【解决方案1】:
for root, dirs, files in os.walk('d:\\test'):
    for file in files:
        #process stuff

files 是您正在迭代的列表 - you should not modify it,正如您所发现的。当您process stuff 时,如果您在 for 循环迭代中删除了一个尚未到达的文件,那么您可以做三件事(我能想到的)

  1. 在处理之前检查文件是否存在

    if fname not in os.listdir(os.getcwd()):
        continue
    
  2. 使用 try/except 来捕获 IOError。如果您想进一步限制异常处理,您可以在 except 套件中查询“没有这样的文件或目录:'yourfilehere'”的错误文本,如果有不同则重新引发异常。

    fname = 'foo.bar'
    try:
        with open(fname) as f:
            pass
    except IOError as e:
        #print(e, str(e), repr(e))
        if 'No such file' in str(e):
            pass
    else:
        raise
    
  3. 我想您可以保留一个单独的列表/集,其中包含您的进程已删除的所有文件,并在尝试处理之前检查文件是否在其中。


如果你真的需要,你可以编写一个具有你需要的行为的类。

#Python 2.7 code
import collections
class F(collections.deque):
    def __iter__(self):
        return self
    def next(self):
        try:
            return self.pop()
        except IndexError:
            raise StopIteration

a = [1,2,3,4]
f = F(a)
for n in f:
    print n
    if n == 3:
        f.remove(2)

结果

>>> 
4
3
1
>>>

【讨论】:

  • 谢谢。我也得出了这两个结论。现在我已经重组了我的函数,以便一次在一个文件上调用 process 函数。它可能比其他两个选项效率低。我也对@KennyOstrom 的列表可能性感兴趣。但是,是否有任何构造允许从 for 类型的迭代循环中动态删除?我对 os.walk 文档的解释是否错误,该文档指出可以为目录名完成此操作?
猜你喜欢
  • 1970-01-01
  • 2020-03-29
  • 1970-01-01
  • 1970-01-01
  • 2019-01-06
  • 1970-01-01
  • 2021-03-08
  • 1970-01-01
  • 2019-02-05
相关资源
最近更新 更多