Python'daki tüm alt dizinleri nasıl edinirim?


Yanıtlar:


32

Geçerli tüm alt dizinlere tam yolu döndürmek için çeşitli işlevler üzerinde bazı hız testleri yaptım .

tl; dr: Her zaman kullan scandir:

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

Bonus: Bunun yerine scandirsadece klasör adlarını kullanarak da alabilirsiniz .f.namef.path

Bu (aşağıdaki diğer tüm işlevlerin yanı sıra) doğal sıralama kullanmaz . Bu, sonuçların şu şekilde sıralanacağı anlamına gelir: 1, 10, 2. Doğal sıralama (1, 2, 10) elde etmek için lütfen https://stackoverflow.com/a/48030307/2441026 adresine bakın.




Sonuçlar : scandiris: 3x daha hızlı walk, 32x daha hızlı listdir(filtre ile), 35x daha hızlı Pathlibve 36x daha hızlı listdirve 37x (!) Daha hızlı 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 ile test edilmiştir. 440 alt klasör içeren klasör. Os.path.join () öğesini iki kez yapmadan hızlanıp hızlanamayacağınızı
merak ediyorsanız listdir, evet, ancak fark temelde mevcut değildir.

Kod:

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
Sadece teşekkür etmek istiyorum, gerçekten bunu arıyordu. Harika analiz.
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

Kimse neden bahsetmedi glob? globUnix tarzı yol adı genişletmesini kullanmanızı sağlar ve birden fazla yol adı bulması gereken hemen hemen her şey için işlevime gider. Bunu çok kolaylaştırır:

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

globÇoğu pathtabanlı çözüm son eğik çizgiyi atlarken dizinin son eğik çizgiyle (unix gibi) döneceğini unutmayın .


3
İyi bir çözüm, basit ve çalışıyor. Bu son eğik çizgiyi istemeyenler için bunu kullanabilir paths = [ p.replace('/', '') for p in glob('*/') ].
Evan Hu

5
Son karakteri basitçe kesmek daha güvenli olabilir [p[:-1] for p in paths], çünkü replace yöntemi dosya adındaki kaçan ileri eğik çizgilerin de yerini alacaktır (bunlar yaygın değildir).
ari

3
Daha güvenli bile, arka eğik çizgileri çıkarmak için şerit ('/') kullanın. Bu şekilde, eğik çizgi olmayan karakterleri kesmemenizi garanti eder
Eliezer Miron

8
Inşaat ile bir eğik çizgi garanti (böylece daha güvenli değil), ancak daha okunabilir olduğunu düşünüyorum. Bununla birlikte, kesinlikle rstripyerine kullanmak istersiniz strip, çünkü ikincisi tam nitelikli yolları göreli yollara dönüştürecektir.
ari

7
I gibi python yeni başlayanlar için @ari yorumuna tamamlayıcı: strip('/')hem başlangıç ​​hem de sondaki '/' karakterini rstrip('/')kaldıracak, sadece sondaki olanı kaldıracak
Titou

35

" Geçerli dizindeki tüm alt dizinlerin listesini alma " " seçeneğini işaretleyin.

İşte bir Python 3 sürümü:

import os

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

print(dir_list)

2
Son derece akıllı. Verimlilik önemli olmasa da ( ... tamamen önemli ), bunun veya glob tabanlı jeneratör ifadesinin (s.rstrip("/") for s in glob(parent_dir+"*/"))daha fazla zaman verimli olup olmadığını merak ediyorum . Benim sezgisel şüphe, a- stat()tabanlı bir os.walk()çözümün kabuk tarzı globbingten çok daha hızlı olması gerektiğidir . Ne yazık ki, irade eksikliği timeitve aslında öğrenmek.
Cecil Curry

3
Bunun, üst dizin adı önek olmadan alt dizin adlarını döndürdüğünü unutmayın.
Paul Chernoch

19
import os, os.path

Bir dizindeki (tam yol) hemen alt dizinleri almak için:

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

En son (en yeni) alt dizini almak için:

def LatestDirectory (d):
    return max(SubDirPath(d), key=os.path.getmtime)

Bir liste almak için şunu eklemeniz yeterlidir list( filter(...) ).
user136036

12

os.walk bu durumda arkadaşın.

Doğrudan dokümantasyondan:

walk (), ağacı aşağıdan yukarıya veya aşağıya doğru yürüterek bir dizin ağacında dosya adları oluşturur. Ağaçtaki üst dizin (üstte dahil) kökündeki her dizin için 3-demet (dirpath, dirnames, dosya adları) verir.


1
Yalnızca birinci düzey alt dizinleri istiyorsanız, ilk dönüş değerleri kümesinden sonra os.walk yinelemesinden çıkın.
yoyo

11

Bu yöntem hepsini tek seferde güzel yapar.

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

7

Twisted'in FilePath modülünü kullanarak:

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

Bazı yorumcular bunun için Twisted kütüphanelerini kullanmanın avantajlarının ne olduğunu sorduğundan, buradaki orijinal sorunun biraz ötesine geçeceğim.


Orada bazı gelişmiş dokümantasyon FilePath avantajlarını açıklayan bir dalda; bunu okumak isteyebilirsiniz.

Daha spesifik olarak bu örnekte: standart kütüphane versiyonundan farklı olarak, bu fonksiyon içe aktarma olmadan uygulanabilir . "Altdiziler" işlevi tamamen geneldir, çünkü argümanından başka bir şey üzerinde çalışmaz. Standart kitaplığı kullanarak dosyaları kopyalamak ve taşımak için " open" yerleşik listdir",", belki " isdir" veya " os.walk" veya " shutil.copy" öğelerine bağlı olmanız gerekir . Belki de " os.path.join". Bir dizeye ihtiyacınız olduğu gerçeğinden bahsetmemek gerekirse, gerçek dosyayı tanımlamak için bir argüman geçti. Her dizinin "index.tpl" dosyasını "index.html" dosyasına kopyalayacak tam uygulamaya bakalım:

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

Yukarıdaki "alt dizinler" işlevi herhangi bir benzer FilePathnesne üzerinde çalışabilir . Bu, diğer şeylerin yanı sıra ZipPathnesneler anlamına gelir . Ne yazık ki ZipPathşu anda salt okunur, ancak yazmayı desteklemek için genişletilebilir.

Test amacıyla kendi nesnelerinizi de geçirebilirsiniz. Burada önerilen os.path kullanan API'leri test etmek için, içe aktarılan adlar ve örtülü bağımlılıklar ile maymunluk yapmalısınız ve testlerinizin çalışması için genellikle kara büyü yapmalısınız. FilePath ile böyle bir şey yaparsınız:

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'a çok az maruz kaldığım için her zaman ek bilgi ve örnekleri memnuniyetle karşılıyorum; bu cevabı görmek güzel. Bu yaklaşım, yerleşik python modüllerini ve Twisted kurulumunu kullanmaktan çok daha fazla çalışma gerektirdiğinden, cevaba ekleyebilmenin herhangi bir avantajı var mı?
Jarret Hardie

1
Glif'in cevabı muhtemelen TwistedLore'un .tpl dosyalarını kullanmasından ilham almıştır.
Constantin

Peki, açıkça İspanyolca sorgulama beklemeyin :-) Ben "* .tpl" bazı soyut uzantı anlamına gelen "şablon" için genel bir referans olduğunu ve belirli bir Twisted şablonu (Ben birçok gördüm .tpl gördüm sonuçta diller). Bunu bildiğim iyi oldu.
Jarret Hardie

Bu nedenle olası Twisted açısına dalmak için +1, ancak yine de Twisted'd 'FilePath' nesnesinin ve 'walk ()' işlevinin standart API'ye ne eklediğini anlamak istiyorum.
Jarret Hardie

Şahsen ben "FilePath.walk () yol nesneleri verir" hatırlıyorum çok daha kolay "os.walk dir 3-tuples dirs, dirs, dosyaları" verir. Ancak başka faydaları da var. FilePath, polimorfizme izin verir, yani dosya sistemlerinden başka şeyleri geçebilirsiniz. Örneğin, 'alt dizinler' işlevime bir twisted.python.zippath.ZipArchive iletebilir ve FilePaths yerine bir ZipPaths oluşturucusu elde edebilirsiniz; mantığınız değişmez, ancak uygulamanız artık zip dosyalarını sihirli bir şekilde işler. Test etmek istiyorsanız, sadece bir nesne sağlamanız gerekir, gerçek dosyalar yazmak zorunda değilsiniz.
Glif

4

Sadece vmware sanal makineleri taşımak için bazı kod yazdım ve kullanarak os.pathve shutilalt dizinler arasında dosya kopyalama gerçekleştirmek için sona erdi .

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

Çok zarif değil, ama işe yarıyor.


1

İşte bir yol:

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: çalışmaz, shutil.copy geçerli dizine kopyalayacağından, alt dizin ağacında bulduğunuz her 'index.tpl' için bir kez geçerli dizinde 'index.html' nin üzerine yazılır.
nosklo

1

Path.py'den bahsetmek zorundayımÇok sık kullandığım kütüphanesinden .

Hemen alt dizinleri getirmek şu kadar basit hale gelir:

my_dir.dirs()

Tam çalışma örneği:

from path import Path

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

subdirs = my_directory.dirs()

Not: Yolum dizenin bir alt sınıfı olduğundan, ancak yolları işlemek için bir dizi yararlı yöntem sağladığından, dizinim hala bir dize olarak işlenebilir


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

Aşağıdaki işlev şu şekilde çağrılabilir:

get_folders_in_directories_recursively (dizin, dizin = 1) -> birinci düzeydeki klasörlerin listesini verir

get_folders_in_directories_recursively (dizin) -> tüm alt klasörleri verir


güzel, sürüm python 3.6 yapıyor, ama ben "kendini" silmek gerekiyordu, içinde fonksiyon değişkenleri
locometro

1
bir sınıf içinde kullanıyordum, güncelledi
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İşlevi, bir yol bir dizin alır ve bir listesini döndürür hemen alt dizinleri içinde.

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 kullanan bir astar:

list_subfolders_with_paths = [p for p in pathlib.Path(path).iterdir() if p.is_dir()]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.