Python'da dizin ağacı yapısını listelemek ister misiniz?
Genellikle sadece GNU ağacını kullanmayı tercih ederiz, ancak tree
her sistemde her zaman bulunmayabilir ve bazen Python 3 kullanılabilir. Buradaki iyi bir cevap, kolayca kopyalanıp yapıştırılabilir ve GNU'yu tree
bir gereksinim haline getirmeyebilir .
tree
çıktısı şuna benzer:
$ 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
Yukarıdaki dizin yapısını ana dizinimde aradığım bir dizin altında oluşturdum pyscratch
.
Burada bu tür çıktılara yaklaşan başka yanıtlar da görüyorum, ancak bence daha basit, daha modern kod ve tembel değerlendirme yaklaşımları ile daha iyisini yapabiliriz.
Python'da Ağaç
Başlamak için bir örnek verelim.
- Python 3
Path
nesnesini kullanır
yield
ve yield from
ifadelerini kullanır (bir oluşturucu işlevi oluşturan)
- zarif bir sadelik için özyinelemeyi kullanır
- Ekstra netlik için yorumlar ve bazı tür ek açıklamaları kullanır
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)
ve şimdi:
for line in tree(Path.home() / 'pyscratch'):
print(line)
baskılar:
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
Her dizini bir liste halinde düzenlememiz gerekiyor çünkü ne kadar uzun olduğunu bilmemiz gerekiyor, ancak daha sonra listeyi atıyoruz. Derin ve geniş özyineleme için bu yeterince tembel olmalıdır.
Yorumlarla birlikte yukarıdaki kod, burada ne yaptığımızı tam olarak anlamak için yeterli olmalıdır, ancak gerekirse daha iyi bir şekilde düzeltmek için bir hata ayıklayıcı ile adım adım ilerlemekten çekinmeyin.
Daha fazla özellik
Şimdi GNU tree
bize bu işlevle sahip olmak istediğim birkaç yararlı özellik sunuyor:
- önce konu dizin adını yazdırır (bunu otomatik olarak yapar, bizimki yazmaz)
- sayısını yazdırır
n directories, m files
- özyinelemeyi sınırlama seçeneği,
-L level
- sadece dizinlerle sınırlama seçeneği,
-d
Ayrıca, büyük bir ağaç olduğunda, islice
yorumlayıcınızı metinle kilitlemekten kaçınmak için yinelemeyi sınırlamak (örn. İle) yararlıdır , çünkü bir noktada çıktı yararlı olamayacak kadar ayrıntılı hale gelir. Bunu varsayılan olarak rastgele yüksek yapabiliriz - diyelim 1000
.
Öyleyse önceki yorumları kaldıralım ve bu işlevi dolduralım:
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 ''))
Ve şimdi aynı tür çıktıyı elde edebiliriz tree
:
tree(Path.home() / 'pyscratch')
baskılar:
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
Ve seviyelerle sınırlayabiliriz:
tree(Path.home() / 'pyscratch', level=2)
baskılar:
pyscratch
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ └── subpackage2
└── package2
└── __init__.py
4 directories, 3 files
Ve çıktıyı dizinlerle sınırlayabiliriz:
tree(Path.home() / 'pyscratch', level=2, limit_to_directories=True)
baskılar:
pyscratch
├── package
│ ├── subpackage
│ └── subpackage2
└── package2
4 directories
Geriye dönük
Geriye dönüp baktığımızda, path.glob
eşleştirme için kullanabilirdik. path.rglob
Özyinelemeli globbing için de kullanabiliriz , ancak bunun yeniden yazılması gerekir. itertools.tee
Bir dizin içeriği listesi oluşturmak yerine kullanabiliriz , ancak bu olumsuz ödünleşmelere sahip olabilir ve muhtemelen kodu daha da karmaşık hale getirebilir.
Yorumlara açığız!