Al fin luego de mucho trabajar ya encontramos el resultado de esta duda que muchos de nuestros lectores de este sitio web han presentado. Si deseas aportar algo puedes aportar tu conocimiento.
Solución:
Aquí hay una función para hacer eso con el formato:
import os
def list_files(startpath):
for root, dirs, files in os.walk(startpath):
level = root.replace(startpath, '').count(os.sep)
indent = ' ' * 4 * (level)
print('/'.format(indent, os.path.basename(root)))
subindent = ' ' * 4 * (level + 1)
for f in files:
print(''.format(subindent, f))
Similar a las respuestas anteriores, pero para python3, posiblemente legible y posiblemente extensible:
from pathlib import Path
class DisplayablePath(object):
display_filename_prefix_middle = '├──'
display_filename_prefix_last = '└──'
display_parent_prefix_middle = ' '
display_parent_prefix_last = '│ '
def __init__(self, path, parent_path, is_last):
self.path = Path(str(path))
self.parent = parent_path
self.is_last = is_last
if self.parent:
self.depth = self.parent.depth + 1
else:
self.depth = 0
@property
def displayname(self):
if self.path.is_dir():
return self.path.name + '/'
return self.path.name
@classmethod
def make_tree(cls, root, parent=None, is_last=False, criteria=None):
root = Path(str(root))
criteria = criteria or cls._default_criteria
displayable_root = cls(root, parent, is_last)
yield displayable_root
children = sorted(list(path
for path in root.iterdir()
if criteria(path)),
key=lambda s: str(s).lower())
count = 1
for path in children:
is_last = count == len(children)
if path.is_dir():
yield from cls.make_tree(path,
parent=displayable_root,
is_last=is_last,
criteria=criteria)
else:
yield cls(path, displayable_root, is_last)
count += 1
@classmethod
def _default_criteria(cls, path):
return True
@property
def displayname(self):
if self.path.is_dir():
return self.path.name + '/'
return self.path.name
def displayable(self):
if self.parent is None:
return self.displayname
_filename_prefix = (self.display_filename_prefix_last
if self.is_last
else self.display_filename_prefix_middle)
parts = ['!s !s'.format(_filename_prefix,
self.displayname)]
parent = self.parent
while parent and parent.parent is not None:
parts.append(self.display_parent_prefix_middle
if parent.is_last
else self.display_parent_prefix_last)
parent = parent.parent
return ''.join(reversed(parts))
Uso de ejemplo:
paths = DisplayablePath.make_tree(Path('doc'))
for path in paths:
print(path.displayable())
Salida de ejemplo:
doc/
├── _static/
│ ├── embedded/
│ │ ├── deep_file
│ │ └── very/
│ │ └── deep/
│ │ └── folder/
│ │ └── very_deep_file
│ └── less_deep_file
├── about.rst
├── conf.py
└── index.rst
Notas
- Esto usa la recursividad. Generará un RecursionError en realmente profundo árboles de carpetas
- El árbol se evalúa perezosamente. Debería comportarse bien en realidad amplio árboles de carpetas. Sin embargo, los hijos inmediatos de una carpeta determinada no se evalúan de forma perezosa.
Editar:
- ¡Bonificación adicional! devolución de llamada de criterios para filtrar rutas.
Listar la estructura del árbol de directorios en Python?
Por lo general, preferimos usar el árbol GNU, pero no siempre tenemos tree
en todos los sistemas y, a veces, Python 3 está disponible. Una buena respuesta aquí podría copiarse y pegarse fácilmente y no hacer GNU tree
un requerimiento.
tree
La salida se ve así:
$ 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
Creé la estructura de directorio anterior en mi directorio de inicio en un directorio al que llamo pyscratch
.
También veo otras respuestas aquí que se acercan a ese tipo de resultado, pero creo que podemos hacerlo mejor, con un código más simple y moderno y enfoques de evaluación perezosa.
Árbol en Python
Para empezar, usemos un ejemplo que
- usa Python 3
Path
objeto - usa el
yield
yyield from
expresiones (que crean una función generadora) - utiliza la recursividad para una simplicidad elegante
- utiliza comentarios y algunos tipos de anotaciones para mayor claridad
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)
y ahora:
for line in tree(Path.home() / 'pyscratch'):
print(line)
huellas dactilares:
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ └── module.py
│ └── subpackage2
│ ├── __init__.py
│ ├── __main__.py
│ └── module2.py
└── package2
└── __init__.py
Necesitamos materializar cada directorio en una lista porque necesitamos saber cuánto tiempo tiene, pero luego tiramos la lista. Para una recursividad amplia y profunda, esto debería ser lo suficientemente perezoso.
El código anterior, con los comentarios, debería ser suficiente para comprender completamente lo que estamos haciendo aquí, pero siéntase libre de revisarlo con un depurador para asimilarlo mejor si es necesario.
Más características
Ahora GNU tree
nos da un par de características útiles que me gustaría tener con esta función:
- imprime primero el nombre del directorio del asunto (lo hace automáticamente, el nuestro no)
- imprime el recuento de
n directories, m files
- opción para limitar la recursividad,
-L level
- opción de limitar solo a directorios,
-d
Además, cuando hay un árbol enorme, es útil limitar la iteración (por ejemplo, con islice
) para evitar bloquear su intérprete con texto, ya que en algún momento la salida se vuelve demasiado detallada para ser útil. Podemos hacer esto arbitrariamente alto por defecto, digamos 1000
.
Así que eliminemos los comentarios anteriores y completemos esta funcionalidad:
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'ndirectories directories' + (f', files files' if files else ''))
Y ahora podemos obtener el mismo tipo de salida que tree
:
tree(Path.home() / 'pyscratch')
huellas dactilares:
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
Y podemos restringir a niveles:
tree(Path.home() / 'pyscratch', level=2)
huellas dactilares:
pyscratch
├── package
│ ├── __init__.py
│ ├── __main__.py
│ ├── subpackage
│ └── subpackage2
└── package2
└── __init__.py
4 directories, 3 files
Y podemos limitar la salida a directorios:
tree(Path.home() / 'pyscratch', level=2, limit_to_directories=True)
huellas dactilares:
pyscratch
├── package
│ ├── subpackage
│ └── subpackage2
└── package2
4 directories
Retrospectivo
En retrospectiva, podríamos haber usado path.glob
para emparejar. Quizás también podríamos usar path.rglob
para el globbing recursivo, pero eso requeriría una reescritura. También podríamos usar itertools.tee
en lugar de materializar una lista de contenidos de directorio, pero eso podría tener compensaciones negativas y probablemente haría que el código fuera aún más complejo.
¡Los comentarios son bienvenidos!
Sección de Reseñas y Valoraciones
Eres capaz de sostener nuestro trabajo ejecutando un comentario y dejando una puntuación te damos las gracias.