Как Python способ обходить дерево каталогов?

Я чувствую, что присвоение файлов и папок и выполнение части += [item] немного странно. Какие-либо предложения? Я использую Python 3.2

from os import *
from os.path import *

def dir_contents(path):
    contents = listdir(path)
    files = []
    folders = []
    for i, item in enumerate(contents):
        if isfile(contents[i]):
            files += [item]
        elif isdir(contents[i]):
            folders += [item]
    return files, folders

17 ответов

Решение

Посмотрите на os.walk функция, которая возвращает путь вместе с каталогами и файлами, которые она содержит. Это должно значительно сократить ваше решение.

os.walk и os.scandirявляются отличными вариантами, однако я все больше и больше использую pathlib, а с pathlib вы можете использовать .glob() метод:

root_directory = Path(".")
for path_object in root_directory.glob('**/*'):
    if path_object.is_file():
        print(f"hi, I'm a file: {path_object}")
    elif path_object.is_dir():
        print(f"hi, I'm a dir: {path_object}")


Для тех, кто ищет решение, использующее pathlib (python >= 3.4)

from pathlib import Path

def walk(path): 
    for p in Path(path).iterdir(): 
        if p.is_dir(): 
            yield from walk(p)
            continue
        yield p.resolve()

# recursively traverse all files from current directory
for p in walk(Path('.')): 
    print(p)

# the function returns a generator so if you need a list you need to build one
all_files = list(walk(Path('.'))) 

Однако, как упоминалось выше, это не сохраняет нисходящий порядок, задаваемый os.walk

С Python >= 3.4существует метод генератора Path.rglob. Итак, чтобы обработать все пути под some/starting/pathпросто сделайте что-нибудь вроде

      from pathlib import Path

path = Path('some/starting/path') 
for subpath in path.rglob('*'):
    # do something with subpath

Чтобы получить все подпути в списке, выполните list(path.rglob('*')). Чтобы получить только файлы с sqlрасширение, сделать list(path.rglob('*.sql')).

Если вы хотите рекурсивно перебирать все файлы, включая все файлы в подпапках, я считаю, что это лучший способ.

import os

def get_files(input):
    for fd, subfds, fns in os.walk(input):
       for fn in fns:
            yield os.path.join(fd, fn)

## now this will print all full paths

for fn in get_files(fd):
    print(fn)

Другое решение, как пройтись по дереву каталогов, используяpathlibмодуль:

      from pathlib import Path

for directory in Path('.').glob('**'):
    for item in directory.iterdir():
        print(item)

Шаблон**соответствует текущему каталогу и всем подкаталогам рекурсивно, а методiterdirзатем перебирает содержимое каждого каталога. Полезно, когда вам нужно больше контроля при обходе дерева каталогов.

Начиная с Python 3.4 появился новый модуль pathlib. Таким образом, чтобы получить все каталоги и файлы, можно сделать:

from pathlib import Path

dirs = [str(item) for item in Path(path).iterdir() if item.is_dir()]
files = [str(item) for item in Path(path).iterdir() if item.is_file()]

Действительно используя

items += [item]

это плохо по многим причинам...

  1. append метод был сделан именно для этого (добавление одного элемента в конец списка)

  2. Вы создаете временный список из одного элемента, чтобы просто выбросить его. Хотя грубая скорость не должна вас беспокоить при использовании Python (в противном случае вы используете не тот язык), тем не менее бесполезная потеря скорости кажется неправильной.

  3. Вы используете небольшую асимметрию языка Python... для записи объектов списка a += b не то же самое, что писать a = a + b потому что первый изменяет объект на месте, а второй вместо этого выделяет новый список, и это может иметь другую семантику, если объект a также достижимо с помощью других способов. В вашем конкретном коде это не так, но это может стать проблемой позже, когда кто-то другой (или вы сами через несколько лет, то же самое) должны будут изменить код. У Python даже есть метод extend с менее тонким синтаксисом, который специально создан для обработки случая, когда вы хотите изменить на месте объект списка, добавив в конце элементы другого списка.

Также, как другие заметили, кажется, что ваш код пытается сделать то, что os.walk уже делает...

def dir_contents(path):
    files,folders = [],[]
    for p in listdir(path):
        if isfile(p): files.append(p)
        else: folders.append(p)
    return files, folders

Вместо встроенных os.walk и os.path.walk я использую что-то полученное из этого фрагмента кода, который я нашел в другом месте:

http://code.google.com/p/mylibs/source/browse/lib/Python/MyPyLib/DirectoryStatWalker.py

Я не буду это повторять, но он рекурсивно просматривает каталоги, довольно эффективен и легко читается.

Вот версия, которая использует и возвращает древовидную структуру. С использованиемos.scandirвернетсяos.DirEntryобъекты, которые содержат информацию об объектах пути в памяти, что позволяет запрашивать информацию об элементах без вызовов файловой системы.

      import os

def treedir(path):
    files = []
    folders = {}
    for entry in os.scandir(path):
        if entry.is_file():
            files.append(entry)
        elif entry.is_dir():
            folders[entry.name] = treedir(entry)
    result = {}
    if files:
        result['files'] = files
    if folders:
        result['folders'] = folders
    return result

Мне нравится структура результата, но я предпочитаюpathlibобщий. Поэтому мое ленивое решение — просто создатьPathс каждого товара, возвращенногоos.walk().

      import os
import pathlib


def walk(path='bin'):
    for root, dirs, files in os.walk(path):
        root = pathlib.Path(root)
        dirs = [root / d for d in dirs]
        files = [root / f for f in files]
        yield root, dirs, files
      import pathlib
import time

def prune_empty_dirs(path: pathlib.Path):
    for current_path in list(path.rglob("*"))[::-1]:
        if current_path.is_dir() and not any(current_path.iterdir()):
            current_path.rmdir()
            while current_path.exists():
                time.sleep(0.1)

Попробуйте использовать append метод.

Я еще не проверял это подробно, но я верю, что это расширит os.walk генератор, присоедините dirnames ко всем путям файлов и сгладьте результирующий список; Чтобы получить прямой список конкретных файлов в вашем пути поиска.

import itertools
import os

def find(input_path):
    return itertools.chain(
        *list(
            list(os.path.join(dirname, fname) for fname in files)
            for dirname, _, files in os.walk(input_path)
        )
    )

В поисках той же информации я нашел этот вопрос.

Я публикую здесь самый маленький и понятный код, который я нашел по адресу http://www.pythoncentral.io/how-to-traverse-a-directory-tree-in-python-guide-to-os-walk/ (вместо просто разместив URL, в случае ссылки гниль).

На странице есть некоторая полезная информация, а также указывает на несколько других соответствующих страниц.

# Import the os module, for the os.walk function
import os

# Set the directory you want to start from
rootDir = '.'
for dirName, subdirList, fileList in os.walk(rootDir):
    print('Found directory: %s' % dirName)
    for fname in fileList:
        print('\t%s' % fname)

Скопируйте и вставьте код для тех, кто хочет глубоко просмотреть все подкаталоги, мы можем использоватьpython recursionвызов:

      import os

def deep_walk(mypath):
    file_list = []
    for root, dirs, files in os.walk(mypath):
        for file in files:
            file_list.append(os.path.join(root, file))
        for dir in dirs:
            if os.path.isdir(dir):
                mypath = os.path.join(root, dir)
                deep_walk(mypath)
    for f in file_list:
        print(f)

def main():
    mypath="/tmp"
    deep_walk(mypath)

if __name__ == '__main__':
    main()
Другие вопросы по тегам