Обход каталогов в Python: что нужно знать заранее
К концу вы сможете рекурсивно пройти по дереву папок, отфильтровать файлы по расширению и не споткнуться о симлинки и права доступа. Займёт минут двадцать.

Что получится и что нужно иметь под рукой
В конце у вас будет скрипт, который проходит по всем подпапкам, собирает нужные файлы и корректно ведёт себя, когда путь недоступен или зациклен через символическую ссылку. Разберём три инструмента стандартной библиотеки — os.walk, pathlib и os.scandir — и покажем, чем они отличаются на практике. Всё работает на Python 3.10 и новее, отдельно отмечу, где поведение менялось.
До начала проверьте версию: python --version. Ничего дополнительно ставить не нужно — все три модуля входят в стандартную поставку. Права на чтение папок, по которым будете ходить, тоже понадобятся: без них скрипт не упадёт, но часть каталогов молча пропустит. Об этом ниже.
Шаг 1. Простой обход через os.walk
Самый привычный способ. os.walk отдаёт по одному кортежу на каждую папку: текущий путь, список подпапок и список файлов.
import os
base_dir = "/home/user/projects"
for current_dir, subdirs, files in os.walk(base_dir):
for file_name in files:
full_path = os.path.join(current_dir, file_name)
print(full_path)Проверка: в консоль построчно посыплются полные пути ко всем файлам внутри base_dir и его подпапок. Если ничего не вывелось — либо папка пуста, либо путь указан неверно.
Как отфильтровать по расширению
Не изобретайте регулярку — хватит проверки суффикса:
import os
base_dir = "/home/user/projects"
for current_dir, subdirs, files in os.walk(base_dir):
for file_name in files:
if file_name.endswith(".py"):
print(os.path.join(current_dir, file_name))Проверка: останутся только пути, заканчивающиеся на .py.
Как пропустить целую ветку дерева
Важная деталь: subdirs — это список, который вы можете менять на месте, и os.walk учтёт изменения. Так пропускают, например, папки виртуального окружения:
import os
base_dir = "/home/user/projects"
skip = {".git", "venv", "__pycache__"}
for current_dir, subdirs, files in os.walk(base_dir):
# правим список in-place, чтобы walk не заходил внутрь
subdirs[:] = [d for d in subdirs if d not in skip]
for file_name in files:
print(os.path.join(current_dir, file_name))Тонкость: срез subdirs[:] = ... обязателен. Если написать subdirs = [...], вы создадите новую локальную переменную, а os.walk продолжит ходить по старому списку и внутрь папок всё равно зайдёт.
Шаг 2. То же самое через pathlib
С Python 3.4 в стандартной библиотеке есть pathlib. Для рекурсивного обхода удобен метод rglob:
from pathlib import Path
base_dir = Path("/home/user/projects")
for path in base_dir.rglob("*.py"):
if path.is_file():
print(path)Проверка: вывод тот же, что у примера с фильтром по .py. Шаблон *.py с rglob означает «во всех подпапках на любой глубине».
Проверка is_file() тут не для красоты: под шаблон может попасть символическая ссылка, ведущая на несуществующий файл, — is_file() отсеет её.
os.walk отдаёт вам структуру дерева и контроль над обходом. pathlib.rglob отдаёт плоский поток путей и лаконичность. Выбор зависит от того, нужно ли вам вмешиваться в сам обход.
Шаг 3. Когда важна скорость — os.scandir
Если файлов десятки тысяч и вы дёргаете размер или тип каждого, os.scandir заметно экономит на системных вызовах: он возвращает объекты DirEntry, у которых тип и метаданные кешируются из одной операции чтения каталога.
import os
def walk_fast(path):
with os.scandir(path) as entries:
for entry in entries:
# follow_symlinks=False — не ходим по ссылкам на папки
if entry.is_dir(follow_symlinks=False):
yield from walk_fast(entry.path)
else:
yield entry.path
for file_path in walk_fast("/home/user/projects"):
print(file_path)Проверка: результат совпадает с ручным обходом дерева. Метод entry.is_dir() не делает лишний вызов stat, если тип уже известен из чтения каталога.
Три подхода рядом
| Инструмент | Что отдаёт | Когда брать |
|---|---|---|
os.walk | Кортеж (папка, подпапки, файлы) | Нужен контроль над обходом, пропуск веток |
pathlib.rglob | Плоский поток Path | Простой сбор файлов по шаблону, читаемость |
os.scandir | DirEntry с кешем метаданных | Много файлов, нужны тип и размер каждого |
Две ловушки, о которых узнают поздно
Симлинки и циклы. По умолчанию os.walk не заходит внутрь символических ссылок на папки (followlinks=False). Если включить followlinks=True — можно получить бесконечный цикл, когда ссылка указывает на родительскую папку. Стандартная библиотека такой цикл сама не разрывает, следить за посещёнными путями придётся вам.
Недоступные папки. Если у процесса нет прав на чтение каталога, os.walk по умолчанию просто пропустит его без ошибки. Чтобы узнать о таких случаях, передайте обработчик:
import os
def report_error(err):
print(f"Не смог прочитать: {err.filename} ({err})")
for current_dir, subdirs, files in os.walk("/some/path", onerror=report_error):
passЕсли не получилось
- FileNotFoundError: [Errno 2] No such file or directory — путь в
base_dirуказан неверно или относительный путь считается не от той папки, где вы думаете. Проверьтеos.getcwd(). - PermissionError: [Errno 13] Permission denied — у
os.scandirиpathlibнедоступный каталог падает с исключением, в отличие от тихогоos.walk. Оберните вызов вtry/exceptили запускайте от пользователя с правами на чтение. - Вывод пустой — при
rglob("*.py")проверьте, что расширение совпадает по регистру: на Linux.PYи.py— разные суффиксы. - Скрипт завис и ест память — почти наверняка вы включили
followlinks=Trueи попали в цикл по симлинку. Уберите флаг или ведите множество посещённых реальных путей черезos.path.realpath. - walk всё равно заходит в venv — вы написали
subdirs = [...]вместоsubdirs[:] = [...]. Список нужно менять на месте.
Что дальше
Когда базовый обход работает, посмотрите на DirEntry.stat() — через него удобно достать размер и время изменения без отдельного os.stat. Если начнёте собирать пути в список на больших деревьях, замерьте память: миллион путей в списке — это сотни мегабайт, генератор (как в примере со scandir) держит нагрузку ровнее. Сравнение подходов под нагрузкой и разбор циклов по симлинкам — отдельная тема, здесь мы только обозначили риск.
Защитник прав потребителей: Досудебные претензии на возврат товара со ссылками на ЗоЗПП.
Спросить за 15 ₽Источники: os.walk — Python documentation, pathlib — Object-oriented filesystem paths
Частые вопросы
У меня Python 3.8, всё это заработает?
Что быстрее — os.walk или os.scandir?
Почему rglob пропустил часть файлов?
Как пройти дерево, не проваливаясь в скрытые папки?
Скрипт молча пропускает каталоги — это нормально?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.