Рубрики
Без рубрики

Python: Список файлов в каталоге

Автор оригинала: Frank Hofmann.

Python: Список файлов в каталоге

Я предпочитаю работать с Python, потому что это очень гибкий язык программирования и позволяет мне легко взаимодействовать с операционной системой. Это также включает в себя функции файловой системы. Чтобы просто перечислить файлы в каталоге, в игру вступают модули os , subprocess , fnmatch и pathlib . Следующие решения демонстрируют, как эффективно использовать эти методы.

Использование os.walk()

Модуль os содержит длинный список методов, которые имеют дело с файловой системой и операционной системой. Один из них – walk() , который генерирует имена файлов в дереве каталогов, проходя по дереву либо сверху вниз, либо снизу вверх (причем по умолчанию используется параметр “сверху вниз”).

os.walk() возвращает список из трех элементов. Он содержит имя корневого каталога, список имен подкаталогов и список имен файлов в текущем каталоге. Листинг 1 показывает, как написать это всего с тремя строками кода. Это работает как с интерпретаторами Python 2, так и с интерпретаторами Python 3.

Листинг 1: Обход текущего каталога с помощью ос.прогулка()

import os

for root, dirs, files in os.walk("."):
    for filename in files:
        print(filename)

Использование командной строки через Подпроцесс

Примечание : Хотя это допустимый способ перечисления файлов в каталоге, он не рекомендуется, поскольку он вводит возможность для атак с использованием командных инъекций.

Как уже было описано в статье Параллельная обработка в Python , модуль subprocess позволяет выполнить системную команду и собрать ее результат. Системная команда, которую мы вызываем в этом случае, является следующей:

Пример 1: Перечисление файлов в текущем каталоге

$ ls -p . | grep -v /$

Команда ls . перечисляет файлы каталогов для текущего каталога и добавляет разделитель /| в конце имени каждого подкаталога, который нам понадобится на следующем шаге. Вывод этого вызова передается в команду grep , которая фильтрует данные по мере необходимости.

Параметры -v/$ исключают все имена записей, которые заканчиваются разделителем //. На самом деле, /$ - это регулярное выражение, которое соответствует всем строкам, содержащим символ /| как самый последний символ перед концом строки, который представлен символом $ .

Модуль subprocess позволяет создавать реальные каналы и соединять входные и выходные потоки, как это делается в командной строке. Вызов метода подпроцесс.Popen() открывает соответствующий процесс и определяет два параметра с именами stdin и stdout .

В листинге 2 показано, как это запрограммировать. Первая переменная ls определяется как процесс, выполняющий lsp . что выводит в трубу. Вот почему канал stdout определяется как подпроцесс.ТРУБА . Вторая переменная grep тоже определяется как процесс, но вместо этого выполняет команду grep-v/$ .

Чтобы считывать выходные данные команды ls из канала, канал stdin grep определяется как ls.stdout . Наконец, переменная end Of Pipe считывает выходные данные grep из grep.stdout , которые печатаются в stdout по элементам в for -цикле ниже. Вывод виден в Примере 2 .

Листинг 2: Определение двух процессов, связанных с трубой

import subprocess

# define the ls command
ls = subprocess.Popen(["ls", "-p", "."],
                      stdout=subprocess.PIPE,
                     )

# define the grep command
grep = subprocess.Popen(["grep", "-v", "/$"],
                        stdin=ls.stdout,
                        stdout=subprocess.PIPE,
                        )

# read from the end of the pipe (stdout)
endOfPipe = grep.stdout

# output the files line by line
for line in endOfPipe:
    print (line)

Пример 2: Запуск программы

$ python find-files3.py
find-files2.py
find-files3.py
find-files4.py
...

Это решение довольно хорошо работает как с Python2, так и с Python3, но можем ли мы как-то улучшить его? Тогда давайте посмотрим на другие варианты.

Объединение ОС и fnmatch

Как вы уже видели, решение с использованием подпроцессов является элегантным, но требует большого количества кода. Вместо этого давайте объединим методы из двух модулей os и fnmatch . Этот вариант также работает с Python 2 и 3.

В качестве первого шага мы импортируем два модуля os и fnmatch . Далее мы определяем каталог, в котором мы хотели бы перечислить файлы , используя os.listdir () , а также шаблон, по которому файлы будут фильтроваться. В цикле for мы перебираем список записей, хранящихся в переменной list Of Files .

Наконец, с помощью fnmatch мы фильтруем записи, которые ищем, и печатаем соответствующие записи в stdout. Листинг 3 содержит скрипт Python и Пример 3 соответствующий вывод.

Листинг 3: Список файлов с использованием модуля os и fnmatch

import os, fnmatch

listOfFiles = os.listdir('.')
pattern = "*.py"
for entry in listOfFiles:
    if fnmatch.fnmatch(entry, pattern):
            print (entry)

Пример 3: Выходные данные листинга 3

$ python2 find-files.py
find-files.py
find-files2.py
find-files3.py
...

Использование os.listdir() и генераторов

Проще говоря, генератор-это мощный итератор, который сохраняет свое состояние. Чтобы узнать больше о генераторах, ознакомьтесь с одной из наших предыдущих статей, Python Generators .

Следующий вариант объединяет метод listdir() модуля os с функцией генератора. Код работает как с версиями 2, так и с версиями 3 Python.

Как вы, возможно, уже отмечали ранее, метод listdir() возвращает список записей для данного каталога. Метод os.path.isfile() возвращает True , если данная запись является файлом. Оператор yield завершает работу функции, но сохраняет текущее состояние и возвращает только имя записи, обнаруженной как файл. Это позволяет нам зацикливаться на функции генератора (см. Листинг 4 ). Выходные данные идентичны выходным данным из Примера 3 .

Листинг 4: Объединение os.listdir() и функция генератора

import os

def files(path):
    for file in os.listdir(path):
        if os.path.isfile(os.path.join(path, file)):
            yield file

for file in files("."):
    print (file)

Используйте path lib

Модуль pathlib описывает себя как способ “Анализировать, строить, тестировать и иным образом работать с именами файлов и путями с использованием объектно-ориентированного API вместо низкоуровневых строковых операций”. Это звучит круто – давайте сделаем это. Начиная с Python 3, модуль относится к стандартному дистрибутиву.

В Листинге 5 мы сначала определяем каталог. Точка (“.”) определяет текущий каталог. Затем метод iterator() возвращает итератор, который выдает имена всех файлов. В цикле for мы печатаем имена файлов один за другим.

Листинг 5: Чтение содержимого каталога с помощью pathlib

import pathlib

# define the path
currentDirectory = pathlib.Path('.')

for currentFile in currentDirectory.iterdir():
    print(currentFile)

Опять же, выходные данные идентичны выходным данным из Примера 3 .

В качестве альтернативы мы можем получить файлы, сопоставив их имена с помощью чего-то под названием glob . Таким образом, мы можем получить только те файлы, которые нам нужны. Например, в приведенном ниже коде мы хотим только перечислить файлы Python в нашем каталоге, что мы и делаем, указывая “*.py” в глобусе.

Листинг 6: Использование pathlib с глоб метод

import pathlib

# define the path
currentDirectory = pathlib.Path('.')

# define the pattern
currentPattern = "*.py"

for currentFile in currentDirectory.glob(currentPattern):
    print(currentFile)

Использование os.scandir()

В Python 3.6 новый метод становится доступным в модуле os . Он называется scandir () и значительно упрощает вызов списка файлов в каталоге.

Сначала импортировав модуль os , используйте метод getcwd() для определения текущего рабочего каталога и сохраните это значение в переменной path . Затем scandir() возвращает список записей для этого пути, который мы тестируем на файл с помощью метода is_file () .

Листинг 7: Чтение содержимого каталога с помощью скандир()

import os

# detect the current working directory
path = os.getcwd()

# read the entries
with os.scandir(path) as listOfEntries:
    for entry in listOfEntries:
        # print all entries that are files
        if entry.is_file():
            print(entry.name)

Опять же, вывод Листинга 7 идентичен выводу из Примера 3 .

Вывод

Существует разногласие, какая версия является лучшей, какая-самой элегантной, а какая-самой “питонической”. Мне нравится простота метода os.walk () , а также использование модулей fnmatch и path lib .

Две версии с процессами/конвейерами и итератором требуют более глубокого понимания процессов UNIX и знаний Python, поэтому они могут быть не лучшими для всех программистов из-за их дополнительной (и ненужной) сложности.

Чтобы найти ответ на вопрос, какая версия является самой быстрой, модуль timeit очень удобен. Этот модуль подсчитывает время, прошедшее между двумя событиями.

Чтобы сравнить все наши решения, не изменяя их, мы используем функциональность Python: вызываем интерпретатор Python с именем модуля и соответствующим кодом Python для выполнения. Для этого для всех скриптов Python сразу помогает shell-скрипт ( Листинг 8 ).

Листинг 8: Оценка времени выполнения с помощью timeit модуль

#! /bin/bash

for filename in *.py; do
    echo "$filename:"
    cat $filename | python3 -m timeit
    echo " "
done

Тесты были проведены с использованием Python 3.5.3. Результат выглядит следующим образом, тогда как os.walk() дает наилучший результат. Запуск тестов с Python 2 возвращает разные значения, но не меняет порядок – os.walk() по-прежнему находится в верхней части списка.

Тесты были проведены с использованием Python 3.5.3. Результат выглядит следующим образом, тогда как || os.walk() || дает наилучший результат. Запуск тестов с Python 2 возвращает разные значения, но не меняет порядок – || os.walk() || по-прежнему находится в верхней части списка. 0,0085 usec на петлю
подпроцесс/труба 0,00859 usec на петлю
подпроцесс/труба 0,00912 usec на петлю
подпроцесс/труба 0,00867 usec на петлю
pathlib 0,00854 usec на петлю
путь lib/glob 0,00858 usec на петлю
путь lib/glob 0,00856 usec на петлю

Признание

Автор хотел бы поблагодарить Герольда Рупрехта за его поддержку и комментарии при подготовке этой статьи.