Я пытаюсь написать простой скрипт на 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
is: в 3 раза быстрее walk
, в 32 раза быстрее listdir
(с фильтром), в 35 раз быстрее Pathlib
и в 36 раз быстрее listdir
и в 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, filenames).
Этот метод прекрасно делает все это за один раз.
from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]
Использование Twisted модуля FilePath:
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; Вы можете прочитать это.
Более конкретно в этом примере: в отличие от стандартной версии библиотеки, эта функция может быть реализована без импорта . Функция "subdirs" является полностью родовой в том смысле, что она работает только с аргументом. Для того чтобы копировать и перемещать файлы с использованием стандартной библиотеки, вам необходимо зависеть от 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"))
Вышеуказанная функция «subdirs» может работать с любым 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()
NB: 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 (directory, index = 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()]