Yinelemeli alt klasör arama ve bir liste python'daki dosyaları döndürme


119

Bir ana klasördeki alt klasörleri tekrar tekrar gözden geçirmek ve belirli bir dosya türünden bir liste oluşturmak için bir komut dosyası üzerinde çalışıyorum. Komut dosyasıyla ilgili bir sorun yaşıyorum. Şu anda aşağıdaki gibi ayarlanmıştır

for root, subFolder, files in os.walk(PATH):
    for item in files:
        if item.endswith(".txt") :
            fileNamePath = str(os.path.join(root,subFolder,item))

sorun, subFolder değişkeninin ITEM dosyasının bulunduğu klasör yerine alt klasörler listesini çekmesidir. Daha önce alt klasör için bir for döngüsü çalıştırmayı ve yolun ilk kısmına katılmayı düşünüyordum, ancak bundan önce herhangi bir önerisi olup olmadığını görmek için Id'yi iki kez kontrol etmeyi düşündüm. Yardımınız için teşekkürler!

Yanıtlar:


157

Aradığınız dirpathşeyi kullanmalısınız root. dirnamesEğer istemiyorsanız bu klasör varsa bunu erik böylece sağlanır os.walkiçine Recurse.

import os
result = [os.path.join(dp, f) for dp, dn, filenames in os.walk(PATH) for f in filenames if os.path.splitext(f)[1] == '.txt']

Düzenle:

En son olumsuz oylamadan sonra, globuzantıya göre seçim yapmak için daha iyi bir araç olduğu aklıma geldi .

import os
from glob import glob
result = [y for x in os.walk(PATH) for y in glob(os.path.join(x[0], '*.txt'))]

Ayrıca bir jeneratör versiyonu

from itertools import chain
result = (chain.from_iterable(glob(os.path.join(x[0], '*.txt')) for x in os.walk('.')))

Python 3.4+ için Edit2

from pathlib import Path
result = list(Path(".").rglob("*.[tT][xX][tT]"))

1
'*. [Tt] [Xx] [Tt]' glob şablonu, aramayı büyük / küçük harfe duyarlı hale getirecektir.
SergiyKolesnikov

@SergiyKolesnikov, Teşekkürler, bunu alttaki düzenlemede kullandım. rglobWindows platformlarında duyarsız olduğunu, ancak portatif olarak duyarsız olmadığını unutmayın.
John La Rooy

1
@JohnLaRooy Bu da çalışır glob(Python 3.6 burada):glob.iglob(os.path.join(real_source_path, '**', '*.[xX][mM][lL]')
SergiyKolesnikov

@Sergiy: iglobAlt-alt klasörlerdeki veya altındaki dosyalar için çalışmazsınız. Eklemeniz gerekiyor recursive=True.
user136036

1
@ user136036, "daha iyi" her zaman en hızlı anlamına gelmez. Bazen okunabilirlik ve sürdürülebilirlik de önemlidir.
John La Rooy

114

Python 3.5'te değişti : "**" kullanan özyinelemeli globlar için destek.

glob.glob()yeni bir özyinelemeli parametre aldı .

Her .txtdosyayı altına almak istiyorsanız my_path(alt dizinler dahil olmak üzere özyinelemeli olarak):

import glob

files = glob.glob(my_path + '/**/*.txt', recursive=True)

# my_path/     the dir
# **/       every file and dir under my_path
# *.txt     every file that ends with '.txt'

Bir yineleyiciye ihtiyacınız varsa, iglob'u alternatif olarak kullanabilirsiniz :

for file in glob.iglob(my_path, recursive=False):
    # ...

1
TypeError: glob () beklenmedik bir anahtar kelime bağımsız değişkeni 'yinelemeli' aldı
CyberJacob

1
Çalışıyor olmalı. > = 3.5 sürümünü kullandığınızdan emin olun. Daha fazla ayrıntı için cevabıma belgelere bir bağlantı ekledim.
Rotareti

Bu yüzden
2.7'deyim

1
Neden liste anlayışı ve sadece değil files = glob.glob(PATH + '/*/**/*.txt', recursive=True)?
tobltobs

Tüh! :) Tamamen gereksiz. Bunu bana ne yaptırdı bilmiyorum. Bahsettiğiniz için teşekkürler! Ben düzelteceğim.
Rotareti

20

John La Rooy'un liste anlayışını iç içe geçmişler için çevireceğim , sadece başka birinin anlamakta güçlük çekmesi durumunda.

result = [y for x in os.walk(PATH) for y in glob(os.path.join(x[0], '*.txt'))]

Eşdeğer olmalıdır:

import glob

result = []

for x in os.walk(PATH):
    for y in glob.glob(os.path.join(x[0], '*.txt')):
        result.append(y)

İşte liste anlama belgeleri ve os.walk ve glob.glob işlevleri .


1
Bu cevap benim için Python 3.7.3'te çalıştı. glob.glob(..., recursive=True)ve list(Path(dir).glob(...'))olmadı.
miguelmorin

11

Bu benim ile gelebilir en hızlı çözüm gibi görünüyor ve bir daha hızlı os.walkve çok daha hızlı hepsinden daha globsolüsyon .

  • Ayrıca, temelde ücretsiz olarak tüm iç içe geçmiş alt klasörlerin bir listesini verecektir.
  • Birkaç farklı uzantı arayabilirsiniz.
  • Ayrıca değiştirerek dosyalar için ya tam yollarını ya da sadece isimleri iade seçebilir f.pathiçin f.name(alt klasörler için değiştirmek yok!).

Args: dir: str, ext: list.
Fonksiyon döndüren iki liste: subfolders, files.

Ayrıntılı bir hız analizi için aşağıya bakın.

def run_fast_scandir(dir, ext):    # dir: str, ext: list
    subfolders, files = [], []

    for f in os.scandir(dir):
        if f.is_dir():
            subfolders.append(f.path)
        if f.is_file():
            if os.path.splitext(f.name)[1].lower() in ext:
                files.append(f.path)


    for dir in list(subfolders):
        sf, f = run_fast_scandir(dir, ext)
        subfolders.extend(sf)
        files.extend(f)
    return subfolders, files


subfolders, files = run_fast_scandir(folder, [".jpg"])


Hız analizi

Tüm alt klasörlerde ve ana klasörde belirli bir dosya uzantısına sahip tüm dosyaları almak için çeşitli yöntemler için.

tl; dr:
- fast_scandiraçıkça kazanır ve os.walk hariç diğer tüm çözümlerden iki kat daha hızlıdır.
- os.walkikinci sırada biraz daha yavaş.
- kullanmak globişlemi büyük ölçüde yavaşlatacaktır.
- Sonuçların hiçbiri doğal sıralama kullanmıyor . Bu, sonuçların şu şekilde sıralanacağı anlamına gelir: 1, 10, 2. Doğal sıralama (1, 2, 10) için lütfen https://stackoverflow.com/a/48030307/2441026 adresine bir göz atın.


Sonuçlar:

fast_scandir    took  499 ms. Found files: 16596. Found subfolders: 439
os.walk         took  589 ms. Found files: 16596
find_files      took  919 ms. Found files: 16596
glob.iglob      took  998 ms. Found files: 16596
glob.glob       took 1002 ms. Found files: 16596
pathlib.rglob   took 1041 ms. Found files: 16596
os.walk-glob    took 1043 ms. Found files: 16596

Testler W7x64, Python 3.8.1, 20 çalıştırma ile yapıldı. 439 (kısmen iç içe geçmiş) alt klasörlerde 16596 dosya.
find_filesdan https://stackoverflow.com/a/45646357/2441026 ve birkaç uzantıları için arama yapmanızı sağlar.
fast_scandirbenim tarafımdan yazılmıştır ve ayrıca bir alt klasör listesi de döndürecektir. Ona aranacak uzantıların bir listesini verebilirsiniz (Bir girişi basit olan bir listeyi test ettim if ... == ".jpg"ve önemli bir fark yoktu).


# -*- coding: utf-8 -*-
# Python 3


import time
import os
from glob import glob, iglob
from pathlib import Path


directory = r"<folder>"
RUNS = 20


def run_os_walk():
    a = time.time_ns()
    for i in range(RUNS):
        fu = [os.path.join(dp, f) for dp, dn, filenames in os.walk(directory) for f in filenames if
                  os.path.splitext(f)[1].lower() == '.jpg']
    print(f"os.walk\t\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def run_os_walk_glob():
    a = time.time_ns()
    for i in range(RUNS):
        fu = [y for x in os.walk(directory) for y in glob(os.path.join(x[0], '*.jpg'))]
    print(f"os.walk-glob\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def run_glob():
    a = time.time_ns()
    for i in range(RUNS):
        fu = glob(os.path.join(directory, '**', '*.jpg'), recursive=True)
    print(f"glob.glob\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def run_iglob():
    a = time.time_ns()
    for i in range(RUNS):
        fu = list(iglob(os.path.join(directory, '**', '*.jpg'), recursive=True))
    print(f"glob.iglob\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def run_pathlib_rglob():
    a = time.time_ns()
    for i in range(RUNS):
        fu = list(Path(directory).rglob("*.jpg"))
    print(f"pathlib.rglob\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(fu)}")


def find_files(files, dirs=[], extensions=[]):
    # https://stackoverflow.com/a/45646357/2441026

    new_dirs = []
    for d in dirs:
        try:
            new_dirs += [ os.path.join(d, f) for f in os.listdir(d) ]
        except OSError:
            if os.path.splitext(d)[1].lower() in extensions:
                files.append(d)

    if new_dirs:
        find_files(files, new_dirs, extensions )
    else:
        return


def run_fast_scandir(dir, ext):    # dir: str, ext: list
    # https://stackoverflow.com/a/59803793/2441026

    subfolders, files = [], []

    for f in os.scandir(dir):
        if f.is_dir():
            subfolders.append(f.path)
        if f.is_file():
            if os.path.splitext(f.name)[1].lower() in ext:
                files.append(f.path)


    for dir in list(subfolders):
        sf, f = run_fast_scandir(dir, ext)
        subfolders.extend(sf)
        files.extend(f)
    return subfolders, files



if __name__ == '__main__':
    run_os_walk()
    run_os_walk_glob()
    run_glob()
    run_iglob()
    run_pathlib_rglob()


    a = time.time_ns()
    for i in range(RUNS):
        files = []
        find_files(files, dirs=[directory], extensions=[".jpg"])
    print(f"find_files\t\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(files)}")


    a = time.time_ns()
    for i in range(RUNS):
        subf, files = run_fast_scandir(directory, [".jpg"])
    print(f"fast_scandir\ttook {(time.time_ns() - a) / 1000 / 1000 / RUNS:.0f} ms. Found files: {len(files)}. Found subfolders: {len(subf)}")

10

Yeni pathlibkütüphane bunu tek satıra indirgiyor:

from pathlib import Path
result = list(Path(PATH).glob('**/*.txt'))

Jeneratör versiyonunu da kullanabilirsiniz:

from pathlib import Path
for file in Path(PATH).glob('**/*.txt'):
    pass

Bu Path, hemen hemen her şey için kullanabileceğiniz veya dosya adını bir dizge olarak alabileceğiniz nesneleri döndürür file.name.


6

Bu en pitonik cevap değil, ama eğlence için buraya koyacağım çünkü bu özyineleme konusunda güzel bir ders

def find_files( files, dirs=[], extensions=[]):
    new_dirs = []
    for d in dirs:
        try:
            new_dirs += [ os.path.join(d, f) for f in os.listdir(d) ]
        except OSError:
            if os.path.splitext(d)[1] in extensions:
                files.append(d)

    if new_dirs:
        find_files(files, new_dirs, extensions )
    else:
        return

Makinemde iki klasörüm var rootveroot2

mender@multivax ]ls -R root root2
root:
temp1 temp2

root/temp1:
temp1.1 temp1.2

root/temp1/temp1.1:
f1.mid

root/temp1/temp1.2:
f.mi  f.mid

root/temp2:
tmp.mid

root2:
dummie.txt temp3

root2/temp3:
song.mid

Diyelim ki bu dizinlerdeki .txttüm .middosyaları bulmak istiyorum , o zaman yapabilirim

files = []
find_files( files, dirs=['root','root2'], extensions=['.mid','.txt'] )
print(files)

#['root2/dummie.txt',
# 'root/temp2/tmp.mid',
# 'root2/temp3/song.mid',
# 'root/temp1/temp1.1/f1.mid',
# 'root/temp1/temp1.2/f.mid']

4

Yinelemeli Python 3.5'te yenidir, bu nedenle Python 2.7'de çalışmayacaktır. İşte rdizeleri kullanan örnek, bu yüzden yolu Win, Lin, ...

import glob

mypath=r"C:\Users\dj\Desktop\nba"

files = glob.glob(mypath + r'\**\*.py', recursive=True)
# print(files) # as list
for f in files:
    print(f) # nice looking single line per file

Not: Ne kadar derine inmesi gerektiğine bakılmaksızın tüm dosyaları listeler.


3

Size mutlak yol dosyalarının bir listesini döndürmek için bu şekilde yapabilirsiniz.

def list_files_recursive(path):
    """
    Function that receives as a parameter a directory path
    :return list_: File List and Its Absolute Paths
    """

    import os

    files = []

    # r = root, d = directories, f = files
    for r, d, f in os.walk(path):
        for file in f:
            files.append(os.path.join(r, file))

    lst = [file for file in files]
    return lst


if __name__ == '__main__':

    result = list_files_recursive('/tmp')
    print(result)

3

Ek bir ışık kitaplığı kurmanın sakıncası yoksa, bunu yapabilirsiniz:

pip install plazy

Kullanımı:

import plazy

txt_filter = lambda x : True if x.endswith('.txt') else False
files = plazy.list_files(root='data', filter_func=txt_filter, is_include_root=True)

Sonuç şunun gibi görünmelidir:

['data/a.txt', 'data/b.txt', 'data/sub_dir/c.txt']

Hem Python 2.7 hem de Python 3 üzerinde çalışır.

Github: https://github.com/kyzas/plazy#list-files

Sorumluluk reddi: adlı kitabın yazarıyım plazy.


1

Bu işlev yinelemeli olarak yalnızca dosyaları bir listeye koyacaktır. Umarım bu sen olursun.

import os


def ls_files(dir):
    files = list()
    for item in os.listdir(dir):
        abspath = os.path.join(dir, item)
        try:
            if os.path.isdir(abspath):
                files = files + ls_files(abspath)
            else:
                files.append(abspath)
        except FileNotFoundError as err:
            print('invalid directory\n', 'Error: ', err)
    return files

0

Özgün çözümünüz neredeyse doğruydu, ancak "kök" değişkeni, özyinelemeli olarak dolaşırken dinamik olarak güncellenir. os.walk () özyinelemeli bir jeneratördür. Her demet kümesi (kök, altKlasör, dosyalar), sizin kurduğunuz şekilde belirli bir kök içindir.

yani

root = 'C:\\'
subFolder = ['Users', 'ProgramFiles', 'ProgramFiles (x86)', 'Windows', ...]
files = ['foo1.txt', 'foo2.txt', 'foo3.txt', ...]

root = 'C:\\Users\\'
subFolder = ['UserAccount1', 'UserAccount2', ...]
files = ['bar1.txt', 'bar2.txt', 'bar3.txt', ...]

...

Tam listeyi yazdırmak için kodunuzda küçük bir değişiklik yaptım.

import os
for root, subFolder, files in os.walk(PATH):
    for item in files:
        if item.endswith(".txt") :
            fileNamePath = str(os.path.join(root,item))
            print(fileNamePath)

Bu yardımcı olur umarım!

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.