Cara mendapatkan semua subdirektori langsung dengan Python

150

Saya mencoba menulis skrip Python sederhana yang akan menyalin index.tpl ke index.html di semua subdirektori (dengan beberapa pengecualian).

Saya terjebak dengan mencoba mendapatkan daftar subdirektori.

Steven Noble
sumber
11
Anda mungkin menemukan bahwa jawaban yang diterima pada pertanyaan SO sebelumnya menyelesaikan masalah: stackoverflow.com/questions/120656/directory-listing-in-python
Jarret Hardie

Jawaban:

31

Saya melakukan beberapa pengujian kecepatan pada berbagai fungsi untuk mengembalikan path lengkap ke semua subdirektori saat ini.

tl; dr: Selalu gunakan scandir:

list_subfolders_with_paths = [f.path for f in os.scandir(path) if f.is_dir()]

Bonus: Dengan scandirAnda juga bisa hanya mendapatkan nama folder dengan menggunakan f.namebukan f.path.

Ini (serta semua fungsi lainnya di bawah) tidak akan menggunakan penyortiran alami . Ini berarti hasil akan diurutkan seperti ini: 1, 10, 2. Untuk mendapatkan penyortiran alami (1, 2, 10), silakan lihat di https://stackoverflow.com/a/48030307/2441026




Hasil : scandiradalah: 3x lebih cepat dari walk, 32x lebih cepat dari listdir(dengan filter), 35x lebih cepat dari Pathlibdan 36x lebih cepat dari listdirdan 37x (!) Lebih cepat dari glob.

Scandir:           0.977
Walk:              3.011
Listdir (filter): 31.288
Pathlib:          34.075
Listdir:          35.501
Glob:             36.277

Diuji dengan W7x64, Python 3.8.1. Folder dengan 440 subfolder.
Jika Anda bertanya-tanya apakah listdirbisa dipercepat dengan tidak melakukan os.path.join () dua kali, ya, tetapi perbedaannya pada dasarnya tidak ada.

Kode:

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}")
pengguna136036
sumber
1
Hanya ingin mengucapkan terima kasih, sungguh mencari ini. Analisis hebat.
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
sumber
76

Kenapa tidak ada yang menyebutkan glob? globmemungkinkan Anda menggunakan perluasan pathname gaya Unix, dan merupakan fungsi saya untuk hampir semua yang perlu menemukan lebih dari satu nama jalur. Itu membuatnya sangat mudah:

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

Catatan yang globakan mengembalikan direktori dengan slash akhir (seperti yang akan unix) sementara sebagian besar pathsolusi berbasis akan menghilangkan slash akhir.

ari
sumber
3
Solusi bagus, sederhana dan berhasil. Bagi mereka yang tidak menginginkan tebasan terakhir, dia bisa menggunakan ini paths = [ p.replace('/', '') for p in glob('*/') ].
Evan Hu
5
Mungkin lebih aman untuk hanya memotong karakter terakhir [p[:-1] for p in paths], karena metode ganti juga akan menggantikan setiap garis miring maju yang melarikan diri dalam nama file (bukan yang umum).
ari
3
Bahkan lebih aman, gunakan strip ('/') untuk menghapus garis miring. Dengan cara ini menjamin bahwa Anda tidak memotong karakter apa pun yang bukan garis miring
Eliezer Miron
8
Dengan konstruksi Anda dijamin memiliki garis miring (sehingga tidak lebih aman), tapi saya pikir itu lebih mudah dibaca. Anda pasti ingin menggunakan rstripbukan strip, karena yang terakhir akan mengubah jalur yang sepenuhnya memenuhi syarat menjadi jalur relatif.
ari
7
melengkapi komentar @ari untuk pemula python seperti I: strip('/')akan menghapus starting dan trailing '/', rstrip('/')hanya akan menghapus yang trailing
Titou
35

Centang " Mendapatkan daftar semua subdirektori di direktori saat ini ".

Ini versi Python 3:

import os

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

print(dir_list)
Geng Jiawen
sumber
2
Sangat pintar. Sementara efisiensi tidak masalah ( ... benar-benar penting ), saya ingin tahu apakah ini atau ekspresi generator berbasis glob (s.rstrip("/") for s in glob(parent_dir+"*/"))lebih efisien waktu. Kecurigaan intuisi saya adalah bahwa solusi stat()berbasiskan seharusnya jauh lebih cepat daripada globbing gaya shell. Sayangnya, saya tidak memiliki kemauan untuk dan benar-benar mencari tahu. os.walk()timeit
Cecil Curry
3
Perhatikan bahwa ini mengembalikan nama subdirektori tanpa diawali nama direktori induk.
Paul Chernoch
19
import os, os.path

Untuk mendapatkan sub-direktori langsung (jalur penuh) langsung di direktori:

def SubDirPath (d):
    return filter(os.path.isdir, [os.path.join(d,f) for f in os.listdir(d)])

Untuk mendapatkan sub-direktori terbaru (terbaru):

def LatestDirectory (d):
    return max(SubDirPath(d), key=os.path.getmtime)
Milan
sumber
Untuk mendapatkan daftar , cukup tambahkan list( filter(...) ).
user136036
12

os.walk adalah teman Anda dalam situasi ini.

Langsung dari dokumentasi:

walk () menghasilkan nama file di pohon direktori, dengan berjalan pohon itu dari atas ke bawah atau dari bawah ke atas. Untuk setiap direktori di pohon yang di-root di direktori teratas (termasuk top itu sendiri), ia menghasilkan 3-tuple (dirpath, dirnames, nama file).

Andrew Cox
sumber
1
Perlu diketahui bahwa jika Anda hanya ingin subdirektori tingkat pertama, maka keluar dari iterasi os.walk setelah set pertama nilai pengembalian.
yoyo
11

Metode ini dengan baik melakukan semuanya sekaligus.

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

Menggunakan modul 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

Karena beberapa komentator telah bertanya apa keuntungan menggunakan perpustakaan Twisted untuk ini, saya akan sedikit melampaui pertanyaan aslinya di sini.


Ada beberapa dokumentasi yang ditingkatkan di cabang yang menjelaskan kelebihan FilePath; Anda mungkin ingin membacanya.

Lebih khusus dalam contoh ini: tidak seperti versi perpustakaan standar, fungsi ini dapat diimplementasikan tanpa impor . Fungsi "subdirs" sepenuhnya generik, dalam arti ia beroperasi hanya dengan argumennya. Untuk menyalin dan memindahkan file menggunakan pustaka standar, Anda harus bergantung pada " open" builtin, " listdir", mungkin " isdir" atau " os.walk" atau " shutil.copy". Mungkin " os.path.join" juga. Belum lagi fakta bahwa Anda perlu sebuah string melewati argumen untuk mengidentifikasi file yang sebenarnya. Mari kita lihat implementasi penuh yang akan menyalin "index.tpl" setiap direktori ke "index.html":

def copyTemplates(topdir):
    for subdir in subdirs(topdir):
        tpl = subdir.child("index.tpl")
        if tpl.exists():
            tpl.copyTo(subdir.child("index.html"))

Fungsi "subdirs" di atas dapat bekerja pada FilePathobjek seperti apa pun . Yang artinya, antara lain, ZipPathbenda. Sayangnya ZipPathhanya baca-sekarang, tetapi dapat diperluas untuk mendukung penulisan.

Anda juga dapat melewatkan objek Anda sendiri untuk tujuan pengujian. Untuk menguji API yang menggunakan os.path yang disarankan di sini, Anda harus menggunakan nama impor dan dependensi tersirat dan umumnya melakukan sihir hitam agar tes Anda berfungsi. Dengan FilePath, Anda melakukan sesuatu seperti ini:

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(...))
Mesin terbang
sumber
Karena saya memiliki sedikit paparan terhadap Twisted, saya selalu menyambut info dan contoh tambahan; jawaban ini bagus untuk dilihat. Karena itu, karena pendekatan ini tampaknya membutuhkan kerja lebih banyak daripada menggunakan modul python bawaan, dan instalasi Twisted, apakah ada keuntungan menggunakan ini yang dapat Anda tambahkan ke jawabannya?
Jarret Hardie
1
Jawaban Glyph mungkin terinspirasi oleh fakta bahwa TwistedLore juga menggunakan file .tpl.
Constantin
Yah, jelas saya tidak berharap inkuisisi Spanyol :-) Saya berasumsi "* .tpl" adalah referensi umum untuk beberapa ekstensi abstrak yang berarti "template", dan bukan template Twisted spesifik (saya telah melihat .tpl digunakan di banyak setelah semua bahasa). Senang mendengarnya.
Jarret Hardie
Karenanya, +1 untuk ranting ke sudut Twisted yang mungkin, meskipun saya masih ingin memahami apa yang ditambahkan objek 'FilePath' dan fungsi 'walk ()' ke API standar.
Jarret Hardie
Secara pribadi saya menemukan "FilePath.walk () menghasilkan objek path" jauh lebih mudah diingat daripada "os.walk menghasilkan 3-tupel dir, dirs, file". Namun ada manfaat lain. FilePath memungkinkan polimorfisme, yang berarti Anda dapat melintasi hal-hal selain sistem file. Sebagai contoh, Anda bisa memberikan twisted.python.zippath.ZipArchive ke fungsi 'subdirs' saya dan mendapatkan generator ZipPaths alih-alih FilePaths; logika Anda tidak berubah, tetapi aplikasi Anda sekarang secara ajaib menangani file zip. Jika Anda ingin mengujinya, Anda hanya perlu menyediakan objek, Anda tidak perlu menulis file nyata.
Glyph
4

Saya baru saja menulis beberapa kode untuk memindahkan mesin virtual vmware, dan akhirnya menggunakan os.pathdan shutilmenyelesaikan penyalinan file antara sub-direktori.

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

Itu tidak terlalu elegan, tetapi itu berhasil.

Manusia Timah
sumber
1

Ini salah satu caranya:

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')
Scott Kirkwood
sumber
-1: tidak akan berfungsi, karena shutil.copy akan menyalin ke direktori saat ini, jadi Anda akan berakhir menimpa 'index.html' di direktori saat ini untuk setiap 'index.tpl' yang Anda temukan di pohon subdirektori.
nosklo
1

Saya harus menyebutkan perpustakaan path.py , yang sering saya gunakan.

Mengambil subdirektori langsung menjadi sesederhana itu:

my_dir.dirs()

Contoh kerja penuh adalah:

from path import Path

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

subdirs = my_directory.dirs()

NB: my_directory masih dapat dimanipulasi sebagai string, karena Path adalah subkelas string, tetapi menyediakan banyak metode yang berguna untuk memanipulasi jalur

olinox14
sumber
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

Fungsi berikut dapat disebut sebagai:

get_folders_in_directories_recursively (direktori, indeks = 1) -> memberikan daftar folder di tingkat pertama

get_folders_in_directories_recursively (direktori) -> memberikan semua sub folder

Kanish Mathew
sumber
baik, versi python 3.6, tapi saya harus menghapus "diri", dari variabel fungsi di dalam
locometro
1
sedang menggunakan di dalam kelas, telah diperbarui
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

Itu child_dirs fungsi mengambil jalur direktori dan mengembalikan daftar subdirektori langsung di dalamnya.

dir
 |
  -- dir_1
  -- dir_2

child_dirs('dir') -> ['dir_1', 'dir_2']
Amjad
sumber
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
sumber
0

Satu liner menggunakan pathlib:

list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]
bsimpson53
sumber