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

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

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

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

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

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

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

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

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

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

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

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

 $ ls -p . | grep -v /$ 

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

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

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

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

Чтобы прочитать вывод команды ls из конвейера, канал stdin grep определяется как ls.stdout . Наконец, переменная endOfPipe считывает вывод grep из grep.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 
 ... 

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

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

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

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

Наконец, с помощью fnmatch мы фильтруем записи, которые ищем, и выводим соответствующие записи в стандартный вывод. В листинге 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» .

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

Как вы могли заметить ранее, метод 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) 

Используйте pathlib

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

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

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

 import pathlib 
 
 # define the path 
 currentDirectory = pathlib.Path('.') 
 
 for currentFile in currentDirectory.iterdir(): 
 print(currentFile) 

Опять же, результат такой же, как в Примере 3 .

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

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

 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: Чтение содержимого каталога с помощью scandir()

 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 и pathlib

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

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

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

Листинг 8: Оценка времени выполнения с timeit модуля 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() по-прежнему находится наверху списка.

Методика Результат для 100000000 циклов


os.walk 0,0085 мксек на цикл подпроцесс / труба 0,00859 мксек на цикл os.listdir / fnmatch 0,00912 мксек на цикл os.listdir / генератор 0,00867 мксек на цикл pathlib 0,00854 мксек на цикл pathlib / glob 0,00858 мксек на цикл os.scandir 0,00856 мксек на цикл

Благодарности

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

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus