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.
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.
Odpowiedzi:
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 scandir
możesz także po prostu uzyskać nazwy folderów tylko za pomocą f.name
zamiast 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 :
scandir
to: 3x szybciej niż walk
, 32x szybciej niż listdir
(z filtrem), 35x szybciej niż Pathlib
i 36x szybciej niż listdir
i 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 listdir
moż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}")
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))]
Dlaczego nikt o tym nie wspomniał glob
? glob
pozwala 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 glob
zwróci katalog z końcowym ukośnikiem (tak jak zrobiłby to Unix), podczas gdy większość path
rozwiązań bazujących pomija końcowy ukośnik.
paths = [ p.replace('/', '') for p in glob('*/') ]
.
[p[:-1] for p in paths]
, ponieważ ta metoda replace zastąpi również wszelkie ukośniki w nazwie pliku (nie są one powszechne).
rstrip
zamiast strip
, ponieważ ta ostatnia zamieni wszystkie w pełni kwalifikowane ścieżki w ścieżki względne.
strip('/')
usunie zarówno początkowy, jak i końcowy rstrip('/')
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)
(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
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)
list( filter(...) )
.
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).
Ta metoda ładnie robi wszystko za jednym razem.
from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]
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 FilePath
podobnym obiekcie. Co oznacza między innymi ZipPath
przedmioty. Niestety ZipPath
jest 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(...))
Właśnie napisałem kod do przenoszenia maszyn wirtualnych vmware i ostatecznie użyłem os.path
i shutil
wykonał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.
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')
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
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
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_dirs
Funkcja 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']
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')
Jedna linijka używająca pathlib:
list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]