在 Python 中列出目录树结构?
我们通常更喜欢只使用 GNU 树,但我们并不总是在每个系统上都有tree,有时可以使用 Python 3。一个好的答案可以很容易地复制粘贴,而不是要求 GNU tree。
tree 的输出如下所示:
$ tree
.
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
4 directories, 9 files
我在我称为pyscratch 的目录下的主目录中创建了上述目录结构。
我在这里也看到了接近这种输出的其他答案,但我认为我们可以做得更好,使用更简单、更现代的代码和惰性评估方法。
Python 中的树
首先,让我们使用一个示例
- 使用 Python 3
Path 对象
- 使用
yield 和yield from 表达式(用于创建生成器函数)
- 使用递归实现优雅简洁
- 使用 cmets 和一些类型注释来提高清晰度
from pathlib import Path
# prefix components:
space = ' '
branch = '│ '
# pointers:
tee = '├── '
last = '└── '
def tree(dir_path: Path, prefix: str=''):
"""A recursive generator, given a directory Path object
will yield a visual tree structure line by line
with each line prefixed by the same characters
"""
contents = list(dir_path.iterdir())
# contents each get pointers that are ├── with a final └── :
pointers = [tee] * (len(contents) - 1) + [last]
for pointer, path in zip(pointers, contents):
yield prefix + pointer + path.name
if path.is_dir(): # extend the prefix and recurse:
extension = branch if pointer == tee else space
# i.e. space because last, └── , above so no more |
yield from tree(path, prefix=prefix+extension)
现在:
for line in tree(Path.home() / 'pyscratch'):
print(line)
打印:
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
我们确实需要将每个目录具体化为一个列表,因为我们需要知道它有多长,但之后我们将列表丢弃。对于深度和广泛的递归,这应该足够懒惰。
上面的代码和 cmets 应该足以完全理解我们在这里所做的事情,但如果需要,请随时使用调试器逐步完成它,以便更好地对其进行调试。
更多功能
现在 GNU tree 为我们提供了一些我希望该功能具有的有用功能:
- 首先打印主题目录名称(自动打印,我们的不会)
- 打印
n directories, m files的计数
- 限制递归的选项,
-L level
- 仅限目录的选项,
-d
此外,当有一棵巨大的树时,限制迭代(例如使用islice)以避免将您的解释器锁定在文本中很有用,因为在某些时候输出变得过于冗长而无用。默认情况下,我们可以将其设置为任意高 - 比如1000。
那么让我们删除之前的 cmets 并填写这个功能:
from pathlib import Path
from itertools import islice
space = ' '
branch = '│ '
tee = '├── '
last = '└── '
def tree(dir_path: Path, level: int=-1, limit_to_directories: bool=False,
length_limit: int=1000):
"""Given a directory Path object print a visual tree structure"""
dir_path = Path(dir_path) # accept string coerceable to Path
files = 0
directories = 0
def inner(dir_path: Path, prefix: str='', level=-1):
nonlocal files, directories
if not level:
return # 0, stop iterating
if limit_to_directories:
contents = [d for d in dir_path.iterdir() if d.is_dir()]
else:
contents = list(dir_path.iterdir())
pointers = [tee] * (len(contents) - 1) + [last]
for pointer, path in zip(pointers, contents):
if path.is_dir():
yield prefix + pointer + path.name
directories += 1
extension = branch if pointer == tee else space
yield from inner(path, prefix=prefix+extension, level=level-1)
elif not limit_to_directories:
yield prefix + pointer + path.name
files += 1
print(dir_path.name)
iterator = inner(dir_path, level=level)
for line in islice(iterator, length_limit):
print(line)
if next(iterator, None):
print(f'... length_limit, {length_limit}, reached, counted:')
print(f'\n{directories} directories' + (f', {files} files' if files else ''))
现在我们可以得到与tree 相同的输出:
tree(Path.home() / 'pyscratch')
打印:
pyscratch
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
4 directories, 9 files
我们可以限制级别:
tree(Path.home() / 'pyscratch', level=2)
打印:
pyscratch
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ └── subpackage2
└── package2
└── __init__.py
4 directories, 3 files
我们可以限制输出到目录:
tree(Path.home() / 'pyscratch', level=2, limit_to_directories=True)
打印:
pyscratch
├── package
│ ├── subpackage
│ └── subpackage2
└── package2
4 directories
回顾
回想起来,我们可以使用path.glob 进行匹配。我们也可以使用path.rglob 进行递归通配,但这需要重写。我们也可以使用itertools.tee,而不是具体化目录内容列表,但这可能会产生负面影响,并且可能会使代码更加复杂。
欢迎评论!