【问题标题】:Write a recursive function to list all paths of parts.txt编写递归函数列出parts.txt的所有路径
【发布时间】:2019-11-24 20:07:21
【问题描述】:

编写一个函数list_files_recursive,它返回所有parts.txt 文件的路径列表,而不使用os 模块的walk 生成器。相反,该函数应该使用递归。输入将是目录名称。 这是我到目前为止的代码,我认为它基本上是正确的,但是发生的事情是输出不是一个完整的列表?

def list_files_recursive(top_dir):
    rec_list_files = []
    list_dir = os.listdir(top_dir)
    for item in list_dir:
        item_path = os.path.join(top_dir, item)
        if os.path.isdir(item_path):
            list_files_recursive(item_path)
        else:
            if os.path.basename(item_path) == 'parts.txt': 
                rec_list_files.append(os.path.join(item_path))
    print(rec_list_files)
    return rec_list_files

这是我得到的输出的一部分(来自打印语句):

['CarItems/Honda/Accord/1996/parts.txt']
[]
['CarItems/Honda/Odyssey/2000/parts.txt']
['CarItems/Honda/Odyssey/2002/parts.txt']
[]

所以问题在于它不是一个列表,而且那里有空列表。我不太清楚为什么这不起作用,并且已经尝试了一切来解决它。任何帮助都非常感谢!

【问题讨论】:

  • 感谢您的操作方法等。我会确保注意这一点并保持礼貌!
  • 没问题。这次更新看起来很清晰。

标签: python-3.x list directory os.walk


【解决方案1】:

这非常接近,但问题是list_files_recursive 的子调用不会将结果传递回父。做到这一点的一种方法是将来自每个子调用的所有列表连接在一起,或者通过调用链一直传递对单个列表的引用。

请注意,在rec_list_files.append(os.path.join(item_path)) 中,os.path.join 只有一个参数是没有意义的。 print(rec_list_files) 应作为 side effect 省略,这会使输出难以解释——仅在调用者中打印。此外,

else:
    if ... :

这里可以更清楚地写成elif:,因为它们在逻辑上是等价的。尽可能减少条件嵌套总是一个好主意。

这是通过扩展父列表起作用的方法:

import os 

def list_files_recursive(top_dir):
    files = []

    for item in os.listdir(top_dir):
        item_path = os.path.join(top_dir, item)

        if os.path.isdir(item_path):
            files.extend(list_files_recursive(item_path)) 
            #     ^^^^^^ add child results to parent
        elif os.path.basename(item_path) == "parts.txt": 
            files.append(item_path)

    return files

if __name__ == "__main__":
    print(list_files_recursive("foo"))

或者通过调用树传递一个结果列表:

import os 

def list_files_recursive(top_dir, files=[]):
    for item in os.listdir(top_dir):
        item_path = os.path.join(top_dir, item)

        if os.path.isdir(item_path):
            list_files_recursive(item_path, files)
            #                               ^^^^^ pass our result list recursively
        elif os.path.basename(item_path) == "parts.txt": 
            files.append(item_path)

    return files

if __name__ == "__main__":
    print(list_files_recursive("foo"))

这些函数的一个主要问题是它们仅适用于查找精确命名为parts.txt 的文件,因为该字符串文字是hard coded。这使得它除了直接目的之外几乎毫无用处。我们应该添加一个参数,允许调用者指定他们想要搜索的目标文件,使函数通用。

另一个问题是该函数并没有像它的名字所声称的那样:list_files_recursive 应该真正称为find_file_recursive,或者,由于硬编码字符串,find_parts_txt_recursive

除此之外,该函数非常适合转换为 generator 函数,这是用于遍历的常见 Python 习惯用法,特别是在子目录可能包含大量数据且保存在内存中的成本很高的情况下一次全部。生成器还允许灵活地使用该函数在第一次匹配后取消搜索,从而进一步增强其(重)可用性。

yield 关键字也使函数代码本身非常干净——我们可以避免完全保留结果数据结构的问题,只需按需触发结果项。

我是这样写的:

import os 

def find_file_recursive(top_dir, target):
    for item in os.listdir(top_dir):
        item_path = os.path.join(top_dir, item)

        if os.path.isdir(item_path):
            yield from find_file_recursive(item_path, target)
        elif os.path.basename(item_path) == target:
            yield item_path

if __name__ == "__main__":
    print(list(find_file_recursive("foo", "parts.txt")))

【讨论】: