Как получить все непосредственные подкаталоги в 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}")

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

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)

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+"*/")]

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 является подклассом строки, но предоставляет кучу полезных методов для манипулирования путями


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']

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

0

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

list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.