215 votes

Comment faire une recherche récursive dans un sous-dossier et retourner les fichiers dans une liste ?

Je travaille sur un script pour parcourir récursivement les sous-dossiers d'un dossier principal et construire une liste à partir d'un certain type de fichier. J'ai un problème avec le script. Il est actuellement configuré comme suit :

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

le problème est que le subFolder extrait une liste de sous-dossiers plutôt que le dossier dans lequel se trouve le fichier ITEM. Je pensais exécuter une boucle for pour le sous-dossier avant et rejoindre la première partie du chemin d'accès, mais je me suis dit que je devais vérifier si quelqu'un avait des suggestions avant cela.

254voto

gnibbler Points 103484

Vous devriez utiliser le dirpath que vous appelez root . Le site dirnames sont fournis afin que vous puissiez les élaguer s'il y a des dossiers que vous ne souhaitez pas. os.walk pour faire des recherches.

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

Edit :

Après le dernier downvote, je me suis dit que glob est un meilleur outil de sélection par extension.

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

Il existe également une version du générateur

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

Edit2 pour Python 3.4 et plus

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

193voto

leOm Points 20

Changé en Python 3.5 : Support pour les globs récursifs utilisant "**".

glob.glob() a obtenu un nouveau paramètre récursif .

Si vous voulez obtenir chaque .txt fichier sous my_path (incluant récursivement les sous-répertoires) :

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'

Si vous avez besoin d'un itérateur, vous pouvez utiliser iglob comme alternative :

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

44voto

user136036 Points 101

C'est la solution la plus rapide que j'ai trouvée, et c'est plus vite que os.walk y beaucoup plus rapide que n'importe quel glob solution .

  • Il vous donnera également une liste de tous les sous-dossiers imbriqués, et ce gratuitement.
  • Vous pouvez rechercher plusieurs extensions différentes.
  • Vous pouvez également choisir de renvoyer les chemins d'accès complets ou seulement les noms des fichiers en modifiant le paramètre f.path a f.name (ne le changez pas pour les sous-dossiers !).

Arguments : dir: str, ext: list .
Fonction renvoie à deux listes : subfolders, files .

Voir ci-dessous pour une analyse détaillée de la vitesse.

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

Au cas où vous auriez besoin de la taille du fichier, vous pouvez également créer un fichier sizes et ajouter f.stat().st_size comme ça pour un affichage de MiB :

sizes.append(f"{f.stat().st_size/1024/1024:.0f} MiB")

Analyse de la vitesse

pour diverses méthodes permettant d'obtenir tous les fichiers ayant une extension spécifique dans tous les sous-dossiers et le dossier principal.

tl;dr :

  • fast_scandir l'emporte clairement et est deux fois plus rapide que toutes les autres solutions, sauf os.walk.
  • os.walk est en deuxième position, légèrement plus lente.
  • en utilisant glob ralentira considérablement le processus.
  • Aucun des résultats n'utilise tri naturel . Cela signifie que les résultats seront triés comme suit : 1, 10, 2 : 1, 10, 2. Pour obtenir un tri naturel (1, 2, 10), veuillez consulter le site suivant https://stackoverflow.com/a/48030307/2441026

**Résultats:**

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

Les tests ont été effectués avec W7x64, Python 3.8.1, 20 exécutions. 16596 fichiers dans 439 sous-dossiers (partiellement imbriqués).
find_files vient de https://stackoverflow.com/a/45646357/2441026 et vous permet de rechercher plusieurs extensions.
fast_scandir a été écrit par moi-même et retournera également une liste de sous-dossiers. Vous pouvez lui donner une liste d'extensions à rechercher (j'ai testé une liste avec une entrée à un simple if ... == ".jpg" et il n'y avait pas de différence significative).

# -*- 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)}")

31voto

Je vais traduire Compréhension de la liste de John La Rooy à des for imbriqués, juste au cas où quelqu'un d'autre aurait du mal à le comprendre.

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

Devrait être équivalent à :

import glob
import os

result = []

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

Voici la documentation pour compréhension de la liste et les fonctions os.walk y glob.glob .

18voto

Emre Points 323

Le nouveau pathlib simplifie cette opération en une seule ligne :

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

Vous pouvez également utiliser la version du générateur :

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

Ce retour Path que vous pouvez utiliser pour à peu près tout. Vous pouvez aussi obtenir le nom du fichier sous forme de chaîne de caractères en utilisant la commande file.name .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X