os.walk, не заглиблюючись у каталоги нижче


103

Як обмежувати os.walkлише повернення файлів у наданому їм каталозі?

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

2
Інший випадок, коли безліч можливих підходів та всіх застережень, які йдуть із ними, говорить про те, що цю функціональність слід додати до стандартної бібліотеки Python.
antred

files_with_full_path = [f.path for f in os.scandir(dir) if f.is_file()]. У випадку, якщо вам потрібно використовувати лише імена файлів f.nameзамість f.path. Це найшвидше рішення і набагато швидше, ніж будь-яке walkабо listdir, див. Stackoverflow.com/a/40347279/2441026 .
користувач136036

Відповіді:


105

Використовуйте walklevelфункцію.

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[:]

Це працює так само os.walk, але ви можете передати йому levelпараметр, який вказує, наскільки глибока буде проходити рекурсія.


3
Чи реально ця функція "проходить" через всю структуру, а потім видаляє записи нижче певного пункту? Або відбувається щось більш розумне? Я навіть не впевнений, як це перевірити за допомогою коду. --python beginner
mathtick

1
@mathtick: коли знайдеться деякий каталог на або нижче потрібного рівня, всі його підкаталоги видаляються зі списку підкаталогів для наступного пошуку. Таким чином, вони не будуть "гуляти".
nosklo

2
Я просто поставив це +1, тому що боровся з тим, як "видалити" панів. Я спробував dirs = []і , dirs = Noneале ті не працюють. map(dirs.remove, dirs)працювали, але було надруковано кілька небажаних повідомлень "[None]". Отже, чому del dirs[:]саме?
Зак Янг

4
Зауважте, що це не працює при використанні topdown=Falseв os.walk. Дивіться 4-й абзац у документах :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.
1616

3
@ZacharyYoung dirs = []і dirs = Noneне працюватиме, оскільки вони просто створюють новий непов'язаний об'єкт і призначають ім'я dirs. Оригінальний об'єкт списку потрібно змінити на місці, а не ім'я dirs.
nosklo

206

Не використовуйте os.walk.

Приклад:

import os

root = "C:\\"
for item in os.listdir(root):
    if os.path.isfile(os.path.join(root, item)):
        print item

1
@ 576i: це не розрізняє файли та каталоги

4
@ Олександр os.path.isfileі os.path.isdirдозволяє вам диференціюватися. Я цього не розумію, оскільки os.path.isfileє у зразковому коді з '08 року, а ваш коментар - '16. Це, очевидно, краща відповідь, оскільки ви не маєте наміру ходити по каталогу, а перелічити його.
Даніель Ф

@DanielF, що я тут мав на увазі, що вам потрібно провести циклічну перевірку над усіма предметами, при цьому walkви отримуєте негайно окремі списки файлів і файлів.

Ну гаразд. Насправді відповідь Алекса здається кращою (використовуючи .next()) і вона набагато ближче до вашої ідеї.
Даніель Ф

Python 3.5 має os.scandirфункцію, яка дозволяє взаємодія файлів чи каталогів-об’єктів. Дивіться мою відповідь нижче
прихильник

48

Я думаю, що рішення насправді дуже просте.

використання

break

щоб зробити першу ітерацію циклу для циклу, повинен бути більш елегантний спосіб.

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

Перший раз, коли ви викликаєте os.walk, він повертає тюльпани для поточного каталогу, а потім у наступному циклі вміст наступного каталогу.

Візьміть оригінальний сценарій і просто додайте перерву .

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

9
Це мала бути прийнятою відповіддю. Просто додавання "перерви" після циклу "for f у файлах" зупиняє рекурсивність. Ви також можете переконатися, що topdown = True.
Алеч

23

Пропозиція використовувати listdirхороша. Пряма відповідь на ваше запитання в Python 2 - цеroot, dirs, files = os.walk(dir_name).next() .

Еквівалентний синтаксис Python 3 є root, dirs, files = next(os.walk(dir_name))


1
О, я отримував усілякі смішні помилки від цього. ValueError: занадто багато значень для розпакування
Setori

1
Приємно! Схоже, хак. Як і коли ви включаєте двигун, але тільки нехай він робить один оборот, а потім потягніть ключ, щоб він загинув.
Даніель Ф

Наткнувся на це; root, dirs, files = os.walk(dir_name).next()дарує меніAttributeError: 'generator' object has no attribute 'next'
Еван

3
@Evan, мабуть тому, що це з 2008 року та використовує синтаксис Python 2. У Python 3 ви можете записати, root, dirs, files = next(os.walk(dir_name))і тоді змінні root, dirs, filesбудуть відповідати лише змінним генератора на dir_nameрівні.
CervEd

13

Ви можете використовувати, os.listdir()що повертає список імен (як для файлів, так і для каталогів) у заданій теці. Якщо вам потрібно розрізняти файли та каталоги, зателефонуйте os.stat()до кожного імені.


9

Якщо у вас є більш складні вимоги, ніж просто головна директорія (наприклад, ігноруйте файли VCS і т. Д.), Ви також можете змінити список каталогів, щоб запобігти тому, щоб os.walk повторювався через них.

тобто:

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

Примітка. Будьте обережні, щоб вимкнути список, а не просто його повторно пов’язувати. Очевидно, os.walk не знає про зовнішнє відсікання.



4

Ця ж ідея listdir, але коротша:

[f for f in os.listdir(root_dir) if os.path.isfile(os.path.join(root_dir, f))]

3

Відчув, як кинув мою 2 пенси.

baselevel = len(rootdir.split("\\"))
for subdirs, dirs, files in os.walk(rootdir):
    curlevel = len(subdirs.split("\\"))
    if curlevel <= baselevel + 1:
        [do stuff]

2

У Python 3 мені вдалося це зробити:

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

Це також працює для Python 2. Як отримати другий рівень?

2

Оскільки Python 3.5 ви можете використовувати os.scandirзамість os.listdir. Замість рядків ви отримуєте ітератор DirEntryоб'єктів натомість. З документів:

Використання scandir()замість listdir()може значно збільшити продуктивність коду, який також потребує інформації про тип файлу або атрибути файлів, оскільки DirEntryоб’єкти викривають цю інформацію, якщо операційна система надає її під час сканування каталогу. Всі DirEntryметоди можуть виконати системний виклик, але is_dir()і , як is_file()правило , потрібно тільки системний виклик для символічних посилань; DirEntry.stat()завжди вимагає системного виклику в Unix, але вимагає лише одного для символічних посилань у Windows.

Ви можете отримати доступ до імені об'єкта, за допомогою DirEntry.nameякого потім еквівалентний вихідos.listdir


1
Ви не тільки "вмієте" ви користуватися, ви повинні використовувати scandir(), оскільки це набагато швидше, ніж listdir(). Див. Орієнтири тут: stackoverflow.com/a/40347279/2441026 .
користувач136036

1

Ви також можете зробити наступне:

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

2
Чи не буде ця петля через усі суб-реєстр та файли зайвими?
Пітер


0

Існує улов при використанні listdir. Os.path.isdir (ідентифікатор) повинен бути абсолютним шляхом. Щоб вибрати підкаталоги:

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

Альтернативно - перейти в каталог, щоб зробити тестування без os.path.join ().



0

створіть список виключень, використовуйте fnmatch, щоб пропустити структуру каталогу та виконати процес

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

те ж, що і для "включає":

if **any**(fnmatch.fnmatch(nf_root, pattern) for pattern in **includes**):

0

Чому б просто не використовувати rangeта os.walkпоєднати з zip? Це не найкраще рішення, але працювало б теж.

Наприклад так:

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

Для мене працює на python 3.

Також: A breakпростіше занадто btw. (Подивіться на відповідь від @Pieter)


0

Невелика зміна відповіді Алекса, але використовуючи __next__():

print(next(os.walk('d:/'))[2]) або print(os.walk('d:/').__next__()[2])

з [2]будучи fileв root, dirs, fileзгаданих в інших відповідях


0

зміни кореневої папки для кожного каталогу, знайденого os.walk. Я вирішую цю перевірку, чи root == каталог

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

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

1
Привіт Річ, Ласкаво просимо в Стек Overflow! Дякуємо за цей фрагмент коду, який може надати деяку короткочасну допомогу. Правильне пояснення значно покращило б його довгострокове значення, показавши, чому це хороше рішення проблеми, та зробило б кориснішим майбутнім читачам інші подібні питання. Будь ласка , змініть свій відповідь , щоб додати деякі пояснення, в тому числі припущень , які ви зробили.
kenny_k
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.