Як отримати всі безпосередні підкаталоги в Python


150

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

Я забуваю, намагаючись отримати список підкаталогів.


11
Ви можете виявити, що прийнята відповідь на це раніше питання ТА вирішує проблему: stackoverflow.com/questions/120656/directory-listing-in-python
Jarret Hardie,

Відповіді:


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




Результати : 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}")

1
Просто хочу подякувати, справді шукав цього. Чудовий аналіз.
Цинг

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, таких як я: 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(...) ).
користувач136036

12

os.walk ваш друг у цій ситуації.

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

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


1
Просто пам’ятайте, що якщо ви хочете лише підкаталоги першого рівня, то вирветесь з ітерації os.walk після першого набору повернутих значень.
yoyo

11

Цей метод чудово робить це за один рух.

from glob import glob
subd = [s.rstrip("/") for s in glob(parent_dir+"*/")]

7

Використання модуля 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(...))

Оскільки у мене мало схильності до Twisted, я завжди вітаю додаткову інформацію та приклади; цю відповідь приємно бачити на це. Сказавши це, оскільки, схоже, цей підхід вимагає значно більше роботи, ніж використання вбудованих модулів python та Twisted install, чи є якісь переваги у використанні цього, що ви могли б додати до відповіді?
Джаррет Харді

1
Відповідь Гліфа, ймовірно, надихнула на те, що TwistedLore також використовує файли .tpl.
Константин

Ну, явно не чекаю, що іспанська інквізиція :-) Я припустила, що "* .tpl" є загальною посиланням на якесь абстрактне розширення, що означає "шаблон", а не конкретним крученим шаблоном (я бачив .tpl, який використовується у багатьох мови зрештою). Добре знати.
Джаррет Харді

+1, тому для підключення до можливого скрученого кута, хоча я все ж хотів би зрозуміти, що об'єкт "FilePath" Twisted та функція "walk ()" додають до стандартного API.
Джаррет Харді

Особисто мені здається, що "FilePath.walk () дає об'єкти шляху" набагато простіше запам’ятати, ніж "os.walk дає 3-кратні dir, dirs, файли". Але є й інші переваги. FilePath дозволяє здійснити поліморфізм, а це означає, що ви можете переміщати інші речі, крім файлових систем. Наприклад, ви можете передати twisted.python.zippath.ZipArchive до моєї функції "subdirs" і отримати генератор ZipPaths замість FilePaths; Ваша логіка не змінюється, але ваша програма тепер магічно обробляє поштові файли. Якщо ви хочете перевірити його, вам просто потрібно поставити об'єкт, вам не потрібно писати реальні файли.
Гліф

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 скопіює у поточний dir, тож ви в кінцевому підсумку перезапишете 'index.html' у поточний dir для кожного 'index.tpl', який ви знайдете у дереві підкаталогу.
nosklo

1

Мушу згадати бібліотеку path.py , якою я користуюся дуже часто.

Вибір безпосередніх підкаталогів стає таким же простим:

my_dir.dirs()

Повний робочий приклад:

from path import Path

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

subdirs = my_directory.dirs()

Примітка: 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 (каталог, індекс = 1) -> дає список папок першого рівня

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


добре, версія python 3.6, але мені потрібно було стерти "я" із змінних функцій всередині
locometro

1
використовував всередині класу, оновили
Kanish Mathew

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.