Массовое переименование и сортировка файлов

Разложить хаос из сотен файлов по аккуратным папкам — классическая задача автоматизации. Главное здесь — сначала рассчитать новые имена, и только потом трогать диск.
Суть: переименование = построить новое имя из старого по правилу. Сначала проверьте план на конфликты, потом примените через rename или shutil.move.

Допустим, фотограф сбросил тысячу файлов IMG_3481.jpg, а нужно получить 2026-06-22_отпуск_001.jpg. Или бухгалтер хочет разложить счета по папкам-месяцам. Суть одинакова: для каждого файла мы вычисляем новое имя или новую папку по правилу, а затем перемещаем.

Самое важное — разделить план и исполнение. Сначала постройте словарь «старое имя → новое имя» и проверьте его: нет ли коллизий, когда два файла претендуют на одно имя. И только потом применяйте изменения. Так вы не испортите данные на середине процесса.

Логику построения имён и проверки коллизий можно полностью отработать в браузере — это чистые строки и словари.

Попробуй сам ▶

# План переименования: фото по дате и порядковому номеру
old_names = ['IMG_3481.jpg', 'IMG_3482.jpg', 'IMG_3483.jpg']
date = '2026-06-22'
label = 'otpusk'

plan = {}
for i, old in enumerate(sorted(old_names), start=1):
    ext = old.split('.')[-1]
    new = f'{date}_{label}_{i:03d}.{ext}'
    plan[old] = new

# Проверка коллизий: новых имён должно быть столько же, сколько файлов
targets = list(plan.values())
if len(set(targets)) != len(targets):
    print('ОШИБКА: есть дубли имён, переименование отменено')
else:
    for old, new in plan.items():
        print(f'{old}  ->  {new}')

Формат {i:03d} печатает число с ведущими нулями: 1 превращается в 001. Это гарантирует правильную сортировку файлов — иначе file10 встанет раньше file2.

Когда план проверен, исполнение тривиально. Этот фрагмент трогает диск, поэтому он нерабочий в браузере.

from pathlib import Path
import shutil

folder = Path('photos')
for old, new in plan.items():
    src = folder / old
    dst = folder / new
    if dst.exists():            # не затираем существующий файл
        print('Пропуск:', new)
        continue
    src.rename(dst)             # переименование в той же папке

# Перемещение в подпапку по месяцу:
month_dir = folder / '2026-06'
month_dir.mkdir(exist_ok=True)
shutil.move(str(folder / new), str(month_dir / new))
БЕЗОПАСНОЕ ПЕРЕИМЕНОВАНИЕ

  [1] собрать план   ->  словарь old -> new
         |
  [2] проверить      ->  нет коллизий? цел?
         |
  [3] применить      ->  rename / move
         |
  [4] лог            ->  что и куда переехало

  Никогда не пропускай шаг [2].

Отдельного внимания заслуживает режим «сухого прогона» (dry-run) — приём, который профессионалы применяют к любой массовой операции с файлами. Идея проста: добавьте в скрипт флаг, при котором он не выполняет действия, а лишь печатает, что собирается сделать. Прогнали с флагом, глазами проверили список переименований, убедились, что логика верна — и только потом запустили «по-настоящему». Этот лишний шаг занимает секунды, но именно он отделяет аккуратного инженера от того, кто однажды одной командой перемешал тысячу важных файлов без возможности откатиться.

Как работает под капотом

Path.rename и os.rename на уровне ОС — это почти мгновенная операция: меняется только запись в каталоге, сами байты файла не двигаются. Но это работает лишь в пределах одного диска. shutil.move умнее: если источник и приёмник на разных дисках, он физически копирует файл, а затем удаляет оригинал.

mkdir(exist_ok=True) создаёт папку, но не падает, если она уже есть — это делает скрипт идемпотентным: повторный запуск не вызовет ошибку «папка существует».

Частые ошибки

  • Переименовывать без проверки коллизий. Два файла с одинаковым новым именем — и один безвозвратно затрёт другой.
  • Менять файлы прямо в цикле обхода. Изменение папки во время её перебора ведёт к пропускам. Сначала соберите список, потом действуйте.
  • Забывать ведущие нули. Без них сортировка по имени ломается на числах.

Best practices

  • Сначала постройте и распечатайте план (dry-run), убедитесь глазами, потом применяйте.
  • Проверяйте dst.exists() перед перемещением, чтобы не затирать данные.
  • Логируйте каждое действие — при ошибке вы будете знать, где остановились.

Итоги. Массовое переименование — это план плюс исполнение: построить имена, проверить коллизии, применить через rename или move. Идемпотентность и dry-run спасают данные. Дальше разберём, как делать резервные копии перед опасными операциями.

Проверьте себя
1. Зачем формат {i:03d} при нумерации файлов?
AЧтобы числа были меньше
BЧтобы добавить ведущие нули и сохранить правильную сортировку
CЧтобы ускорить переименование
DЭто требование pathlib
2. Чем shutil.move надёжнее простого rename при переносе между дисками?
AОн работает быстрее на одном диске
BОн копирует файл и удаляет оригинал, если диски разные
CОн не требует прав на запись
DОн сжимает файл при переносе