Мышление пайплайнами: вход — обработка — выход

Любой скрипт автоматизации — это конвейер: данные входят, проходят через цепочку преобразований и выходят в новом виде. Спроектируйте конвейер — и код напишется сам.
Суть: разбейте задачу на функции «прочитать», «обработать», «записать». Каждая делает одно дело, и их легко тестировать и переставлять.

Новички часто пишут автоматизацию одной длинной простынёй кода: тут читаем файл, тут же считаем, тут же отправляем письмо. Такой код невозможно ни понять, ни починить. Профессионал сначала рисует поток данных, а потом превращает каждую стрелку в функцию.

Возьмём типичную задачу: из списка продаж посчитать выручку по менеджерам и отдать отчёт. Поток выглядит так.

ПОТОК ОБРАБОТКИ ДАННЫХ

  read_sales()      обработка        write_report()
  ----------    ----------------    --------------
  список    ->  группируем по   ->  строки отчёта
  сделок        менеджеру, сумма    готовый текст
                |
                +-> validate() отсеять брак

  каждая функция = один блок схемы

Когда поток нарисован, код становится почти механическим переводом схемы. Вот рабочий пример на чистом stdlib — он группирует сделки и считает выручку с помощью collections.

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

from collections import defaultdict

# ВХОД: список сделок (менеджер, сумма)
sales = [
    ('Аня', 1200), ('Боря', 800), ('Аня', 1500),
    ('Вика', 2000), ('Боря', 1100), ('Аня', 300),
]

# ОБРАБОТКА: группируем по менеджеру
revenue = defaultdict(int)
for manager, amount in sales:
    revenue[manager] += amount

# ВЫХОД: отчёт, отсортированный по убыванию
print('Выручка по менеджерам:')
for manager, total in sorted(revenue.items(), key=lambda x: -x[1]):
    print(f'  {manager:6} {total:>6} руб')
print(f'  ИТОГО  {sum(revenue.values()):>6} руб')

Обратите внимание: три комментария-блока (вход / обработка / выход) повторяют схему. Это не случайно — хорошо спроектированный код читается как диаграмма.

Пайплайн-мышление окупается и при отладке. Когда скрипт выдаёт неверный результат, монолитный код приходится перечитывать целиком, гадая, где ошибка. А разбитый на блоки пайплайн отлаживается по шагам: распечатали результат чтения — верно; распечатали результат обработки — вот здесь поехало. Вы локализуете проблему за минуты, а не за час. Тот же принцип помогает и расширять скрипт: новое требование почти всегда ложится в один из блоков, не задевая остальные. Это и есть инженерная зрелость — код, который не страшно менять.

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

defaultdict(int) — это словарь, который для отсутствующего ключа сам создаёт значение по умолчанию (здесь 0). Поэтому revenue[manager] += amount работает даже при первой встрече менеджера: не нужно вручную проверять, есть ли ключ. Это классический приём группировки.

Функция sorted с параметром key сортирует не сами элементы, а результат функции от них. key=lambda x: -x[1] сортирует по сумме (второй элемент пары) в обратном порядке — минус превращает возрастание в убывание.

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

  • Смешивать чтение, расчёт и вывод в одной функции. Тогда нельзя протестировать расчёт, не трогая файлы.
  • Не валидировать вход. Битая строка в данных уронит весь скрипт. Отсеивайте брак на входе.
  • Жёстко зашивать пути и параметры в середину логики. Выносите их наверх, в начало скрипта.

Best practices

  • Одна функция — одна ответственность: читать, обрабатывать или писать.
  • Передавайте данные между функциями, а не глобальные переменные.
  • Сначала схема потока на бумаге, потом код. Так короче и надёжнее.

Итоги. Пайплайн-мышление — главный навык автоматизатора. Разделяйте чтение, обработку и запись на отдельные функции, проектируйте поток данных заранее, валидируйте вход. Теперь, когда фундамент заложен, перейдём к первому реальному источнику данных — файлам и папкам.

Проверьте себя
1. Почему автоматизацию делят на функции read / process / write?
AТак код работает быстрее
BЧтобы каждый блок отвечал за одно дело и легко тестировался
CЭто требование интерпретатора Python
DЧтобы использовать меньше памяти
2. Что делает defaultdict(int) при обращении к новому ключу?
AБросает KeyError
BВозвращает None
CАвтоматически создаёт значение 0
DУдаляет ключ