Как искать файлы в Python: от os.walk до pathlib
Основные способы поиска файлов в Python
В работе с файловым вводом-выводом часто требуется найти определённые файлы по имени, расширению или другим параметрам. Python предоставляет несколько инструментов для решения этой задачи. Рассмотрим наиболее эффективные и популярные из них.
Основной рекомендуемый способ: pathlib.Path.rglob
Модуль pathlib, появившийся в Python 3.4, предлагает объектно-ориентированный интерфейс для работы с путями. Метод rglob рекурсивно обходит все подкаталоги и находит файлы, соответствующие шаблону. Это самый читаемый и современный подход.
from pathlib import Path
# Найти все текстовые файлы в текущем каталоге и подкаталогах
for file in Path('.').rglob('*.txt'):
print(file)
ввод программ на python (ввод данных в программе python)
./data/file1.txt ./data/subdir/notes.txt ./readme.txt
Python file io (ввод-вывод файлов в python)
В результате выводятся относительные пути к каждому найденному файлу. Если требуется получить абсолютные пути, можно вызвать file.resolve().
Типичные проблемы:
- Если каталог не существует, pathlib выбросит исключение FileNotFoundError. Перед поиском можно проверить существование: Path('.').exists().
- При отсутствии прав доступа к некоторым подкаталогам возникнет PermissionError. Для обработки ошибок используйте try-except внутри цикла.
- Символические ссылки по умолчанию обходятся. Если нужно игнорировать их, используйте параметр follow_symlinks=False (доступно в Python 3.12+ через Path.rglob? В текущих версиях нет такого параметра у rglob, но можно отфильтровать позже).
Как выполнить рекурсивный обход папок с помощью os.walk?
Модуль os предоставляет функцию walk, которая проходит по дереву каталогов и возвращает имена файлов и папок на каждом уровне. Это гибкий, но более многословный вариант.
import os
# Найти все .txt файлы
for root, dirs, files in os.walk('.'):
for file in files:
if file.endswith('.txt'):
print(os.path.join(root, file))
Python temp files (временные файлы в python)
./data/file1.txt ./data/subdir/notes.txt ./readme.txt
Python index files (индексация файлов в python)
Функция walk также позволяет изменять список dirs на ходу, чтобы исключать некоторые подкаталоги. Например, для игнорирования папки 'node_modules':
for root, dirs, files in os.walk('.'):
dirs[:] = [d for d in dirs if d != 'node_modules']
# ... остальная логика
File python class (класс для работы с файлами в python)
Возможные ошибки:
- Ошибки доступа к папкам по умолчанию игнорируются, но можно передать аргумент onerror для логирования. Пример: os.walk('.', onerror=lambda e: print('Ошибка:', e)).
- При большом количестве файлов walk может быть медленнее, чем pathlib.rglob, из-за создания множества строк.
Как найти файлы по шаблону с помощью модуля glob?
Модуль glob использует правила подстановки (wildcards), аналогичные командной строке Unix. Для рекурсивного поиска используется шаблон '**' с параметром recursive=True.
import glob
# Плоский поиск: только в текущей папке
print(glob.glob('*.txt'))
# Рекурсивный поиск: все .txt файлы внутри папки
print(glob.glob('**/*.txt', recursive=True))
Python file utf 8 (кодировка utf-8 для файлов в python)
['readme.txt'] ['data\\file1.txt', 'data\\subdir\\notes.txt', 'readme.txt']
Python config files (конфигурационные файлы в python)
На Windows разделители обратные; при желании можно нормализовать с помощью os.path.normpath или Path.
Проблемы:
- Рекурсивный поиск с '**' работает только при указании recursive=True. Если забыть, вернутся только файлы на один уровень.
- Glob не позволяет фильтровать по другим атрибутам, кроме имени. Для более сложной логики потребуется комбинировать с os.path или pathlib.
Как написать собственную рекурсивную функцию поиска файлов?
Иногда требуется полный контроль над процессом поиска. Можно реализовать рекурсивную функцию с использованием os.listdir и os.path.isdir.
import os
def find_files(path, pattern):
result = []
for entry in os.listdir(path):
full = os.path.join(path, entry)
if os.path.isfile(full) and entry.endswith(pattern):
result.append(full)
elif os.path.isdir(full):
result.extend(find_files(full, pattern))
return result
print(find_files('.', '.txt'))
Python copy file (копирование файла в python)
['.\\data\\file1.txt', '.\\data\\subdir\\notes.txt', '.\\readme.txt']
Python log file (логирование в файл в python)
Функция рекурсивно обходит все подпапки. Можно добавить обработку ошибок доступа или игнорирование отдельных папок.
Возможные трудности:
- Глубоко вложенные каталоги могут вызвать RecursionError. Для очень глубоких деревьев лучше использовать итеративный подход с явным стеком (списком).
- Код сложнее поддерживать по сравнению с готовыми решениями.
Как найти файлы в одной папке без рекурсии с pathlib.Path.glob?
Если рекурсия не требуется, метод glob объекта Path находит файлы только в текущем каталоге.
from pathlib import Path
txt_files = list(Path('.').glob('*.txt'))
print(txt_files)
Python file methods (методы работы с файлами в python)
[WindowsPath('readme.txt')]
File models in python (модели файлов в python)
Этот способ прост и быстр, но не заходит в подпапки.
Ограничение:
Подходит только для поиска на одном уровне. Для рекурсии используйте rglob или iteridir с ручной проверкой.
Как применить маску к списку имен с fnmatch.filter?
Функция fnmatch.filter принимает список строк (имен файлов) и возвращает те, которые соответствуют шаблону в стиле Unix. Полезна, когда список уже получен, например, через os.listdir.
import os, fnmatch
files = os.listdir('.')
txt_files = fnmatch.filter(files, '*.txt')
print(txt_files)
File handle python (обработка файлов в python)
['readme.txt']
Python open file read (открытие файла для чтения в python)
Недостатки:
- Работает только с именами, а не полными путями; для рекурсивного поиска требуется комбинировать с os.walk.
- Шаблоны поддерживают *, ?, но не рекурсию **.
Как использовать быстрый сканер os.scandir для рекурсивного поиска?
os.scandir возвращает итератор объектов DirEntry, которые содержат информацию о файле (размер, тип, время и т.д.) без лишних системных вызовов. Для рекурсии нужно вызывать scandir для каждой подпапки.
import os
def scandir_recursive(path, pattern):
for entry in os.scandir(path):
if entry.is_file() and entry.name.endswith(pattern):
yield entry.path
elif entry.is_dir() and not entry.is_symlink():
yield from scandir_recursive(entry.path, pattern)
for p in scandir_recursive('.', '.txt'):
print(p)
Этот подход быстрее os.walk на больших деревьях, так как scandir не собирает все имена сразу.
Проблемы:
- Необходимо вручную обрабатывать ошибки доступа (добавить try-except при entry.is_dir()).
- Не следует бесконечно следовать символическим ссылкам, чтобы избежать циклов. В примере добавлена проверка is_symlink().
Выбор подходящего метода зависит от задачи. Для простого рекурсивного поиска по маске pathlib.rglob является наиболее современным и лаконичным решением. os.walk даёт больше контроля на уровне каталогов, а os.scandir оптимизирован по скорости. Для нерекурсивного поиска удобно использовать glob.glob или Path.glob.
Расширенные примеры с дополнительными критериями
Помимо простого поиска по имени, часто требуется отбирать файлы по размеру, дате изменения, содержимому или другим свойствам. Ниже приведены примеры таких задач.
Поиск файлов больше заданного размера
Можно комбинировать rglob с фильтрацией по размеру (используя st_size из stat).
from pathlib import Path
# Найти все файлы больше 1 МБ (1000000 байт)
large_files = [f for f in Path('.').rglob('*') if f.is_file() and f.stat().st_size > 1_000_000]
for f in large_files[:5]:
print(f, f.stat().st_size)
./data/bigfile.dat 2048576 ./archive.tar.gz 1500000
Обратите внимание на экранирование символа > (больше) в коде. В реальном коде пишется >.
Если файлов очень много, вызов stat для каждого может замедлить процесс. В таких случаях можно использовать os.scandir, который предоставляет статистику без дополнительного вызова.
Поиск файлов по дате последнего изменения
Пример: найти все файлы, изменённые за последние 7 дней.
from pathlib import Path
from datetime import datetime, timedelta
cutoff = datetime.now() - timedelta(days=7)
recent = []
for f in Path('.').rglob('*'):
if f.is_file():
mtime = datetime.fromtimestamp(f.stat().st_mtime)
if mtime > cutoff:
recent.append(f)
for f in recent[:5]:
print(f, mtime)
./log/app.log 2025-03-10 14:23:05 ./tmp/temp.txt 2025-03-11 08:15:00
Аналогично можно искать файлы старше определённой даты, заменив условие на mtime < cutoff.
Поиск по содержимому файла (текстовая строка)
Для поиска файлов, содержащих определённую строку, требуется читать содержимое. Для больших файлов лучше читать построчно.
from pathlib import Path
search_string = 'Error'
for f in Path('.').rglob('*.log'):
try:
with f.open('r', errors='ignore') as fp:
for line in fp:
if search_string in line:
print(f'Найдено в {f}: {line.strip()}')
break
except PermissionError:
print(f'Нет доступа к {f}')
Найдено в ./logs/app.log: Error: connection lost Найдено в ./logs/error.log: Error: timeout
Осторожно: поиск по содержимому может быть очень медленным на большом количестве файлов. Рекомендуется предварительно фильтровать по расширению и размеру.
Многопоточный поиск для ускорения
При поиске по содержимому или при большом количестве файлов можно использовать потоки для параллельной обработки.
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
import os
def search_in_file(path):
if path.is_file() and path.suffix == '.log':
with path.open('r', errors='ignore') as f:
for line in f:
if 'Error' in line:
return path, line.strip()
return None
with ThreadPoolExecutor(max_workers=4) as executor:
futures = {executor.submit(search_in_file, p): p for p in Path('.').rglob('*.log')}
for future in as_completed(futures):
result = future.result()
if result:
print(f'Файл: {result[0]}, строка: {result[1]}')
Файл: ./logs/app.log, строка: Error: connection lost Файл: ./logs/error.log, строка: Error: timeout
Использование потоков может ускорить поиск на дисках с большими задержками, но не увеличивает пропускную способность CPU из-за GIL. Для чисто IO-операций это оправдано.
Игнорирование определённых каталогов (например, .git, node_modules)
Часто нужно исключить из поиска системные или сгенерированные папки. С pathlib это можно сделать с помощью проверки part пути.
from pathlib import Path
ignore_dirs = {'node_modules', '.git', '__pycache__'}
for f in Path('.').rglob('*'):
if any(ign in f.parts for ign in ignore_dirs):
continue
if f.is_file() and f.suffix == '.py':
print(f)
./src/main.py ./tests/test_app.py
Альтернатива с os.walk уже показывает, как модифицировать список dirs.
Поиск символических ссылок
Иногда требуется найти только символические ссылки, а не обычные файлы.
from pathlib import Path
for f in Path('.').rglob('*'):
if f.is_symlink():
print(f'Ссылка: {f} -> {f.resolve()}')
Ссылка: ./link_to_file -> ./real_file.txt
По умолчанию rglob следует симлинкам; если нужно избежать циклов, можно отфильтровать по is_symlink при рекурсии (как в примере с scandir).
Поиск файлов с использованием регулярных выражений
Можно комбинировать rglob с модулем re для более гибкой фильтрации имени.
import re
from pathlib import Path
pattern = re.compile(r'^test_.*\.py$')
for f in Path('.').rglob('*'):
if f.is_file() and pattern.match(f.name):
print(f)
./test_unit.py ./test_integration.py
Вместо pattern.match можно использовать pattern.search для частичного совпадения.
Эти примеры демонстрируют гибкость Python при поиске файлов. Комбинируя разные подходы, можно решать самые разнообразные задачи файлового ввода-вывода.