Jak uzyskać wszystkie bezpośrednie podkatalogi w Pythonie

150

Próbuję napisać prosty skrypt w Pythonie, który skopiuje plik index.tpl do index.html we wszystkich podkatalogach (z kilkoma wyjątkami).

Grzęznę w kłopotach, próbując uzyskać listę podkatalogów.

Steven Noble
źródło
11
Może się okazać, że zaakceptowana odpowiedź na to wcześniejsze pytanie SO rozwiązuje problem: stackoverflow.com/questions/120656/directory-listing-in-python
Jarret Hardie

Odpowiedzi:

31

Przeprowadziłem testy szybkości różnych funkcji, aby zwrócić pełną ścieżkę do wszystkich bieżących podkatalogów.

tl; dr: Zawsze używaj scandir:

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

Bonus: dzięki scandirmożesz także po prostu uzyskać nazwy folderów tylko za pomocą f.namezamiast f.path.

Ta (podobnie jak wszystkie inne funkcje poniżej) nie będzie używać naturalnego sortowania . Oznacza to, że wyniki będą sortowane w następujący sposób: 1, 10, 2. Aby uzyskać naturalne sortowanie (1, 2, 10), spójrz na https://stackoverflow.com/a/48030307/2441026




Wyniki : scandirto: 3x szybciej niż walk, 32x szybciej niż listdir(z filtrem), 35x szybciej niż Pathlibi 36x szybciej niż listdiri 37x (!) Szybciej niż glob.

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

Testowane z W7x64, Python 3.8.1. Folder z 440 podfolderami.
Jeśli zastanawiasz się, czy listdirmożna by przyspieszyć, nie wykonując dwukrotnie os.path.join (), tak, ale różnica w zasadzie nie istnieje.

Kod:

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
źródło
1
Chciałem tylko podziękować, naprawdę tego szukałem. Świetna analiza.
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
źródło
76

Dlaczego nikt o tym nie wspomniał glob? globpozwala korzystać z rozwijania nazw ścieżek w stylu uniksowym i jest moim działaniem dla prawie wszystkiego, co musi znaleźć więcej niż jedną nazwę ścieżki. To bardzo ułatwia:

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

Zauważ, że globzwróci katalog z końcowym ukośnikiem (tak jak zrobiłby to Unix), podczas gdy większość pathrozwiązań bazujących pomija końcowy ukośnik.

ari
źródło
3
Dobre rozwiązanie, proste i działa. Dla tych, którzy nie chcą tego ostatecznego cięcia, może tego użyć paths = [ p.replace('/', '') for p in glob('*/') ].
Evan Hu
5
Bezpieczniej może być po prostu wyciąć ostatni znak za pomocą [p[:-1] for p in paths], ponieważ ta metoda replace zastąpi również wszelkie ukośniki w nazwie pliku (nie są one powszechne).
ari
3
Jeszcze bezpieczniej, użyj strip („/”), aby usunąć końcowe ukośniki. W ten sposób gwarantujesz, że nie usuniesz żadnych postaci, które nie są ukośnikami do przodu
Eliezer Miron
8
Dzięki konstrukcji masz gwarancję końcowego ukośnika (więc nie jest to bezpieczniejsze), ale myślę, że jest bardziej czytelny. Na pewno chcesz użyć rstripzamiast strip, ponieważ ta ostatnia zamieni wszystkie w pełni kwalifikowane ścieżki w ścieżki względne.
ari
7
uzupełnienie komentarza @ari dla początkujących pythona, takich jak I: strip('/')usunie zarówno początkowy, jak i końcowy rstrip('/')
znak
35

Zaznacz " Pobieranie listy wszystkich podkatalogów w bieżącym katalogu ".

Oto wersja Pythona 3:

import os

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

print(dir_list)
Geng Jiawen
źródło
2
Niezwykle sprytny. Chociaż wydajność nie ma znaczenia ( ... całkowicie ma znaczenie ), jestem ciekawy, czy to lub wyrażenie generatora oparte na globach (s.rstrip("/") for s in glob(parent_dir+"*/"))jest bardziej wydajne czasowo. Moje intuicyjne podejrzenie jest takie, że rozwiązanie stat()oparte na a powinno być znacznie szybsze niż globbing w stylu powłoki. Niestety brakuje mi chęci i rzeczywiście się tego dowiem. os.walk()timeit
Cecil Curry
3
Zwróć uwagę, że zwraca to nazwy podkatalogów bez poprzedzonej nazwy katalogu nadrzędnego.
Paul Chernoch,
19
import os, os.path

Aby uzyskać (pełną ścieżkę) bezpośrednie podkatalogi w katalogu:

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

Aby uzyskać najnowszy (najnowszy) podkatalog:

def LatestDirectory (d):
    return max(SubDirPath(d), key=os.path.getmtime)
Mediolan
źródło
Aby uzyskać listę , po prostu dodaj list( filter(...) ).
user136036
12

os.walk jest twoim przyjacielem w tej sytuacji.

Prosto z dokumentacji:

walk () generuje nazwy plików w drzewie katalogów, przechodząc po drzewie od góry do dołu lub od dołu do góry. Dla każdego katalogu w drzewie zakorzenionym na szczycie katalogu (w tym samego wierzchołka), zwraca 3-krotkę (dirpath, dirnames, filenames).

Andrew Cox
źródło
1
Po prostu pamiętaj, że jeśli chcesz tylko podkatalogów pierwszego poziomu, po pierwszym zestawie zwracanych wartości przerwij iterację os.walk.
jojo
11

Ta metoda ładnie robi wszystko za jednym razem.

from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]
SuaveSouris
źródło
7

Korzystanie z modułu FilePath 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

Ponieważ niektórzy komentatorzy pytali, jakie są zalety korzystania z bibliotek Twisted w tym celu, wyjdę nieco poza oryginalne pytanie.


W gałęzi jest ulepszona dokumentacja, która wyjaśnia zalety FilePath; możesz to przeczytać.

Dokładniej w tym przykładzie: w przeciwieństwie do standardowej wersji biblioteki, tę funkcję można zaimplementować bez importu . Funkcja „subdirs” jest całkowicie ogólna, ponieważ działa tylko na swoim argumencie. Aby kopiować i przenosić pliki przy użyciu standardowej biblioteki, musisz polegać na " open" wbudowanym " listdir", być może " isdir" lub " os.walk" lub " shutil.copy". Może też " os.path.join". Nie wspominając już o tym, że potrzebujesz ciągu znaków przekazujących argument, aby zidentyfikować rzeczywisty plik. Przyjrzyjmy się pełnej implementacji, która skopiuje „index.tpl” każdego katalogu do „index.html”:

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

Powyższa funkcja „subdirs” może działać na każdym FilePathpodobnym obiekcie. Co oznacza między innymi ZipPathprzedmioty. Niestety ZipPathjest teraz tylko do odczytu, ale można go rozszerzyć, aby obsługiwał pisanie.

Możesz także przekazać własne obiekty do celów testowych. Aby przetestować sugerowane tutaj interfejsy API wykorzystujące os.path, musisz małpować zaimportowane nazwy i niejawne zależności i generalnie wykonać czarną magię, aby testy działały. Z FilePath możesz zrobić coś takiego:

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(...))
Glif
źródło
Ponieważ mam niewielki kontakt z Twisted, zawsze z zadowoleniem przyjmuję dodatkowe informacje i przykłady; ta odpowiedź jest miła do zobaczenia. Powiedziawszy to, skoro takie podejście wydaje się wymagać znacznie więcej pracy niż użycie wbudowanych modułów Pythona i instalacji Twisted, czy są jakieś zalety korzystania z tego, które możesz dodać do odpowiedzi?
Jarret Hardie
1
Odpowiedź Glypha była prawdopodobnie zainspirowana faktem, że TwistedLore używa również plików .tpl.
Constantin
Cóż, najwyraźniej nie spodziewam się hiszpańskiej inkwizycji :-) Założyłem, że „* .tpl” jest ogólnym odniesieniem do jakiegoś abstrakcyjnego rozszerzenia oznaczającego „szablon”, a nie konkretnego szablonu Twisted (widziałem .tpl używany w wielu języki w końcu). Dobrze wiedzieć.
Jarret Hardie
W związku z tym +1 za obrócenie do możliwego kąta Twisted, chociaż nadal chciałbym zrozumieć, co obiekt Twisted'd 'FilePath' i funkcja 'walk ()' dodają do standardowego interfejsu API.
Jarret Hardie
Osobiście uważam, że „FilePath.walk () daje obiekty ścieżek” o wiele łatwiejsze do zapamiętania niż „os.walk daje 3-krotki dir, dirs, files”. Ale są też inne korzyści. FilePath dopuszcza polimorfizm, co oznacza, że ​​możesz przechodzić przez rzeczy inne niż systemy plików. Na przykład, możesz przekazać twisted.python.zippath.ZipArchive do mojej funkcji „subdirs” i pobrać generator ścieżek ZipPath zamiast FilePaths; Twoja logika się nie zmienia, ale Twoja aplikacja w magiczny sposób obsługuje pliki zip. Jeśli chcesz to przetestować, wystarczy dostarczyć obiekt, nie musisz pisać prawdziwych plików.
Glyph
4

Właśnie napisałem kod do przenoszenia maszyn wirtualnych vmware i ostatecznie użyłem os.pathi shutilwykonałem kopiowanie plików między podkatalogami.

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

Nie jest zbyt elegancka, ale działa.

Blaszany Człowiek
źródło
1

Oto jeden sposób:

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
źródło
-1: nie zadziała, ponieważ shutil.copy skopiuje do bieżącego katalogu, więc skończysz nadpisując „index.html” w bieżącym katalogu raz dla każdego „index.tpl”, który znajdziesz w drzewie podkatalogów.
nosklo
1

Muszę wspomnieć o bibliotece path.py , z której bardzo często korzystam.

Pobieranie bezpośrednich podkatalogów staje się tak proste:

my_dir.dirs()

Pełny przykład roboczy to:

from path import Path

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

subdirs = my_directory.dirs()

NB: my_directory nadal można manipulować jako ciąg znaków, ponieważ Path jest podklasą łańcucha, ale zapewnia kilka użytecznych metod manipulowania ścieżkami

olinox14
źródło
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

Następującą funkcję można wywołać jako:

get_folders_in_directories_recursively (directory, index = 1) -> podaje listę folderów na pierwszym poziomie

get_folders_in_directories_recursively (katalog) -> podaje wszystkie podfoldery

Kanish Mathew
źródło
robię fajnie, wersja pythona 3.6, ale musiałem skasować "self", od wewnątrz zmienne funkcyjne
locometro
1
używał wewnątrz klasy, zaktualizował
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

child_dirsFunkcja przyjmuje ścieżkę katalogu i zwraca listę bezpośrednich podkatalogów w nim.

dir
 |
  -- dir_1
  -- dir_2

child_dirs('dir') -> ['dir_1', 'dir_2']
Amjad
źródło
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
źródło
0

Jedna linijka używająca pathlib:

list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]
bsimpson53
źródło