Comment obtenir tous les sous-répertoires immédiats en Python

150

J'essaie d'écrire un simple script Python qui copiera un index.tpl vers index.html dans tous les sous-répertoires (à quelques exceptions près).

Je m'embourbe en essayant d'obtenir la liste des sous-répertoires.

Steven Noble
la source
11
Vous constaterez peut-être que la réponse acceptée à cette question SO précédente résout le problème: stackoverflow.com/questions/120656/directory-listing-in-python
Jarret Hardie

Réponses:

31

J'ai fait des tests de vitesse sur diverses fonctions pour renvoyer le chemin complet vers tous les sous-répertoires actuels.

tl; dr: utilisez toujours scandir:

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

Bonus: Avec scandirvous pouvez également obtenir simplement les noms de dossiers en utilisant f.nameau lieu de f.path.

Ceci (ainsi que toutes les autres fonctions ci-dessous) n'utilisera pas le tri naturel . Cela signifie que les résultats seront triés comme suit: 1, 10, 2. Pour obtenir un tri naturel (1, 2, 10), veuillez consulter https://stackoverflow.com/a/48030307/2441026




Résultats : scandirest: 3x plus rapide que walk, 32x plus rapide que listdir(avec filtre), 35x plus rapide que Pathlibet 36x plus rapide que listdiret 37x (!) Plus rapide que glob.

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

Testé avec W7x64, Python 3.8.1. Dossier avec 440 sous-dossiers.
Au cas où vous vous demanderiez si cela listdirpourrait être accéléré en ne faisant pas os.path.join () deux fois, oui, mais la différence est pratiquement inexistante.

Code:

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}")
user136036
la source
1
Je veux juste vous remercier, je cherchais vraiment cela. Excellente analyse.
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
la source
76

Pourquoi personne n'a mentionné glob? globvous permet d'utiliser l'expansion des noms de chemin de style Unix, et c'est ma fonction préférée pour presque tout ce qui a besoin de trouver plus d'un nom de chemin. Cela rend les choses très faciles:

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

Notez que globcela renverra le répertoire avec la barre oblique finale (comme le ferait unix) tandis que la plupart des pathsolutions basées omettra la barre oblique finale.

Ari
la source
3
Bonne solution, simple et fonctionne. Pour ceux qui ne veulent pas de cette dernière barre oblique, il peut l'utiliser paths = [ p.replace('/', '') for p in glob('*/') ].
Evan Hu
5
Il peut être plus sûr de simplement couper le dernier caractère avec [p[:-1] for p in paths], car cette méthode replace remplacera également toutes les barres obliques échappées dans le nom de fichier (non pas que celles-ci soient courantes).
ari
3
Encore plus sûr, utilisez strip ('/') pour supprimer les barres obliques de fin. De cette façon, vous ne coupez aucun caractère autre que des barres obliques
Eliezer Miron
8
Par construction, vous êtes assuré d'avoir une barre oblique à la fin (donc ce n'est pas plus sûr), mais je pense que c'est plus lisible. Cependant, vous voulez certainement utiliser à la rstripplace de strip, car ce dernier transformera tous les chemins complets en chemins relatifs.
ari
7
complément au commentaire @ari pour les débutants en python tels que I: strip('/')supprimera à la fois le début et la fin '/', rstrip('/')ne supprimera que le dernier
Titou
35

Cochez " Obtenir une liste de tous les sous-répertoires dans le répertoire courant ".

Voici une version de Python 3:

import os

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

print(dir_list)
Geng Jiawen
la source
2
Extrêmement intelligent. Bien que l'efficacité ne compte pas ( ... c'est totalement le cas ), je suis curieux de savoir si cette expression ou celle du générateur basé sur glob (s.rstrip("/") for s in glob(parent_dir+"*/"))est plus efficace en temps. Mon soupçon intuitif est qu'une solution stat()basée sur une base devrait être profondément plus rapide que le globbing de type shell. Malheureusement, je n'ai pas la volonté de le découvrir. os.walk()timeit
Cecil Curry
3
Notez que cela renvoie les noms de sous-répertoire sans le nom du répertoire parent préfixé.
Paul Chernoch
19
import os, os.path

Pour obtenir (chemin complet) des sous-répertoires immédiats dans un répertoire:

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

Pour obtenir le dernier sous-répertoire (le plus récent):

def LatestDirectory (d):
    return max(SubDirPath(d), key=os.path.getmtime)
Milan
la source
Pour obtenir une liste , ajoutez simplement list( filter(...) ).
user136036
12

os.walk est votre ami dans cette situation.

Directement à partir de la documentation:

walk () génère les noms de fichiers dans une arborescence de répertoires, en parcourant l'arborescence de haut en bas ou de bas en haut. Pour chaque répertoire de l'arborescence enraciné au sommet du répertoire (y compris le sommet lui-même), il donne un 3-tuple (dirpath, dirnames, fichiers).

Andrew Cox
la source
1
Sachez simplement que si vous ne voulez que les sous-répertoires de premier niveau, sortez de l'itération os.walk après le premier ensemble de valeurs de retour.
yoyo
11

Cette méthode fait tout cela en une seule fois.

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

Utilisation du module 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

Étant donné que certains commentateurs ont demandé quels sont les avantages d'utiliser les bibliothèques de Twisted pour cela, je vais aller un peu au-delà de la question originale ici.


Il existe une documentation améliorée dans une branche qui explique les avantages de FilePath; vous voudrez peut-être lire cela.

Plus précisément dans cet exemple: contrairement à la version standard de la bibliothèque, cette fonction peut être implémentée sans importation . La fonction "sous-répertoires" est totalement générique, en ce qu'elle n'opère que sur son argument. Pour copier et déplacer les fichiers en utilisant la bibliothèque standard, vous devez dépendre du " open" builtin listdir"," peut-être " isdir" ou " os.walk" ou " shutil.copy". Peut-être " os.path.join" aussi. Sans parler du fait que vous avez besoin d'une chaîne passée un argument pour identifier le fichier réel. Jetons un coup d'œil à l'implémentation complète qui copiera "index.tpl" de chaque répertoire dans "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 fonction "sous-répertoires" ci-dessus peut fonctionner sur n'importe quel FilePathobjet semblable. Ce qui signifie, entre autres, des ZipPathobjets. MalheureusementZipPath est actuellement en lecture seule, mais il pourrait être étendu pour prendre en charge l'écriture.

Vous pouvez également transmettre vos propres objets à des fins de test. Afin de tester les API utilisant os.path suggérées ici, vous devez singe avec des noms importés et des dépendances implicites et généralement effectuer de la magie noire pour que vos tests fonctionnent. Avec FilePath, vous faites quelque chose comme ceci:

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(...))
Glyphe
la source
Comme je suis peu exposé à Twisted, je suis toujours heureux d'avoir des informations et des exemples supplémentaires; cette réponse est agréable à voir pour cela. Cela dit, étant donné que cette approche semble nécessiter beaucoup plus de travail que l'utilisation des modules python intégrés et une installation Twisted, y a-t-il des avantages à utiliser cela que vous pourriez ajouter à la réponse?
Jarret Hardie
1
La réponse de Glyph a probablement été inspirée par le fait que TwistedLore utilise également des fichiers .tpl.
Constantin
Eh bien, clairement, je ne m'attends pas à l'inquisition espagnole :-) J'ai supposé que "* .tpl" était une référence générique à une extension abstraite signifiant "modèle", et non à un modèle Twisted spécifique (j'ai vu .tpl utilisé dans beaucoup langues après tout). Bon à savoir.
Jarret Hardie
+1 donc pour ramener à l'angle Twisted possible, bien que j'aimerais quand même comprendre ce que l'objet 'FilePath' et la fonction 'walk ()' de Twisted ajoutent à l'API standard.
Jarret Hardie
Personnellement, je trouve que "FilePath.walk () produit des objets de chemin" est beaucoup plus facile à retenir que "os.walk donne 3-tuples de dir, dirs, files". Mais il y a d'autres avantages. FilePath permet le polymorphisme, ce qui signifie que vous pouvez parcourir des choses autres que des systèmes de fichiers. Par exemple, vous pouvez passer un twisted.python.zippath.ZipArchive à ma fonction 'sous-répertoires' et obtenir un générateur de ZipPaths au lieu de FilePaths; votre logique ne change pas, mais votre application gère désormais comme par magie les fichiers zip. Si vous voulez le tester, il vous suffit de fournir un objet, vous n'avez pas à écrire de vrais fichiers.
Glyph
4

Je viens d'écrire du code pour déplacer les machines virtuelles vmware, et j'ai fini par utiliser os.pathet shutilpour effectuer la copie de fichiers entre les sous-répertoires.

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))

Ce n'est pas très élégant, mais ça marche.

l'homme d'étain
la source
1

Voici une façon:

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
la source
-1: ne fonctionnera pas, car shutil.copy copiera dans le répertoire actuel, vous finirez donc par écraser «index.html» dans le répertoire actuel une fois pour chaque «index.tpl» que vous trouverez dans l'arborescence des sous-répertoires.
nosklo
1

Je dois mentionner la bibliothèque path.py , que j'utilise très souvent.

Récupérer les sous-répertoires immédiats devient aussi simple que cela:

my_dir.dirs()

L'exemple de travail complet est:

from path import Path

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

subdirs = my_directory.dirs()

NB: my_directory peut toujours être manipulé comme une chaîne, puisque Path est une sous-classe de string, mais fournit un tas de méthodes utiles pour manipuler les chemins

olinox14
la source
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 fonction suivante peut être appelée comme:

get_folders_in_directories_recursively (directory, index = 1) -> donne la liste des dossiers du premier niveau

get_folders_in_directories_recursively (directory) -> donne tous les sous-dossiers

Kanish Mathew
la source
faire bien, version python 3.6, mais j'avais besoin d'effacer "self", à partir des variables de fonction internes
locometro
1
utilisait à l'intérieur d'une classe, ont mis à jour
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_dirsfonction prend un chemin dans un répertoire et retourne une liste des sous-répertoires immédiats qu'il contient .

dir
 |
  -- dir_1
  -- dir_2

child_dirs('dir') -> ['dir_1', 'dir_2']
Amjad
la source
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
la source
0

Un liner utilisant pathlib:

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