os.walk bez zagłębiania się w poniższe katalogi

105

Jak ograniczyć os.walkzwracanie plików tylko z katalogu, który go podam?

def _dir_list(self, dir_name, whitelist):
    outputList = []
    for root, dirs, files in os.walk(dir_name):
        for f in files:
            if os.path.splitext(f)[1] in whitelist:
                outputList.append(os.path.join(root, f))
            else:
                self._email_to_("ignore")
    return outputList
Setori
źródło
2
Inny przypadek, w którym mnogość możliwych podejść i wszystkie związane z nimi zastrzeżenia sugerują, że ta funkcjonalność powinna zostać dodana do standardowej biblioteki Pythona.
antred
files_with_full_path = [f.path for f in os.scandir(dir) if f.is_file()]. W przypadku, gdy potrzebujesz tylko nazw plików, użyj f.namezamiast f.path. Jest to najszybsze rozwiązanie i znacznie szybsze niż jakiekolwiek inne walklub listdir, zobacz stackoverflow.com/a/40347279/2441026 .
user136036

Odpowiedzi:

105

Użyj walklevelfunkcji.

import os

def walklevel(some_dir, level=1):
    some_dir = some_dir.rstrip(os.path.sep)
    assert os.path.isdir(some_dir)
    num_sep = some_dir.count(os.path.sep)
    for root, dirs, files in os.walk(some_dir):
        yield root, dirs, files
        num_sep_this = root.count(os.path.sep)
        if num_sep + level <= num_sep_this:
            del dirs[:]

Działa tak samo os.walk, ale możesz przekazać mu levelparametr, który wskazuje, jak głęboko zajdzie rekurencja.

nosklo
źródło
3
Czy ta funkcja faktycznie „przechodzi” przez całą strukturę, a następnie usuwa wpisy poniżej określonego punktu? A może dzieje się coś mądrzejszego? Nie jestem nawet pewien, jak to sprawdzić za pomocą kodu. --python początkujący
mathtick Sierpnia
1
@mathtick: gdy zostanie znaleziony katalog na lub poniżej żądanego poziomu, wszystkie jego podkatalogi są usuwane z listy podkatalogów w celu przeszukania. Więc nie będą „chodzić”.
nosklo
2
Po prostu dałem +1, ponieważ zmagałem się z „usuwaniem” książek. Próbowałem dirs = []i dirs = Noneale te nie działają. map(dirs.remove, dirs)zadziałało, ale zostało wydrukowanych kilka niechcianych komunikatów „[Brak]”. Więc dlaczego del dirs[:]konkretnie?
Zach Young,
4
Zauważ, że to nie działa, gdy używasz topdown=Falsew os.walk. Zobacz czwarty akapit w dokumentach :Modifying dirnames when topdown is False has no effect on the behavior of the walk, because in bottom-up mode the directories in dirnames are generated before dirpath itself is generated.
dthor,
3
@ZacharyYoung dirs = []i dirs = Nonenie będą działać, ponieważ po prostu tworzą nowy niepowiązany obiekt i przypisują go do nazwy dirs. Oryginalny obiekt listy należy zmodyfikować lokalnie, a nie nazwę dirs.
nosklo
207

Nie używaj os.walk.

Przykład:

import os

root = "C:\\"
for item in os.listdir(root):
    if os.path.isfile(os.path.join(root, item)):
        print item
Yuval Adam
źródło
1
@ 576i: to nie rozróżnia plików i katalogów
4
@Alexandr os.path.isfilei os.path.isdirpozwala odróżnić. Nie rozumiem, ponieważ os.path.isfileznajduje się w przykładowym kodzie od '08, a twój komentarz pochodzi z '16. To jest zdecydowanie lepsza odpowiedź, ponieważ nie zamierzasz chodzić po katalogu, ale go wymienić.
Daniel F
@DanielF, miałem tutaj na myśli to, że musisz zapętlić wszystkie elementy, a jednocześnie walkwyświetla oddzielne listy katalogów i plików.
Ach, ok. Właściwie odpowiedź Alexa wydaje się lepsza (używając .next()) i jest znacznie bliższa Twojemu pomysłowi.
Daniel F
Python 3.5 ma os.scandirfunkcję, która umożliwia bardziej wyrafinowaną interakcję plik-lub-katalog-obiekt. Zobacz moją odpowiedź poniżej
ascripter
48

Myślę, że rozwiązanie jest w rzeczywistości bardzo proste.

posługiwać się

break

aby wykonać tylko pierwszą iterację pętli for, musi istnieć bardziej elegancki sposób.

for root, dirs, files in os.walk(dir_name):
    for f in files:
        ...
        ...
    break
...

Przy pierwszym wywołaniu os.walk zwraca on tulipany dla bieżącego katalogu, a następnie w następnej pętli wyświetla zawartość następnego katalogu.

Weź oryginalny skrypt i po prostu dodaj przerwę .

def _dir_list(self, dir_name, whitelist):
    outputList = []
    for root, dirs, files in os.walk(dir_name):
        for f in files:
            if os.path.splitext(f)[1] in whitelist:
                outputList.append(os.path.join(root, f))
            else:
                self._email_to_("ignore")
        break
    return outputList
Pieter
źródło
9
To powinna być akceptowana odpowiedź. Samo dodanie „przerwy” po pętli „for f in files” zatrzymuje rekursywność. Możesz również upewnić się, że topdown = True.
Alecz
23

Sugestia użycia listdirjest dobra. Bezpośrednia odpowiedź na twoje pytanie w Pythonie 2 brzmi root, dirs, files = os.walk(dir_name).next().

Odpowiednia składnia Pythona 3 to root, dirs, files = next(os.walk(dir_name))

Alex Coventry
źródło
1
Och, dostałem od tego czegoś zabawnego błędu. ValueError: zbyt wiele wartości do rozpakowania
Setori,
1
Miły! Czuje się jednak jak włamanie. Tak jak wtedy, gdy włączasz silnik, ale pozwalasz mu wykonać tylko jeden obrót, a następnie pociągasz za klucz, aby umarł.
Daniel F
Natknąłem się na to; root, dirs, files = os.walk(dir_name).next()daje miAttributeError: 'generator' object has no attribute 'next'
Evan
3
@Evan, prawdopodobnie dlatego, że pochodzi z 2008 roku i używa składni Pythona 2. W Pythonie 3 można pisać root, dirs, files = next(os.walk(dir_name))i wtedy zmienne root, dirs, filesbędą odpowiadały tylko zmiennym generatora na dir_namepoziomie.
CervEd
13

Możesz użyć, os.listdir()który zwraca listę nazw (zarówno plików, jak i katalogów) w podanym katalogu. Jeśli potrzebujesz rozróżnić pliki i katalogi, wywołaj os.stat()każdą nazwę.

Greg Hewgill
źródło
9

Jeśli masz bardziej złożone wymagania niż tylko główny katalog (np. Zignoruj ​​katalogi VCS itp.), Możesz również zmodyfikować listę katalogów, aby zapobiec powtarzaniu się przez nie os.walk.

to znaczy:

def _dir_list(self, dir_name, whitelist):
    outputList = []
    for root, dirs, files in os.walk(dir_name):
        dirs[:] = [d for d in dirs if is_good(d)]
        for f in files:
            do_stuff()

Uwaga - uważaj, aby zmodyfikować listę, a nie tylko ją ponownie powiązać. Oczywiście os.walk nie wie o zewnętrznym rebindingu.

Brian
źródło
6
for path, dirs, files in os.walk('.'):
    print path, dirs, files
    del dirs[:] # go only one level deep
masterxilo
źródło
4

Ten sam pomysł listdir, ale krótszy:

[f for f in os.listdir(root_dir) if os.path.isfile(os.path.join(root_dir, f))]
Oleg Gryb
źródło
3

Czułem się jak wrzucenie moich 2 pensów.

baselevel = len(rootdir.split("\\"))
for subdirs, dirs, files in os.walk(rootdir):
    curlevel = len(subdirs.split("\\"))
    if curlevel <= baselevel + 1:
        [do stuff]
Matt R.
źródło
2

W Pythonie 3 udało mi się to zrobić:

import os
dir = "/path/to/files/"

#List all files immediately under this folder:
print ( next( os.walk(dir) )[2] )

#List all folders immediately under this folder:
print ( next( os.walk(dir) )[1] )
Jay Sheth
źródło
Działa to również dla Pythona 2. Jak zdobyć drugi poziom?
2

Od Pythona 3.5 możesz używać os.scandirzamiast os.listdir. Zamiast łańcuchów otrzymujesz DirEntryw zamian iterator obiektów. Z dokumentów:

Używanie scandir()zamiast tego listdir()może znacznie zwiększyć wydajność kodu, który również wymaga informacji o typie pliku lub atrybutach pliku, ponieważ DirEntryobiekty ujawniają te informacje, jeśli system operacyjny udostępnia je podczas skanowania katalogu. Wszystkie DirEntrymetody mogą wykonywać połączenia systemu, ale is_dir()i is_file()zazwyczaj wymagają jedynie wywołanie systemowe dla dowiązania symboliczne; DirEntry.stat()zawsze wymaga wywołania systemowego w Uniksie, ale wymaga tylko jednego dla dowiązań symbolicznych w Windows.

Możesz uzyskać dostęp do nazwy obiektu, za pośrednictwem DirEntry.namektórej jest odpowiednikiem danych wyjściowych poleceniaos.listdir

ascripter
źródło
1
Nie tylko możesz używać, ale powinieneś używać scandir(), ponieważ jest o wiele szybszy niż listdir(). Zobacz testy porównawcze tutaj: stackoverflow.com/a/40347279/2441026 .
user136036
1

Możesz też wykonać następujące czynności:

for path, subdirs, files in os.walk(dir_name):
    for name in files:
        if path == ".": #this will filter the files in the current directory
             #code here
Diana G.
źródło
2
Czy ta pętla nie przejdzie niepotrzebnie przez wszystkie podkatalogi i pliki?
Pieter
0

Tak to rozwiązałem

if recursive:
    items = os.walk(target_directory)
else:
    items = [next(os.walk(target_directory))]

...
Deifyed
źródło
0

Podczas korzystania z listdir jest pewien haczyk. Os.path.isdir (identyfikator) musi być ścieżką bezwzględną. Aby wybrać podkatalogi, które robisz:

for dirname in os.listdir(rootdir):
  if os.path.isdir(os.path.join(rootdir, dirname)):
     print("I got a subdirectory: %s" % dirname)

Alternatywą jest przejście do katalogu, aby wykonać test bez funkcji os.path.join ().

Kemin Zhou
źródło
0

Możesz użyć tego fragmentu

for root, dirs, files in os.walk(directory):
    if level > 0:
        # do some stuff
    else:
        break
    level-=1
Rousseau Alexandre
źródło
0

utwórz listę wykluczeń, użyj fnmatch, aby pominąć strukturę katalogów i wykonać proces

excludes= ['a\*\b', 'c\d\e']
for root, directories, files in os.walk('Start_Folder'):
    if not any(fnmatch.fnmatch(nf_root, pattern) for pattern in excludes):
        for root, directories, files in os.walk(nf_root):
            ....
            do the process
            ....

to samo, co w przypadku „obejmuje”:

if **any**(fnmatch.fnmatch(nf_root, pattern) for pattern in **includes**):
Hamsavardhini
źródło
0

Dlaczego nie po prostu użyć rangei w os.walkpołączeniu zzip ? To nie jest najlepsze rozwiązanie, ale też by działało.

Na przykład w ten sposób:

# your part before
for count, (root, dirs, files) in zip(range(0, 1), os.walk(dir_name)):
    # logic stuff
# your later part

U mnie działa na Pythonie 3.

Poza tym: A przy breakokazji też jest prostszy. (Spójrz na odpowiedź od @Pieter)

PiMathCLanguage
źródło
0

Niewielka zmiana w odpowiedzi Alexa, ale używając __next__():

print(next(os.walk('d:/'))[2]) lub print(os.walk('d:/').__next__()[2])

z [2]bycia filew root, dirs, filewspomniano w innych odpowiedzi

Oleg
źródło
0

zmiany folderu głównego dla każdego katalogu znalezionego przez os.walk. Rozwiązuję to sprawdzanie, czy root == katalog

def _dir_list(self, dir_name, whitelist):
    outputList = []
    for root, dirs, files in os.walk(dir_name):
        if root == dir_name: #This only meet parent folder
            for f in files:
                if os.path.splitext(f)[1] in whitelist:
                    outputList.append(os.path.join(root, f))
                else:
                    self._email_to_("ignore")
    return outputList
Pedro J. Sola
źródło
0
import os

def listFiles(self, dir_name):
    names = []
    for root, directory, files in os.walk(dir_name):
        if root == dir_name:
            for name in files:
                names.append(name)
    return names
Bogaty
źródło
1
Cześć Rich, witaj w Stack Overflow! Dziękujemy za ten fragment kodu, który może zapewnić ograniczoną krótkoterminową pomoc. Właściwe wyjaśnienie znacznie poprawiłoby jego długoterminową wartość, pokazując, dlaczego jest to dobre rozwiązanie problemu, i uczyniłoby go bardziej użytecznym dla przyszłych czytelników z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
kenny_k