Liste de l'arborescence des répertoires en Python?
Nous préférons généralement utiliser l'arborescence GNU, mais nous n'en avons pas toujours tree
sur tous les systèmes, et parfois Python 3 est disponible. Une bonne réponse ici pourrait être facilement copiée et ne pas faire de GNU tree
une exigence.
tree
La sortie de ressemble à ceci:
$ 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
J'ai créé la structure de répertoires ci-dessus dans mon répertoire personnel sous un répertoire que j'appelle pyscratch
.
Je vois également d'autres réponses ici qui abordent ce genre de sortie, mais je pense que nous pouvons faire mieux, avec un code plus simple et plus moderne et des approches d'évaluation paresseuses.
Arbre en Python
Pour commencer, utilisons un exemple qui
- utilise l'
Path
objet Python 3
- utilise les expressions
yield
et yield from
(qui créent une fonction de générateur)
- utilise la récursivité pour une simplicité élégante
- utilise des commentaires et des annotations de type pour plus de clarté
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)
et maintenant:
for line in tree(Path.home() / 'pyscratch'):
print(line)
imprime:
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
Nous avons besoin de matérialiser chaque répertoire dans une liste parce que nous avons besoin de savoir combien de temps il dure, mais ensuite nous jetons la liste. Pour une récursion profonde et large, cela devrait être assez paresseux.
Le code ci-dessus, avec les commentaires, devrait être suffisant pour bien comprendre ce que nous faisons ici, mais n'hésitez pas à le parcourir avec un débogueur pour mieux le faire si vous en avez besoin.
Plus de fonctionnalités
Maintenant, GNU tree
nous donne quelques fonctionnalités utiles que j'aimerais avoir avec cette fonction:
- imprime d'abord le nom du répertoire sujet (le fait automatiquement, le nôtre ne le fait pas)
- imprime le nombre de
n directories, m files
- option pour limiter la récursivité,
-L level
- possibilité de se limiter aux répertoires,
-d
De plus, quand il y a un arbre énorme, il est utile de limiter l'itération (par exemple avec islice
) pour éviter de bloquer votre interpréteur avec du texte, car à un moment donné la sortie devient trop verbeuse pour être utile. Nous pouvons rendre cela arbitrairement élevé par défaut - disons 1000
.
Supprimons donc les commentaires précédents et remplissons cette fonctionnalité:
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 ''))
Et maintenant, nous pouvons obtenir le même type de sortie que tree
:
tree(Path.home() / 'pyscratch')
imprime:
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
Et nous pouvons nous limiter aux niveaux:
tree(Path.home() / 'pyscratch', level=2)
imprime:
pyscratch
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ └── subpackage2
└── package2
└── __init__.py
4 directories, 3 files
Et nous pouvons limiter la sortie aux répertoires:
tree(Path.home() / 'pyscratch', level=2, limit_to_directories=True)
imprime:
pyscratch
├── package
│ ├── subpackage
│ └── subpackage2
└── package2
4 directories
Rétrospective
Rétrospectivement, nous aurions pu utiliser path.glob
pour l'appariement. Nous pourrions peut-être aussi l'utiliser path.rglob
pour le globbing récursif, mais cela nécessiterait une réécriture. Nous pourrions également utiliser itertools.tee
au lieu de matérialiser une liste de contenus de répertoires, mais cela pourrait avoir des compromis négatifs et rendrait probablement le code encore plus complexe.
Les commentaires sont les bienvenus!