Я намагаюся написати простий скрипт Python, який буде копіювати index.tpl до index.html у всіх підкаталогах (за кількома винятками).
Я забуваю, намагаючись отримати список підкаталогів.
Я намагаюся написати простий скрипт Python, який буде копіювати index.tpl до index.html у всіх підкаталогах (за кількома винятками).
Я забуваю, намагаючись отримати список підкаталогів.
Відповіді:
Я зробив кілька тестувань швидкості на різних функціях, щоб повернути повний шлях до всіх поточних підкаталогів.
tl; dr:
Завжди використовуйте scandir
:
list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]
Бонус: За scandir
допомогою f.name
цього пункту ви також можете просто отримати лише імена папок, використовуючи замість них f.path
.
Це (як і всі інші функції нижче) не використовуватиме природне сортування . Це означає, що результати будуть сортовані так: 1, 10, 2. Щоб отримати природне сортування (1, 2, 10), перегляньте https://stackoverflow.com/a/48030307/2441026
Результати :
scandir
є: в 3 рази швидше walk
, на 32 рази швидше , ніж listdir
(з фільтром), на 35 Pathlib
разів швидше listdir
і на 36 рази швидше, а на 37 разів (!) Швидше, ніж на glob
.
Scandir: 0.977
Walk: 3.011
Listdir (filter): 31.288
Pathlib: 34.075
Listdir: 35.501
Glob: 36.277
Тестовано на W7x64, Python 3.8.1. Папка з 440 папками.
Якщо ви замислюєтесь, чи listdir
можна було б прискорити, не зробивши os.path.join () двічі, так, але різниці в основному немає.
Код:
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))]
Чому ніхто не згадав glob
? glob
дозволяє використовувати розширення імені шляху у стилі Unix, і це мій перехід до функції майже всього, що потрібно, щоб знайти більше одного імені шляху. Це дуже легко:
from glob import glob
paths = glob('*/')
Зверніть увагу, що glob
поверне каталог з кінцевою косою рисою (як це було б в Unix), тоді як більшість path
заснованих рішень опустить остаточну косу рису.
paths = [ p.replace('/', '') for p in glob('*/') ]
.
[p[:-1] for p in paths]
, оскільки цей метод заміни також замінить будь-які прорізані косої риски в імені файлу (не те, що вони є загальними).
rstrip
замість цього strip
, оскільки останні перетворять будь-які повністю кваліфіковані шляхи у відносні шляхи.
strip('/')
видалить і стартовий, і трейлінг '/', rstrip('/')
видалить лише трейлінг
Поставте прапорець " Отримання списку всіх підкаталогів у поточному каталозі ".
Ось версія Python 3:
import os
dir_list = next(os.walk('.'))[1]
print(dir_list)
(s.rstrip("/") for s in glob(parent_dir+"*/"))
більш ефективні у часі. Моє інтуїтивне підозру полягає в тому, що рішення, stat()
засноване на роботі, повинно бути набагато швидшим, ніж глобальний стиль. На жаль, мені не вистачає волі і насправді це з’ясовують. os.walk()
timeit
import os, os.path
Щоб отримати (повний шлях) безпосередні підкаталоги в каталозі:
def SubDirPath (d):
return filter(os.path.isdir, [os.path.join(d,f) for f in os.listdir(d)])
Щоб отримати найновіший (найновіший) підкаталог:
def LatestDirectory (d):
return max(SubDirPath(d), key=os.path.getmtime)
list( filter(...) )
.
os.walk
ваш друг у цій ситуації.
Прямо з документації:
walk () генерує імена файлів у дереві каталогів, пересуваючи дерево або зверху вниз, або знизу вгору. Для кожного каталогу в дереві, вкоріненому у верхній частині каталогу (включаючи сам верх), він отримує 3-макет (dirpath, dirnames, filename).
Цей метод чудово робить це за один рух.
from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]
Використання модуля 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
Оскільки деякі коментатори запитали, у чому переваги використання бібліотек Twisted, я перейду трохи далі від початкового питання.
Існує деяка вдосконалена документація у галузі, яка пояснює переваги FilePath; ви можете прочитати це.
Більш конкретно в цьому прикладі: на відміну від стандартної бібліотечної версії, ця функція може бути реалізована без імпорту . Функція "підкаталів" є абсолютно загальною, оскільки вона працює лише на аргументі. Для того щоб скопіювати та перемістити файли за допомогою стандартної бібліотеки, вам потрібно залежати від " open
" вбудованої, " listdir
", можливо, " isdir
" або " os.walk
" або " shutil.copy
". Можливо " os.path.join
" теж. Не кажучи вже про те, що вам потрібен рядок, який передав аргумент, щоб ідентифікувати фактичний файл. Давайте розглянемо повну реалізацію, яка буде копіювати "index.tpl" кожного каталогу в "index.html":
def copyTemplates(topdir):
for subdir in subdirs(topdir):
tpl = subdir.child("index.tpl")
if tpl.exists():
tpl.copyTo(subdir.child("index.html"))
Функція "підкаталів" вище може працювати на будь-якому FilePath
об'єкті. Що означає, серед іншого, ZipPath
предмети. На жаль, ZipPath
зараз це лише для читання, але це може бути розширено для підтримки написання.
Ви також можете передавати власні об’єкти для тестування. Щоб перевірити запропоновані тут API-програми OS.path, вам доведеться мавповувати імпортованими іменами та неявними залежностями і, як правило, виконувати чорну магію, щоб змусити свої тести працювати. З FilePath ви робите щось подібне:
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(...))
Я просто написав якийсь код, щоб перемістити віртуальну машину vmware навколо, і в кінцевому підсумку використовував os.path
та shutil
виконував копіювання файлів між підкаталогами.
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))
Це не страшно елегантно, але це працює.
Ось один із способів:
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')
Мушу згадати бібліотеку path.py , якою я користуюся дуже часто.
Вибір безпосередніх підкаталогів стає таким же простим:
my_dir.dirs()
Повний робочий приклад:
from path import Path
my_directory = Path("path/to/my/directory")
subdirs = my_directory.dirs()
Примітка: my_directory все ще може маніпулювати як рядок, оскільки Path є підкласом рядка, але надає купу корисних методів для маніпулювання шляхами
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
Наступну функцію можна назвати як:
get_folders_in_directories_recursively (каталог, індекс = 1) -> дає список папок першого рівня
get_folders_in_directories_recursively (каталог) -> дає всі підпапки
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
Функція приймає шлях директорії і повертає список найближчих підкаталогів в ньому.
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')
Один лайнер, що використовує pathlib:
list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]