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
and yield from
式を使用する(ジェネレーター関数を作成する)
- 再帰を使用してエレガントなシンプルさ
- コメントといくつかの型注釈を使用してさらに明確にする
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
各ディレクトリを具体化してリストにする必要があります。これは、その長さを知る必要があるためですが、その後、リストを破棄します。深くて広範な再帰の場合、これは十分に遅延する必要があります。
上記のコードとコメントは、ここで何をしているのかを完全に理解するのに十分ですが、必要に応じて、デバッガを使用してコードをより適切に理解することができます。
その他の機能
今GNU tree
は私がこの機能で持っていたいいくつかの便利な機能を私たちに提供します:
- サブジェクトディレクトリ名を最初に出力します(自動的に実行されますが、自動では行われません)。
- カウントを出力します
n directories, m files
- 再帰を制限するオプション、
-L level
- ディレクトリのみに制限するオプション、
-d
また、巨大なツリーがある場合islice
、ある時点で出力が冗長になりすぎて役に立たなくなるため、反復を(たとえばで)制限して、インタープリターがテキストでロックされないようにすると便利です。デフォルトでこれを任意に高くすることができます1000
。
それでは、前のコメントを削除して、この機能に記入しましょう:
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
ディレクトリの内容のリストを具体化する代わりにを使用することもできますが、これにはマイナスのトレードオフがあり、コードがさらに複雑になる可能性があります。
コメントは大歓迎です!