Cómo obtener todos los subdirectorios inmediatos en Python

150

Estoy tratando de escribir un script Python simple que copiará un index.tpl a index.html en todos los subdirectorios (con algunas excepciones).

Me estoy estancando al tratar de obtener la lista de subdirectorios.

Steven Noble
fuente
11
Puede encontrar que la respuesta aceptada en esta pregunta SO anterior resuelve el problema: stackoverflow.com/questions/120656/directory-listing-in-python
Jarret Hardie

Respuestas:

31

Hice algunas pruebas de velocidad en varias funciones para devolver la ruta completa a todos los subdirectorios actuales.

tl; dr: siempre use scandir:

list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]

Bonificación: con scandirusted también solo puede obtener nombres de carpetas utilizando en f.namelugar de f.path.

Esto (así como todas las demás funciones a continuación) no utilizará la ordenación natural . Esto significa que los resultados se ordenarán así: 1, 10, 2. Para obtener una clasificación natural (1, 2, 10), consulte https://stackoverflow.com/a/48030307/2441026




Resultados : scandires: 3x más rápido que walk, 32x más rápido que listdir(con filtro), 35x más rápido que Pathliby 36x más rápido que listdiry 37x (!) Más rápido que glob.

Scandir:           0.977
Walk:              3.011
Listdir (filter): 31.288
Pathlib:          34.075
Listdir:          35.501
Glob:             36.277

Probado con W7x64, Python 3.8.1. Carpeta con 440 subcarpetas.
En caso de que se pregunte si listdirpodría acelerarse al no hacer os.path.join () dos veces, sí, pero la diferencia es básicamente inexistente.

Código:

import os
import pathlib
import timeit
import glob

path = r"<example_path>"



def a():
    list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]
    # print(len(list_subfolders_with_paths))


def b():
    list_subfolders_with_paths = [os.path.join(path, f) for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))]
    # print(len(list_subfolders_with_paths))


def c():
    list_subfolders_with_paths = []
    for root, dirs, files in os.walk(path):
        for dir in dirs:
            list_subfolders_with_paths.append( os.path.join(root, dir) )
        break
    # print(len(list_subfolders_with_paths))


def d():
    list_subfolders_with_paths = glob.glob(path + '/*/')
    # print(len(list_subfolders_with_paths))


def e():
    list_subfolders_with_paths = list(filter(os.path.isdir, [os.path.join(path, f) for f in os.listdir(path)]))
    # print(len(list(list_subfolders_with_paths)))


def f():
    p = pathlib.Path(path)
    list_subfolders_with_paths = [x for x in p.iterdir() if x.is_dir()]
    # print(len(list_subfolders_with_paths))



print(f"Scandir:          {timeit.timeit(a, number=1000):.3f}")
print(f"Listdir:          {timeit.timeit(b, number=1000):.3f}")
print(f"Walk:             {timeit.timeit(c, number=1000):.3f}")
print(f"Glob:             {timeit.timeit(d, number=1000):.3f}")
print(f"Listdir (filter): {timeit.timeit(e, number=1000):.3f}")
print(f"Pathlib:          {timeit.timeit(f, number=1000):.3f}")
usuario136036
fuente
1
Solo quiero agradecerte, realmente estaba buscando esto. Gran análisis
Cing
225
import os
def get_immediate_subdirectories(a_dir):
    return [name for name in os.listdir(a_dir)
            if os.path.isdir(os.path.join(a_dir, name))]
RichieHindle
fuente
76

¿Por qué nadie ha mencionado glob? globle permite usar la expansión de nombre de ruta de estilo Unix, y es mi función para casi todo lo que necesita encontrar más de un nombre de ruta. Lo hace muy fácil:

from glob import glob
paths = glob('*/')

Tenga en cuenta que globdevolverá el directorio con la barra diagonal final (como lo haría Unix), mientras que la mayoría de las pathsoluciones basadas omitirán la barra diagonal final.

ari
fuente
3
Buena solución, simple y funciona. Para aquellos que no quieren ese corte final, puede usar esto paths = [ p.replace('/', '') for p in glob('*/') ].
Evan Hu
55
Puede ser más seguro simplemente cortar el último carácter [p[:-1] for p in paths], ya que ese método de reemplazo también reemplazará las barras diagonales escapadas en el nombre del archivo (no es que sean comunes).
Ari
3
Aún más seguro, use la tira ('/') para eliminar las barras diagonales. De esta manera se garantiza que no se eliminarán los caracteres que no sean barras diagonales
Eliezer Miron,
8
Por construcción, se garantiza que tendrá una barra inclinada (por lo que no es más seguro), pero creo que es más legible. Sin embargo, definitivamente desea usar en rstriplugar de strip, ya que este último convertirá las rutas totalmente calificadas en rutas relativas.
ari
77
complemento al comentario de @ari para los novatos de Python como I: strip('/')eliminará tanto el inicio como el final '/', rstrip('/')eliminará solo el final
Titou
35

Marque " Obtener una lista de todos los subdirectorios en el directorio actual ".

Aquí hay una versión de Python 3:

import os

dir_list = next(os.walk('.'))[1]

print(dir_list)
Geng Jiawen
fuente
2
Extremadamente inteligente. Si bien la eficiencia no importa ( ... lo hace totalmente ), tengo curiosidad por saber si esto o la expresión del generador basada en glob (s.rstrip("/") for s in glob(parent_dir+"*/"))es más eficiente en el tiempo. Mi sospecha intuitiva es que una solución stat()basada debería ser profundamente más rápida que el glob de estilo shell. Lamentablemente, me falta la voluntad y realmente lo descubro. os.walk()timeit
Cecil Curry
3
Tenga en cuenta que esto devuelve los nombres de subdirectorio sin el nombre del directorio principal prefijado.
Paul Chernoch
19
import os, os.path

Para obtener subdirectorios inmediatos (ruta completa) en un directorio:

def SubDirPath (d):
    return filter(os.path.isdir, [os.path.join(d,f) for f in os.listdir(d)])

Para obtener el último (más nuevo) subdirectorio:

def LatestDirectory (d):
    return max(SubDirPath(d), key=os.path.getmtime)
Milán
fuente
Para obtener una lista , simplemente agregue list( filter(...) ).
user136036
12

os.walk Es tu amigo en esta situación.

Directamente de la documentación:

walk () genera los nombres de archivo en un árbol de directorios, caminando el árbol de arriba hacia abajo o de abajo hacia arriba. Para cada directorio en el árbol enraizado en la parte superior del directorio (incluida la parte superior), produce una tupla de 3 (dirpath, dirnames, nombres de archivo).

Andrew Cox
fuente
1
Solo tenga en cuenta que si solo desea los subdirectorios de primer nivel, salga de la iteración os.walk después del primer conjunto de valores de retorno.
Yoyo
11

Este método lo hace todo de una vez.

from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]
SuaveSouris
fuente
7

Usando el módulo FilePath de Twisted:

from twisted.python.filepath import FilePath

def subdirs(pathObj):
    for subpath in pathObj.walk():
        if subpath.isdir():
            yield subpath

if __name__ == '__main__':
    for subdir in subdirs(FilePath(".")):
        print "Subdirectory:", subdir

Dado que algunos comentaristas han preguntado cuáles son las ventajas de usar las bibliotecas de Twisted para esto, iré un poco más allá de la pregunta original aquí.


Hay una documentación mejorada en una rama que explica las ventajas de FilePath; tal vez quieras leer eso.

Más específicamente en este ejemplo: a diferencia de la versión de biblioteca estándar, esta función se puede implementar sin importar . La función "subdire" es totalmente genérica, en el sentido de que opera únicamente con su argumento. Para copiar y mover los archivos utilizando la biblioteca estándar, debe depender de " open" incorporado listdir",", quizás " isdir" o " os.walk" o " shutil.copy". Quizás " os.path.join" también. Sin mencionar el hecho de que necesita una cadena pasó un argumento para identificar el archivo real. Echemos un vistazo a la implementación completa que copiará el "index.tpl" de cada directorio a "index.html":

def copyTemplates(topdir):
    for subdir in subdirs(topdir):
        tpl = subdir.child("index.tpl")
        if tpl.exists():
            tpl.copyTo(subdir.child("index.html"))

La función "subdire" anterior puede funcionar en cualquier FilePathobjeto similar. Lo que significa, entre otras cosas, ZipPathobjetos. Lamentablemente, ZipPathes de solo lectura en este momento, pero podría ampliarse para admitir la escritura.

También puede pasar sus propios objetos con fines de prueba. Para probar las API que usan os.path sugeridas aquí, debe usar los nombres importados y las dependencias implícitas y, en general, realizar magia negra para que sus pruebas funcionen. Con FilePath, haces algo como esto:

class MyFakePath:
    def child(self, name):
        "Return an appropriate child object"

    def walk(self):
        "Return an iterable of MyFakePath objects"

    def exists(self):
        "Return true or false, as appropriate to the test"

    def isdir(self):
        "Return true or false, as appropriate to the test"
...
subdirs(MyFakePath(...))
Glifo
fuente
Como tengo poca exposición a Twisted, siempre agradezco información y ejemplos adicionales; esta respuesta es agradable de ver para eso. Habiendo dicho eso, dado que este enfoque parece requerir mucho más trabajo que el uso de los módulos integrados de Python y una instalación Twisted, ¿hay alguna ventaja en usar esto que pueda agregar a la respuesta?
Jarret Hardie
1
La respuesta de Glyph probablemente se inspiró en el hecho de que TwistedLore también usa archivos .tpl.
Constantin
Bueno, claramente no espero la inquisición española :-) Asumí que "* .tpl" era una referencia genérica a alguna extensión abstracta que significa "plantilla", y no una plantilla Twisted específica (he visto que .tpl se usa en muchos idiomas después de todo). Bueno saber.
Jarret Hardie
+1, por lo tanto, para girar al posible ángulo Twisted, aunque todavía me gustaría entender lo que el objeto Twisted'd 'FilePath' y la función 'walk ()' agregan a la API estándar.
Jarret Hardie
Personalmente, encuentro que "FilePath.walk () produce objetos de ruta" mucho más fácil de recordar que "os.walk produce 3-tuplas de dir, dirs, archivos". Pero hay otros beneficios. FilePath permite el polimorfismo, lo que significa que puede atravesar otras cosas que no sean sistemas de archivos. Por ejemplo, podría pasar un twisted.python.zippath.ZipArchive a mi función 'subdireccionar' y obtener un generador de ZipPaths en lugar de FilePaths; su lógica no cambia, pero su aplicación ahora maneja mágicamente archivos zip. Si desea probarlo, solo tiene que proporcionar un objeto, no tiene que escribir archivos reales.
Glifo
4

Acabo de escribir un código para mover máquinas virtuales vmware, y terminé usando os.pathy shutilpara realizar la copia de archivos entre subdirectorios.

def copy_client_files (file_src, file_dst):
    for file in os.listdir(file_src):
            print "Copying file: %s" % file
            shutil.copy(os.path.join(file_src, file), os.path.join(file_dst, file))

No es terriblemente elegante, pero funciona.

el hombre de hojalata
fuente
1

Aquí hay una manera:

import os
import shutil

def copy_over(path, from_name, to_name):
  for path, dirname, fnames in os.walk(path):
    for fname in fnames:
      if fname == from_name:
        shutil.copy(os.path.join(path, from_name), os.path.join(path, to_name))


copy_over('.', 'index.tpl', 'index.html')
Scott Kirkwood
fuente
-1: no funcionará, ya que shutil.copy se copiará en el directorio actual, por lo que terminará sobrescribiendo 'index.html' en el directorio actual una vez por cada 'index.tpl' que encuentre en el árbol del subdirectorio.
nosklo
1

Tengo que mencionar la biblioteca path.py , que uso con mucha frecuencia.

Obtener los subdirectorios inmediatos se vuelve tan simple como eso:

my_dir.dirs()

El ejemplo de trabajo completo es:

from path import Path

my_directory = Path("path/to/my/directory")

subdirs = my_directory.dirs()

NB: my_directory todavía se puede manipular como una cadena, ya que Path es una subclase de cadena, pero proporciona un montón de métodos útiles para manipular rutas

olinox14
fuente
1
def get_folders_in_directories_recursively(directory, index=0):
    folder_list = list()
    parent_directory = directory

    for path, subdirs, _ in os.walk(directory):
        if not index:
            for sdirs in subdirs:
                folder_path = "{}/{}".format(path, sdirs)
                folder_list.append(folder_path)
        elif path[len(parent_directory):].count('/') + 1 == index:
            for sdirs in subdirs:
                folder_path = "{}/{}".format(path, sdirs)
                folder_list.append(folder_path)

    return folder_list

La siguiente función se puede llamar como:

get_folders_in_directories_recursively (directorio, índice = 1) -> da la lista de carpetas en el primer nivel

get_folders_in_directories_recursively (directorio) -> da todas las subcarpetas

Kanish Mathew
fuente
funciona bien, la versión Python 3.6, pero necesitaba borrar "self", desde las variables de función internas
locometro
1
estaba usando dentro de una clase, he actualizado
Kanish Mathew
0
import glob
import os

def child_dirs(path):
     cd = os.getcwd()        # save the current working directory
     os.chdir(path)          # change directory 
     dirs = glob.glob("*/")  # get all the subdirectories
     os.chdir(cd)            # change directory to the script original location
     return dirs

La child_dirsfunción toma una ruta en un directorio y devuelve una lista de los subdirectorios inmediatos en él.

dir
 |
  -- dir_1
  -- dir_2

child_dirs('dir') -> ['dir_1', 'dir_2']
Amjad
fuente
0
import pathlib


def list_dir(dir):
    path = pathlib.Path(dir)
    dir = []
    try:
        for item in path.iterdir():
            if item.is_dir():
                dir.append(item)
        return dir
    except FileNotFoundError:
        print('Invalid directory')
Yossarian42
fuente
0

Un revestimiento usando pathlib:

list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]
bsimpson53
fuente