Как получить все непосредственные подкаталоги в Python

150

Я пытаюсь написать простой скрипт на Python, который будет копировать index.tpl в index.html во всех подкаталогах (за некоторыми исключениями).

Я застрял, пытаясь получить список подкаталогов.

Стивен Нобл
источник
11
Вы можете обнаружить, что принятый ответ на этот более ранний вопрос SO решает проблему: stackoverflow.com/questions/120656/directory-listing-in-python
Джаррет Харди

Ответы:

31

Я провел некоторое тестирование скорости для различных функций, чтобы вернуть полный путь ко всем текущим подкаталогам.

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




Результаты : scandiris: в 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}")
user136036
источник
1
Просто хочу поблагодарить вас, действительно искал это. Отличный анализ.
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
источник
76

Почему никто не упомянул glob? globпозволяет вам использовать расширение пути в стиле Unix, и это моя функция для работы почти со всем, что нужно для поиска более одного имени пути. Это делает это очень просто:

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

Обратите внимание, что globвернет каталог с окончательной косой чертой (как и в случае с Unix), в то время как большинство pathоснованных решений пропустит последнюю косую черту.

ари
источник
3
Хорошее решение, простое и работает. Для тех, кто не хочет этот последний слэш, он может использовать это paths = [ p.replace('/', '') for p in glob('*/') ].
Эван Ху
5
Возможно, было бы безопаснее просто вырезать последний символ [p[:-1] for p in paths], так как этот метод замены также заменит любые экранированные косые черты в имени файла (не то, что они являются общими).
ари
3
Еще безопаснее использовать полоску ('/'), чтобы удалить косые черты. Этот способ гарантирует, что вы не будете вырезать символы, которые не являются косыми чертами
Eliezer Miron
8
По построению вы гарантированно получите косую черту (так что это не безопаснее), но я думаю, что она более читабельна. Вы определенно хотите использовать rstripвместо этого strip, так как последний превратит любые полностью определенные пути в относительные пути.
ари
7
дополнение к комментарию @ari для новичков в Python, таких как I: strip('/')удалит как начальный, так и конечный '/', rstrip('/')удалит только последний
Titou
35

Проверьте « Получение списка всех подкаталогов в текущем каталоге ».

Вот версия Python 3:

import os

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

print(dir_list)
Geng Jiawen
источник
2
Очень умный Хотя эффективность не имеет значения ( ... она полностью имеет значение ), мне любопытно, является ли это или глобальное выражение генератора (s.rstrip("/") for s in glob(parent_dir+"*/"))более эффективным по времени. Мое интуитивное подозрение заключается в том, что решение на stat()основе должно быть намного быстрее, чем глобализация в стиле оболочки. К сожалению, мне не хватает воли и на самом деле узнать. os.walk()timeit
Сесил Карри
3
Обратите внимание, что при этом возвращаются имена подкаталогов без префикса родительского каталога.
Павел Чернох
19
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(...) ).
user136036
12

os.walk твой друг в этой ситуации.

Прямо из документации:

walk () генерирует имена файлов в дереве каталогов, обходя дерево сверху вниз или снизу вверх. Для каждого каталога в дереве с корнем в вершине каталога (включая саму вершину) он выдает 3-кортеж (dirpath, dirnames, filenames).

Эндрю Кокс
источник
1
Просто имейте в виду, что если вам нужны только подкаталоги первого уровня, то выходите из итерации os.walk после первого набора возвращаемых значений.
йойо
11

Этот метод прекрасно делает все это за один раз.

from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]
SuaveSouris
источник
7

Использование 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(...))
глиф
источник
Поскольку я мало знаком с Twisted, я всегда рад дополнительной информации и примерам; этот ответ приятно видеть за это. Сказав это, поскольку этот подход требует значительно больше работы, чем использование встроенных модулей Python и установка Twisted, есть ли какие-либо преимущества в использовании этого, которые вы могли бы добавить к ответу?
Джаррет Харди
1
Ответ Глифа, вероятно, был вдохновлен тем фактом, что TwistedLore также использует файлы .tpl.
Константин
Ну, ясно, я не ожидаю, что испанская инквизиция :-) Я предположил, что "* .tpl" - это общая ссылка на некое абстрактное расширение, означающее "шаблон", а не конкретный шаблон Twisted (я видел .tpl, используемый во многих языки ведь). Хорошо знать.
Джаррет Харди
+1, следовательно, для перехода к возможному углу скручивания, хотя я все же хотел бы понять, что Twisted'd объект FilePath и функция walk () добавляют к стандартному API.
Джаррет Харди
Лично я нахожу, что «FilePath.walk () возвращает объекты пути» гораздо легче запомнить, чем «os.walk выдает 3 кортежа dir, dirs, files». Но есть и другие преимущества. FilePath допускает полиморфизм, что означает, что вы можете обходить вещи, отличные от файловых систем. Например, вы можете передать twisted.python.zippath.ZipArchive в мою функцию 'subdirs' и получить генератор ZipPath вместо FilePaths; ваша логика не меняется, но ваше приложение теперь волшебным образом обрабатывает zip-файлы. Если вы хотите проверить это, вам просто нужно предоставить объект, вам не нужно писать реальные файлы.
Символ
4

Я просто написал некоторый код для перемещения виртуальных машин 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))

Это не очень элегантно, но работает.

жестяной человек
источник
1

Вот один из способов:

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')
Скотт Кирквуд
источник
-1: не сработает, так как shutil.copy будет копировать в текущий каталог, так что вам придется перезаписывать «index.html» в текущем каталоге один раз для каждого «index.tpl», найденного в дереве подкаталогов.
nosklo
1

Я должен упомянуть библиотеку path.py , которую я использую очень часто.

Извлечение непосредственных подкаталогов становится таким простым:

my_dir.dirs()

Полный рабочий пример:

from path import Path

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

subdirs = my_directory.dirs()

NB: my_directory все еще можно манипулировать как строку, так как Path является подклассом строки, но предоставляет кучу полезных методов для манипулирования путями

olinox14
источник
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

Следующая функция может быть вызвана как:

get_folders_in_directories_recursively (directory, index = 1) -> выдает список папок первого уровня

get_folders_in_directories_recursively (каталог) -> дает все подпапки

Каниш Мэтью
источник
делаю хорошо, версия python 3.6, но мне нужно было стереть «self» из внутренних переменных функции
locometro
1
использовал внутри класса, обновили
Каниш Мэтью
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_dirsФункция принимает путь директории и возвращает список ближайших подкаталогов в нем.

dir
 |
  -- dir_1
  -- dir_2

child_dirs('dir') -> ['dir_1', 'dir_2']
Amjad
источник
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
источник
0

Один лайнер, использующий pathlib:

list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]
bsimpson53
источник