Python: от Junior до Middle

Курс системно прокачивает навыки Python-разработчика от уверенной базы до уровня Middle. Вы освоите инструменты разработки, работу с данными, ООП, тестирование, асинхронность и практики проектирования и деплоя приложений.

1. База Python: синтаксис, типы данных, функции и модули

База Python: синтаксис, типы данных, функции и модули

Python ценят за читаемость и богатую стандартную библиотеку. Эта статья закладывает фундамент, на котором дальше будем строить навыки junior и постепенно двигаться к уровню middle: уверенная работа с типами данных, функциями, модульностью и стилем кода.

Полезные официальные источники:

  • Учебник Python
  • Встроенные типы данных
  • PEP 8 — стиль кода
  • Синтаксис и базовые правила

    Отступы и блоки

    В Python блоки кода задаются отступами, а не фигурными скобками.

    Правило: внутри одного проекта используйте один стиль отступов (обычно 4 пробела).

    Комментарии и документация

  • Однострочный комментарий: # комментарий
  • Многострочную документацию для функций и модулей пишут в docstring (тройные кавычки).
  • Переменные и присваивание

    Python — язык с динамической типизацией: тип хранится у значения, а не у имени переменной.

    При этом Python — язык со строгой типизацией: он не будет автоматически складывать строку и число.

    !Диаграмма связывания имён и объектов, чтобы понять, почему мутабельные объекты могут «меняться сразу в нескольких местах»

    Операторы (коротко)

  • Арифметика: +, -, , /, //, %, *
  • Сравнение: ==, !=, <, <=, >, >=
  • Логика: and, or, not
  • Встроенные типы данных

    Скаляры: числа, булевы значения, строки, None

    Основные типы:

    | Тип | Пример | Особенности | |---|---|---| | int | 42 | Целые числа произвольной точности | | float | 3.14 | Вещественные числа с ограниченной точностью | | bool | True, False | Подтип int: True == 1, False == 0 | | str | "hello" | Неизменяемая последовательность Unicode-символов | | NoneType | None | Отсутствие значения (часто: “не задано”) |

    Строки часто форматируют через f-строки:

    Коллекции: list, tuple, dict, set

    | Тип | Пример | Упорядоченность | Изменяемость | Типичный кейс | |---|---|---|---|---| | list | [1, 2, 3] | Да | Да | Набор элементов, который нужно менять | | tuple | (1, 2) | Да | Нет | Фиксированная структура (например, координаты) | | dict | {"a": 1} | Да (по вставке) | Да | Отображение ключ → значение | | set | {1, 2, 3} | Нет | Да | Уникальные элементы, проверки принадлежности |

    #### Индексация и срезы

    Работают у последовательностей (str, list, tuple).

    #### Мутабельность и частые ошибки

    Списки и словари изменяемые.

    Это не “магия”, а следствие того, что a и b ссылаются на один объект.

    #### Приведение типов

    Иногда нужно преобразовать тип явно:

    Truthy/Falsy: как Python понимает “истину”

    В условиях (if, while) Python приводит значения к bool по правилам:

  • Falsy: False, None, 0, 0.0, "", [], {}, set()
  • Почти всё остальное — truthy
  • Управляющие конструкции

    Ветвления

    Циклы

    for итерируется по последовательности или любому итерируемому объекту.

    while работает, пока условие истинно.

    Полезные управляющие слова:

  • break — прервать цикл
  • continue — перейти к следующей итерации
  • Генераторы коллекций (comprehensions)

    Это компактный способ создавать списки/множества/словари.

    Функции

    Функции позволяют:

  • Убирать дублирование
  • Декомпозировать задачу
  • Изолировать ответственность и упростить тестирование
  • Объявление и возврат значения

    Если return не указан, функция возвращает None.

    Параметры: позиционные, именованные, значения по умолчанию

    Важное правило: значения по умолчанию вычисляются один раз при определении функции. Поэтому нельзя использовать мутабельные значения по умолчанию (например, []).

    Правильно:

    args и *kwargs

  • *args собирает лишние позиционные аргументы в кортеж
  • **kwargs собирает лишние именованные аргументы в словарь
  • Область видимости (scope)

    Переменные внутри функции — локальные.

    Аннотации типов (для читаемости)

    Аннотации помогают IDE и линтерам, но сами по себе не “заставляют” Python проверять типы во время выполнения.

    Документация по аннотациям: Модуль typing

    Ошибки и исключения (база)

    Когда что-то идёт не так, Python выбрасывает исключение (например, ValueError, TypeError, KeyError). Их можно обработать.

    Рекомендация: ловите конкретные исключения, а не просто except Exception без необходимости.

    Модули и импорт

    Что такое модуль

    Модуль — это файл *.py. Его можно импортировать и переиспользовать код.

    Пример структуры:

    Использование:

    Варианты импорта

    Рекомендация: избегайте from module import *, потому что это ухудшает читаемость и может перезаписывать имена.

    Пакеты

    Пакет — это директория с Python-кодом, которую Python воспринимает как набор модулей. В современном Python пакет может быть и без __init__.py, но в реальных проектах он часто присутствует.

    !Дерево проекта и точки импорта, чтобы увидеть разницу между модулем и пакетом

    __name__ == "__main__"

    Этот блок позволяет запускать модуль как скрипт и при этом безопасно импортировать его в другие места.

    Стандартная библиотека

    Python поставляется с множеством модулей “из коробки”. Часто используемые:

  • pathlib — работа с путями
  • datetime — даты и время
  • json — JSON
  • re — регулярные выражения
  • Каталог модулей: Стандартная библиотека Python

    Стиль кода: минимум, который нужен сразу

    Хороший стиль ускоряет ревью и снижает количество ошибок.

  • Имена переменных и функций: snake_case
  • Имена классов: PascalCase
  • Константы: UPPER_CASE
  • Отступы: 4 пробела
  • Длина строки: часто ориентируются на 79–88 символов (зависит от команды)
  • Основные рекомендации: PEP 8 — стиль кода

    Итоги

    Теперь у вас есть база:

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

    2. Структуры данных и алгоритмическое мышление: сложность и паттерны

    Структуры данных и алгоритмическое мышление: сложность и паттерны

    После базы синтаксиса, типов и функций из предыдущей статьи важно научиться думать алгоритмически: выбирать структуру данных под задачу и заранее оценивать, насколько решение будет быстрым и “дешёвым” по памяти. Это один из ключевых навыков перехода от junior к middle.

    Полезные источники:

  • Big O notation
  • collections — Container datatypes
  • bisect — Array bisection algorithm
  • heapq — Heap queue algorithm
  • timeit — Measure execution time of small code snippets
  • Алгоритмическое мышление: что именно мы “оптимизируем”

    В большинстве практических задач вы оптимизируете одно из двух:

  • Время: сколько операций выполняется при росте входных данных
  • Память: сколько дополнительной памяти нужно для решения
  • Алгоритмическое мышление — это привычка:

  • чётко формулировать вход и выход
  • выделять “ядро” задачи (что повторяется много раз)
  • выбирать структуру данных так, чтобы операции в “ядре” были дешёвыми
  • !Интуитивный график, показывающий как разные классы сложности растут с увеличением размера входа

    Сложность: как читать , и почему это важно

    Асимптотическая сложность показывает, как растут затраты при увеличении размера входа.

  • — размер входных данных (например, длина списка)
  • — время почти не зависит от (например, доступ к элементу по индексу)
  • — время растёт примерно линейно (например, один проход по списку)
  • — обычно это вложенные циклы по одному и тому же набору данных
  • — рост очень медленный (часто связан с бинарным поиском)
  • Важно: Big O — это про порядок роста, а не про “миллисекунды”. В реальном коде константы тоже важны, но Big O помогает выбрать правильный подход до микроскопической оптимизации.

    Частая ловушка: “в среднем быстро” и амортизация

    Некоторые операции бывают:

  • обычно быстрыми
  • иногда дорогими, но редко
  • Например, list.append в Python обычно работает за амортизированное : иногда список расширяет внутренний буфер, и конкретный append может быть дороже, но в среднем на больших данных это ведёт себя как .

    Структуры данных в Python: что выбирать под задачу

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

    Базовые коллекции и типичные сложности

    | Структура | Когда использовать | Доступ | Поиск элемента | Вставка/удаление в конец | Вставка/удаление в начало/середину | |---|---|---:|---:|---:|---:| | list | упорядоченные данные, частый проход, индексы | по индексу | | амортизированное (append) | из-за сдвигов | | tuple | “запись” фиксированной структуры | | | нет мутации | нет мутации | | dict | отображение ключ → значение | в среднем | в среднем | в среднем | в среднем | | set | уникальные элементы, проверки принадлежности | нет индекса | в среднем (in) | в среднем | в среднем |

    Пояснение “в среднем”: у dict и set операции обычно , потому что они построены на хеш-таблицах. В редких случаях возможны деградации, но для прикладного кода это стандартный выбор.

    Очередь и стек: collections.deque

    Если вам нужно быстро добавлять и извлекать элементы с обоих концов, используйте collections.deque.

  • deque.append и deque.popleft обычно работают быстро
  • если пытаться делать очередь на list, то pop(0) будет из-за сдвига всех элементов
  • Бинарный поиск по отсортированному массиву: bisect

    Если у вас отсортированный список и нужна быстрая проверка/позиция вставки, используйте бинарный поиск.

  • поиск позиции:
  • вставка в список по позиции: (сдвиги)
  • Вывод: bisect ускоряет поиск места, но не отменяет стоимость вставки в list.

    Приоритетная очередь: heapq

    Когда нужно много раз получать минимальный или максимальный элемент (например, “топ-N” или планирование задач), полезна куча.

  • извлечение минимума:
  • добавление:
  • Как “видеть” сложность в коде

    Признаки

  • один цикл по коллекции
  • один проход по строке
  • один раз построили словарь счётчиков
  • Признаки

  • вложенный цикл по одной и той же коллекции
  • для каждого элемента делаем поиск по списку
  • Оптимизация через set часто превращает в :

    Паттерны решения задач

    Паттерн — это узнаваемая схема, которая часто повторяется. Это ускоряет решение задач на собеседованиях и в реальной разработке.

    Hash map / set для “быстрого поиска”

    Сигнал, что нужен dict или set:

  • “проверить, встречалось ли уже”
  • “посчитать частоты”
  • “найти пару/группу по условию”
  • Пример: частоты слов.

    Если не хотите Counter, можно обычным dict:

    Two pointers: два указателя

    Используется, когда данные линейные (массив/строка) и нужно искать диапазоны или пары.

    Типовые задачи:

  • “найти пару в отсортированном массиве”
  • “удалить элементы по условию на месте”
  • “сжать последовательность”
  • Пример: найти, есть ли в отсортированном списке пара с суммой target.

    Почему это быстро: оба указателя за весь алгоритм двигаются не более чем на шагов, итого .

    Sliding window: скользящее окно

    Сигнал:

  • “самая длинная/короткая подстрока/подмассив”
  • “подмассив с ограничением”
  • “поддерживать состояние для текущего диапазона”
  • Мини-пример: максимальная сумма подмассива фиксированной длины k.

    Наивное решение пересчитывало бы сумму окна каждый раз за , превращая задачу в . Скользящее окно даёт .

    Stack: стек для “вложенности” и “отката”

    Сигнал:

  • скобки и корректность последовательности
  • вычисление выражений
  • “ближайший больший/меньший элемент”
  • Пример: проверка корректности скобок.

    BFS/DFS: графовый способ мышления

    Даже если у вас “не граф”, часто он прячется:

  • дерево каталогов
  • связи между объектами
  • лабиринт (клетки как вершины)
  • Идея:

  • DFS (глубина): идём “вглубь”, удобно для обходов и поиска пути с возвратом
  • BFS (ширина): идём “слоями”, удобно для поиска кратчайшего пути в невзвешенном графе
  • Мини-пример BFS по сетке (идея очереди):

    Как проверять гипотезы про скорость: timeit

    Интуиция важна, но иногда два “одинаково асимптотических” решения отличаются в разы.

    Модуль timeit позволяет аккуратно измерять небольшие фрагменты кода.

    Правило: сначала выбирайте правильную структуру данных и сложность, а потом измеряйте и полируйте.

    Итоги

    Теперь у вас есть практический минимум для алгоритмического мышления:

  • как интерпретировать , , и почему это влияет на архитектуру кода
  • какие структуры данных Python использовать под типовые операции
  • основные паттерны: set/dict для быстрых проверок, two pointers, sliding window, stack, BFS/DFS
  • как подтверждать предположения о скорости через timeit
  • Дальше в курсе эти навыки будут постоянно использоваться: в работе с файлами и данными, в проектировании модулей и в более “прикладных” задачах, где производительность и читаемость должны быть сбалансированы.

    3. ООП и dataclasses: проектирование классов и архитектурные принципы

    ООП и dataclasses: проектирование классов и архитектурные принципы

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

    ООП в Python не про “писать классы ради классов”, а про то, чтобы:

  • упаковывать данные вместе с поведением
  • защищать инварианты (правила, которые всегда должны быть истинными)
  • отделять детали реализации от интерфейса
  • упрощать тестирование и развитие проекта
  • Полезные официальные источники:

  • Классы в учебнике Python
  • Модуль dataclasses
  • abc — абстрактные базовые классы
  • typing — поддержка аннотаций типов
  • PEP 557 — Data Classes
  • Когда ООП действительно полезно

    ООП особенно хорошо “ложится” на задачи, где есть сущности и правила:

  • заказы, платежи, пользователи, подписки
  • документы с состояниями (черновик, опубликован)
  • операции с ограничениями (например, баланс не может быть отрицательным)
  • В таких задачах классы помогают держать поведение рядом с данными, а не размазывать его по функциям и словарям.

    Связь с предыдущими темами курса:

  • из статьи про структуры данных вы уже знаете цену операций list/dict/set; в ООП это превращается в осознанный выбор внутреннего представления
  • из статьи про функции и модули вы уже умеете декомпозировать; классы становятся ещё одним инструментом модульности
  • !Как классы дополняют модульность: модели хранят инварианты, сервисы описывают сценарии

    Базовая анатомия класса

    Атрибуты и методы

    Ключевые идеи:

  • self — ссылка на текущий объект
  • __init__ — инициализация (не “конструктор” в смысле создания, объект уже создан)
  • объект хранит состояние в атрибутах (self.value)
  • Методы класса и статические методы

  • @classmethod получают cls и часто используются как альтернативные конструкторы
  • @staticmethod не получают ни self, ни cls; обычно это признак, что функцию можно вынести из класса, но иногда так удобнее группировать утилиты
  • Свойства: @property для контроля доступа

    В Python нет “настоящих приватных полей” как в некоторых языках, но есть соглашения:

  • публичное: name
  • “внутреннее”: _name (не трогать снаружи без необходимости)
  • имя с “манглингом”: __name (механизм против случайных конфликтов, а не безопасность)
  • @property позволяет оставить внешний интерфейс простым, но добавить проверку.

    Зачем это нужно:

  • объект сам охраняет свои инварианты
  • вызывающий код не обязан помнить все ограничения
  • Специальные методы: “поведение как у встроенных типов”

    Python-объекты становятся удобными, когда они поддерживают стандартные протоколы.

    Часто используемые методы:

  • __repr__ — представление для отладки (желательно однозначное)
  • __str__ — человекочитаемый вывод
  • __eq__ — сравнение
  • __hash__ — хеширование (важно для ключей dict и элементов set)
  • Пример:

    Почему это связано с предыдущей статьёй про структуры данных:

  • если объект должен быть ключом в dict или лежать в set, ему нужна стабильная логика __hash__
  • если объект изменяемый, хешировать его опасно: изменение полей может “сломать” структуру dict/set
  • Наследование и полиморфизм: применять осторожно

    Наследование

    Наследование полезно, когда выполняются оба условия:

  • “является” (is-a) действительно верно по смыслу
  • базовый класс задаёт общий интерфейс и контракт
  • Частая ошибка — использовать наследование ради переиспользования кода. В Python очень часто лучше работает композиция.

    Композиция

    Композиция — это “содержит” (has-a): объект хранит внутри другой объект и делегирует часть работы.

    Пример: заказ содержит список позиций.

    Композиция помогает:

  • менять детали реализации без ломки иерархий
  • снижать связанность
  • Абстрактные базовые классы: фиксируем интерфейс

    Если вам важно зафиксировать контракт (особенно в команде), используйте abc.

    Идея полиморфизма: код работает с интерфейсом Notifier, не зная конкретную реализацию.

    dataclasses: меньше “шумного” кода, больше смысла

    dataclasses решают типичную боль Python-классов: много однотипного кода для хранения данных.

    Минимальный пример

    После этого Python автоматически создаст:

  • __init__ с параметрами id и email
  • __repr__ удобный для отладки
  • __eq__ (по полям, в порядке объявления)
  • Значения по умолчанию и default_factory

    Нельзя задавать изменяемые значения напрямую как дефолт (по той же причине, что и у аргументов функций): объект будет разделяться.

    Плохо:

    Правильно:

    frozen=True: иммутабельные “value objects”

    Иногда объект лучше сделать неизменяемым: например, деньги, координаты, идентификаторы.

    Плюсы:

  • проще рассуждать о состоянии
  • такие объекты часто безопасно использовать в set и как ключи dict
  • slots=True: экономия памяти и защита от случайных атрибутов

    Если у вас много однотипных объектов (тысячи и больше), slots=True может заметно снизить расход памяти и ускорить доступ к атрибутам.

    __post_init__: валидация и производные поля

    dataclass генерирует __init__, но вы можете добавить логику после инициализации.

    Архитектурные принципы: как проектировать классы, чтобы код жил долго

    Ниже — практичные принципы, которые удобно применять даже в небольших проектах.

    Единая ответственность

    Класс должен отвечать за одну “роль”. Если класс одновременно:

  • валидирует вход
  • ходит в сеть
  • сохраняет в базу
  • форматирует вывод
  • то он быстро превратится в “комбайн”, который трудно тестировать.

    Практика:

  • модель хранит данные и инварианты
  • сервис выполняет сценарий (use case)
  • инфраструктура общается с внешним миром (файлы, HTTP, БД)
  • Открытость для расширения

    Старайтесь добавлять новое поведение через новые классы/функции, а не через постоянное переписывание старых.

    Типовой приём:

  • есть интерфейс Notifier
  • добавляем TelegramNotifier, не трогая код, который ожидает Notifier
  • “Композиция вместо наследования”

    Если вы хотите “добавить возможность” (логирование, кеширование, стратегию расчёта), чаще безопаснее:

  • передать зависимость объектом
  • хранить объект внутри
  • делегировать вызов
  • Это уменьшает вероятность проблем с иерархиями и super().

    !Полиморфизм через интерфейс и композиция внутри доменной модели

    Практический пример: “заказ” как модель + сервис как сценарий

    Ниже пример, где dataclass описывает данные, а сервис — бизнес-операцию.

    Что здесь важно с точки зрения middle-подхода:

  • Order сам защищает свои правила (нельзя подтверждать пустой заказ)
  • OrderService описывает сценарий и использует зависимость notifier
  • для тестов можно подставить “фейковый” notifier, не трогая модель
  • Когда НЕ стоит делать класс

    Иногда класс ухудшает код. Хорошие сигналы “оставить функцией”:

  • нет состояния, просто преобразование данных
  • функция чистая и легко тестируется
  • не появляется инвариантов и жизненного цикла
  • Тогда лучше сделать модуль с функциями, как вы уже делали в теме про модули.

    Итоги

    Вы получили рабочий набор инструментов проектирования:

  • как устроены классы и зачем нужны @property, __repr__, __eq__
  • почему наследование стоит использовать осторожно, а композиция часто проще
  • как фиксировать контракт через abc
  • как dataclasses помогают писать меньше шаблонного кода и аккуратнее работать с данными
  • как разделять модель (инварианты) и сервис (сценарии), чтобы проект был расширяемым
  • Дальше эти идеи пригодятся при работе с вводом-выводом, файлами, API, базами данных и при построении более крупной архитектуры, где классы становятся “скелетом” системы.

    4. Работа с файлами, JSON, CSV и базами данных: SQL и ORM

    Работа с файлами, JSON, CSV и базами данных: SQL и ORM

    Большая часть “прикладного” Python-кода живёт на границе: читает данные (файлы, сеть, БД), валидирует и преобразует их, а потом сохраняет обратно. Переход от junior к middle во многом состоит в том, чтобы уверенно работать с вводом-выводом, понимать где возможны ошибки и как проектировать код так, чтобы он был тестируемым и расширяемым.

    Связь с предыдущими темами курса:

  • из статьи про базу Python вы уже знаете, как устроены модули, исключения и контекстные менеджеры на уровне идеи; здесь мы закрепим это на реальном I/O
  • из статьи про структуры данных и сложность вы уже понимаете, почему важно читать файлы потоково, а не “всё в память”
  • из статьи про ООП и dataclasses вы уже умеете описывать модели предметной области; здесь мы покажем, как эти модели живут в БД и сериализуются в JSON
  • Полезные источники:

  • pathlib — Object-oriented filesystem paths
  • Built-in open()
  • json — JSON encoder and decoder
  • csv — CSV File Reading and Writing
  • sqlite3 — DB-API 2.0 interface for SQLite databases
  • SQLAlchemy Documentation
  • !Общая картина: от внешних форматов к моделям и к хранению

    Работа с файлами

    Пути и структура проекта

    Для путей в современных проектах удобно использовать pathlib.Path: меньше строковых склеек, больше ясности.

    Практика уровня middle: держать пути “на краю” приложения. Не смешивайте бизнес-логику с тем, где именно лежат файлы.

    Чтение и запись: текст и байты

    open() умеет работать в текстовом и бинарном режимах.

  • текстовый режим: "r", "w", "a"
  • бинарный режим: "rb", "wb"
  • В тексте ключевые параметры:

  • encoding (почти всегда явно "utf-8")
  • newline (важно для CSV)
  • Path.read_text и Path.write_text хороши для небольших файлов. Для больших файлов используйте потоковое чтение.

    Контекстный менеджер: почему нужен with

    I/O может падать, а файл нужно закрывать всегда. Поэтому используйте with.

    with гарантирует закрытие файла даже если внутри произошла ошибка.

    Потоковая обработка и память

    Частая ошибка junior: читать большой файл целиком через f.read() и потом обрабатывать. Для больших данных лучше обрабатывать построчно.

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

    Частые ошибки и как их обрабатывать

  • FileNotFoundError: файл не найден
  • PermissionError: нет прав
  • UnicodeDecodeError: неверная кодировка
  • Общая рекомендация: ловите конкретные исключения и добавляйте контекст.

    Атомарная запись (когда важно не потерять данные)

    Если вы перезаписываете файл и процесс может упасть посередине, полезна стратегия: писать во временный файл и затем заменить.

    JSON: обмен данными и сериализация

    JSON используют для конфигов, логов, API, интеграций. Это текстовый формат, похожий на структуры Python, но с ограничениями.

    Соответствие типов:

    | JSON | Python | |---|---| | object | dict | | array | list | | string | str | | number | int или float | | true/false | bool | | null | None |

    Чтение и запись JSON

  • json.load(f) читает JSON из файла
  • json.loads(s) читает JSON из строки
  • json.dump(obj, f) пишет JSON в файл
  • json.dumps(obj) пишет JSON в строку
  • Параметры, которые обычно нужны:

  • ensure_ascii=False, чтобы кириллица не превращалась в \uXXXX
  • indent=2, чтобы файл был читаем человеком
  • JSON и dataclasses: как не превращать код в “словари повсюду”

    Хорошая практика: на границе преобразовывать JSON в типизированные модели, а внутри приложения работать с ними.

    Обратите внимание: здесь уже появляется валидация и приведение типов, а не “как пришло, так и живём”.

    Что JSON не умеет “из коробки”

    JSON не знает про:

  • datetime
  • Decimal
  • байты
  • Поэтому для таких полей обычно делают явный контракт, например хранить дату как ISO-строку.

    CSV: табличные данные, импорты и экспорты

    CSV часто используют для выгрузок, отчётов и массовых импортов. Важная деталь: CSV это не “строки через запятую”, а формат с диалектами (разделитель, кавычки, экранирование).

    Чтение CSV

    Для корректной работы используйте newline="" при открытии файла. Это рекомендация документации модуля csv.

    CSV почти всегда читает значения как строки. Дальше вы решаете, где приводить типы и как валидировать.

    Запись CSV

    Большие CSV: потоковый подход

    Если файл большой:

  • не накапливайте все строки в список
  • обрабатывайте итеративно
  • пишите результат также итеративно
  • Это напрямую связано с алгоритмическим мышлением из предыдущей статьи: вы избегаете лишней памяти.

    Базы данных и SQL: когда файлов уже недостаточно

    Файлы хороши для небольших задач, но БД выигрывает, когда нужны:

  • конкурентный доступ (несколько процессов)
  • надёжность и транзакции
  • индексы и быстрые запросы
  • связи между сущностями
  • Минимальная терминология

  • таблица: набор строк одной структуры
  • строка: одна запись
  • столбец: поле записи
  • первичный ключ: уникальный идентификатор строки (часто id)
  • индекс: структура для ускорения поиска по столбцу
  • транзакция: группа операций, которая выполняется целиком или не выполняется вовсе
  • SQLite как “тренажёр” SQL

    SQLite встроен в Python, не требует отдельного сервера и подходит для обучения и небольших приложений.

    Запросы и параметры: защита от SQL-инъекций

    Никогда не собирайте SQL через конкатенацию строк с пользовательским вводом.

    Правильно: используйте параметры.

    Здесь ? это плейсхолдер, а второй аргумент это кортеж параметров.

    Чтение данных и удобные строки

    По умолчанию SQLite возвращает кортежи. Можно включить row_factory, чтобы получать объекты “как словарь”.

    Транзакции: commit и rollback

    Транзакции важны, когда вы делаете несколько изменений и хотите, чтобы они применились вместе.

  • старт транзакции в SQLite происходит автоматически, когда вы меняете данные
  • commit() фиксирует изменения
  • rollback() откатывает изменения
  • Практика: группируйте пакетные операции в транзакции, это и надёжнее, и часто быстрее.

    ORM: когда SQL “слишком низкоуровневый”

    ORM (Object-Relational Mapping) позволяет описывать таблицы как классы и работать с ними через объекты. Это не магия, а слой, который:

  • помогает строить запросы безопасно
  • управляет транзакциями через сессию
  • связывает таблицы отношениями
  • Но важно помнить: ORM не отменяет знание SQL. На уровне middle ожидается умение понимать, какой SQL будет выполнен.

    SQLAlchemy: минимальный пример

    Ниже пример “в стиле SQLAlchemy 2.0” (концептуально; детали зависят от версии и настроек проекта).

    Что важно понять:

  • engine управляет подключением к БД
  • Session это unit-of-work: вы накапливаете изменения и затем фиксируете commit()
  • ограничения (unique, nullable) описывают контракт на уровне схемы
  • Где ORM помогает, а где мешает

    ORM хорошо подходит, когда:

  • доменная модель сложная, есть связи и правила
  • важна поддерживаемость и единый стиль доступа к данным
  • вы хотите переиспользовать модели в сервисах и тестах
  • Часто разумнее использовать чистый SQL, когда:

  • нужен сложный аналитический запрос
  • важна максимальная предсказуемость и производительность
  • проще написать один SELECT руками, чем “собирать” его через ORM
  • Практика уровня middle: выбирать инструмент под задачу, а не использовать ORM “везде по умолчанию”.

    Миграции схемы

    Схема БД со временем меняется. В реальных проектах миграции делают инструментами (для SQLAlchemy обычно используют Alembic), а не ручным редактированием таблиц.

    Идея миграций:

  • схема версионируется
  • изменения применяются последовательно
  • любой разработчик и любой стенд приходят к одинаковому состоянию
  • Небольшой сквозной пример: CSV → SQLite → JSON

    Сценарий:

  • читаем CSV с пользователями
  • валидируем и приводим типы
  • сохраняем в SQLite
  • выгружаем результат в JSON
  • Почему это решение похоже на middle-подход:

  • преобразование в User происходит сразу на границе, дальше не “плавают” словари
  • SQL использует параметры, а не конкатенацию строк
  • executemany делает пакетную вставку
  • чтение/запись разделены на функции с понятной ответственностью
  • Итоги

    Теперь у вас есть практический набор для работы с данными “снаружи”:

  • файлы: pathlib, open, with, кодировки, потоковая обработка
  • JSON: load/loads, dump/dumps, контракт типов, сериализация моделей
  • CSV: DictReader/DictWriter, важность newline="", потоковый импорт
  • SQL: базовые CRUD-запросы, параметры, транзакции, особенности SQLite
  • ORM: зачем нужен, как выглядит минимальный цикл engineSessioncommit, и когда лучше писать SQL руками
  • Это фундамент для следующего уровня: построения приложений, которые уверенно хранят данные, управляют схемой и сохраняют читаемую архитектуру.

    5. Инструменты разработки: Git, виртуальные окружения, линтеры, форматирование

    Инструменты разработки: Git, виртуальные окружения, линтеры, форматирование

    К уровню middle обычно относят не только знание синтаксиса, структур данных, ООП и работы с файлами/БД, но и умение делать воспроизводимую разработку: чтобы проект одинаково собирался на разных машинах, изменения были безопасными, а качество кода контролировалось автоматически.

    Эта статья связывает предыдущие темы курса с реальной практикой разработки:

  • после модульности и архитектуры из ООП-части важно поддерживать стабильную структуру проекта и контракты
  • после темы I/O и БД важно уметь воспроизводимо ставить зависимости и запускать проект в “чистом” окружении
  • после алгоритмического мышления важно уметь быстро находить регрессии и понимать, что именно изменилось
  • Полезные источники:

  • Pro Git (книга)
  • Документация Git
  • venv — создание виртуальных окружений
  • pip — установка пакетов
  • Python Packaging User Guide
  • Ruff (линтер и форматирование)
  • Black (форматтер)
  • isort (сортировка импортов)
  • pre-commit (хуки)
  • mypy (проверка типов)
  • !Общая картина, как инструменты качества и Git связываются в один поток

    Git как основа командной работы

    Git решает две задачи:

  • хранит историю изменений и позволяет безопасно экспериментировать
  • помогает нескольким людям работать над одним кодом без хаоса
  • Репозиторий, коммиты и индекс

    Ключевые понятия:

  • репозиторий хранит историю
  • рабочее дерево это ваши файлы на диске
  • индекс (staging area) это “корзина” того, что попадёт в следующий коммит
  • коммит это снимок изменений с сообщением
  • Минимальный цикл:

    Что коммитить и как писать сообщения

    Практики, которые повышают качество истории:

  • делайте коммиты небольшими и логически цельными
  • пишите сообщения так, чтобы было понятно что изменилось и зачем
  • не коммитьте секреты и локальный мусор
  • Пример структуры сообщения:

  • Add user model validation
  • Fix CSV import for empty lines
  • .gitignore: защита от мусора и утечек

    В Python-проектах обычно игнорируют:

  • виртуальные окружения: .venv/
  • кэш и байткод: __pycache__/, *.pyc
  • артефакты тестов: .pytest_cache/
  • локальные базы: .sqlite3, .db (зависит от проекта)
  • переменные окружения: .env
  • Удобно начинать с шаблонов:

  • gitignore templates
  • Ветки и pull request мышление

    Базовый командный процесс часто выглядит так:

  • main или master хранит стабильную версию
  • вы создаёте ветку под задачу
  • делаете изменения, коммиты, открываете pull request
  • Типовые команды:

    Merge и rebase: в чём разница на практике

  • merge сохраняет “ветвление” истории и добавляет merge-коммит
  • rebase переносит ваши коммиты поверх новой базы и делает историю линейной
  • Практическая рекомендация для роста до middle:

  • используйте rebase для обновления своей ветки перед PR, если это принято в команде
  • не делайте rebase публичных веток, которыми пользуются другие
  • Полезные команды для реальной жизни

  • быстро спрятать незавершённую работу:
  • посмотреть, что менялось построчно:
  • понять, где появилась ошибка в истории (бинарный поиск по коммитам):
  • Виртуальные окружения и зависимости

    Python-проекты часто ломаются не из-за кода, а из-за окружения: разные версии пакетов, разные интерпретаторы, конфликтующие зависимости.

    Виртуальное окружение решает это, изолируя зависимости проекта.

    venv: встроенный способ

    Создание окружения:

    Активация:

  • macOS/Linux:
  • Windows PowerShell:
  • Проверка:

    Установка зависимостей:

    Зачем всегда писать python -m pip

    Рекомендация python -m pip уменьшает риск поставить пакет “не туда”, потому что pip будет запущен именно тем интерпретатором Python, который активен в окружении.

    Фиксация зависимостей

    Для воспроизводимости нужно фиксировать зависимости. Самый простой подход:

    А установка на другой машине:

    Важно понимать ограничение: pip freeze фиксирует все транзитивные зависимости. В небольших учебных проектах это нормально, но в более серьёзных командах часто используют инструменты управления зависимостями и lock-файлы. Общие принципы и стандарты описаны в:

  • Python Packaging User Guide
  • Переменные окружения и секреты

    Не храните секреты в репозитории. Типовой подход:

  • конфигурация читается из переменных окружения
  • локально вы можете использовать файл .env, но сам файл не коммитится
  • На уровне практики:

  • добавьте .env в .gitignore
  • храните пример: .env.example (без секретов)
  • Линтеры, форматирование и проверка типов

    Здесь цель не в том, чтобы “угодить инструментам”, а в том, чтобы:

  • сделать код единообразным
  • находить ошибки раньше запуска
  • упростить ревью и сопровождение
  • Форматтер: единый стиль без споров

    Форматтер автоматически приводит код к согласованному стилю. Это снимает постоянные обсуждения на ревью.

    Популярный вариант:

  • Black: Black documentation
  • Типичный запуск:

    Альтернатива, которая объединяет много функций в одном инструменте:

  • Ruff: Ruff documentation
  • Линтер: ошибки и потенциальные баги

    Линтер проверяет код на:

  • потенциальные ошибки (например, неиспользуемые переменные)
  • подозрительные конструкции
  • стиль и соглашения
  • Пример с Ruff:

    Часто удобно сразу и исправлять то, что линтер умеет чинить автоматически:

    Сортировка импортов

    Импорты быстро превращаются в хаос, особенно в проектах с модулями (тема из первой статьи). Инструменты поддерживают порядок автоматически.

    Пример:

  • isort: isort documentation
  • Проверка типов: mypy

    Аннотации типов из базовой статьи становятся полезнее, когда вы начинаете их проверять.

    mypy помогает:

  • ловить несоответствия типов без запуска кода
  • стабилизировать контракты между модулями и слоями
  • безопаснее рефакторить
  • Ссылка:

  • mypy documentation
  • Типичный запуск:

    Практическая связка с темой ООП и dataclasses:

  • модели (например, User, Order) с типами проще использовать в сервисах
  • ошибки на границах (JSON, CSV, БД) становятся заметнее
  • Автоматизация через pre-commit

    Чтобы качество не зависело от дисциплины человека, проверки стоит запускать автоматически перед коммитом.

    Инструмент:

  • pre-commit
  • Идея:

  • вы описываете набор хуков
  • pre-commit запускает их перед коммитом
  • если проверка не прошла, коммит не создаётся
  • Минимальный пример .pre-commit-config.yaml (концептуально):

    Установка хуков:

    Зачем это нужно на уровне middle:

  • сокращает “петлю обратной связи”: ошибки видны до push
  • снижает нагрузку на код-ревью
  • помогает держать репозиторий в одинаковом состоянии
  • Рекомендуемая минимальная конфигурация проекта

    Ниже практичный минимум для учебного и небольшого прикладного проекта:

  • README.md с инструкцией запуска
  • .gitignore
  • .venv/ как локальное окружение
  • requirements.txt или другой способ фиксации зависимостей
  • линтер и форматирование (например, Ruff)
  • опционально mypy
  • опционально pre-commit
  • Пример структуры:

    Итоги

    На этом шаге вы прокачали то, что часто отличает junior от middle в реальных командах:

  • Git как инструмент истории, веток, ревью и поиска регрессий
  • виртуальные окружения как способ воспроизводимо запускать проект
  • линтеры и форматирование как автоматический контроль качества
  • pre-commit как механизм, который делает качество “умолчанием”, а не просьбой
  • Дальше эти инструменты будут использоваться как стандартный фон: вы сможете собирать более крупные проекты, безопасно рефакторить код, поддерживать архитектуру и уверенно работать в команде.

    6. Тестирование и качество: pytest, mock, coverage, обработка ошибок

    Тестирование и качество: pytest, mock, coverage, обработка ошибок

    К уровню middle обычно относят не только умение писать рабочий код, но и умение доказывать, что он работает, и быстро находить, что сломалось после изменений.

    Связь с предыдущими темами курса:

  • из темы про модули важно уметь тестировать код по модульным границам, не смешивая бизнес-логику и ввод-вывод
  • из темы про структуры данных и сложность важно писать тесты, которые не зависят от случайных деталей реализации и не тормозят разработку
  • из темы про ООП и dataclasses важно проверять инварианты моделей и сценарии сервисов, не привязываясь к инфраструктуре
  • из темы про файлы/JSON/CSV/БД важно разделять тесты “чистой” логики и тесты интеграции с внешними ресурсами
  • из темы про инструменты разработки важно встроить тестирование в ежедневный поток: локальный запуск, pre-commit и CI
  • Полезные источники:

  • Документация pytest
  • pytest fixtures
  • unittest.mock
  • coverage.py documentation
  • pytest-cov
  • Исключения в Python
  • logging — журналирование
  • !Пирамида тестирования помогает выбрать правильный уровень проверки для каждой задачи

    Зачем тесты, если можно “просто запустить и проверить”

    Тесты дают три практические выгоды:

  • защита от регрессий: изменение в одном месте не ломает незаметно другое
  • быстрый цикл разработки: вы меняете код и сразу понимаете, что сломалось
  • контракт поведения: тест фиксирует ожидаемое поведение лучше, чем комментарий
  • Для уровня middle особенно важно уметь тестировать код так, чтобы:

  • тесты были быстрыми и запускались постоянно
  • тесты были детерминированными (одинаковый результат при каждом запуске)
  • тесты не зависели от сети, случайных данных и локальной машины без необходимости
  • Виды тестов и как выбирать уровень

    Чаще всего выделяют три уровня:

  • Unit-тесты: проверяют маленький кусок логики (функцию, метод) без внешних систем
  • Интеграционные тесты: проверяют взаимодействие с реальными зависимостями (БД, файловая система, HTTP) или несколькими модулями вместе
  • E2E (end-to-end): проверяют сценарий “как пользователь” через весь стек
  • Практическое правило:

  • бизнес-правила и инварианты моделей тестируйте unit-тестами
  • доступ к БД, файлам и внешним API покрывайте интеграционными тестами
  • e2e держите минимальными, только для критичных сценариев
  • pytest: базовая механика

    pytest популярен, потому что:

  • использует обычные assert без специальных “assert-методов”
  • автоматически находит тесты
  • имеет мощную систему фикстур и параметризацию
  • Структура и обнаружение тестов

    pytest обычно находит:

  • файлы вида test_.py или _test.py
  • функции вида test_*
  • классы вида Test (если внутри методы test_)
  • Пример минимального теста:

    Запуск:

    Проверка исключений

    Ошибки и исключения лучше тестировать явно.

    Если важно проверить сообщение:

    Параметризация: один тест, много примеров

    Параметризация уменьшает копипасту и делает набор проверок читабельным.

    Фикстуры: подготовка окружения

    Фикстура это функция, которая создаёт данные или ресурсы для тестов.

    Рекомендация уровня middle: фикстуры должны быть маленькими и переиспользуемыми, а не “одна огромная фикстура на весь проект”.

    Временные файлы: tmp_path

    Если нужно работать с файлами, не пишите в реальный проектный каталог. Используйте tmp_path.

    Mock и подмена зависимостей

    Тесты становятся хрупкими, когда бизнес-логика напрямую зависит от:

  • сети
  • времени
  • случайности
  • базы данных
  • внешних библиотек, которые сложно контролировать
  • Идея: на уровне unit-теста вы подменяете “дорогую/нестабильную” зависимость на управляемую.

    Основной инструмент: unittest.mock

    Чаще всего применяют patch.

    Практические правила:

  • патчьте там, где функция используется, а не там, где определена
  • используйте autospec=True, чтобы мок повторял интерфейс оригинала и ловил ошибки в аргументах
  • Пример с autospec:

    Когда мок вреден

    Сигналы, что вы замокали “слишком много”:

  • тест повторяет внутреннюю реализацию, а не проверяет поведение
  • вы проверяете десятки вызовов моков вместо результата
  • при рефакторинге без изменения поведения тесты массово ломаются
  • Хороший баланс:

  • unit-тест проверяет бизнес-результат и инварианты
  • интеграционный тест проверяет реальные взаимодействия (например, с SQLite из прошлой темы)
  • Подмена через дизайн (предпочтительно)

    Часто лучше, чем patch, работает простая инъекция зависимости.

    Такой подход проще поддерживать: меньше магии, больше архитектуры.

    Coverage: измеряем покрытие кода тестами

    Покрытие показывает, какие строки выполнялись при запуске тестов. Это полезный индикатор, но не “цель сама по себе”.

    Важно различать:

  • покрытие строк: сколько строк было выполнено
  • покрытие веток: были ли пройдены обе стороны условий if, try/except и подобного
  • Высокое покрытие не гарантирует качество тестов, но низкое покрытие часто сигнализирует, что важные сценарии не проверяются.

    Запуск с pytest-cov

    Установите зависимости:

    Запуск:

  • --cov-report=term-missing показывает строки, которые не были выполнены
  • --cov-report=html создаёт HTML-отчёт, удобный для анализа
  • Практика: покрывать важное, а не всё подряд

    Обычно имеет смысл:

  • покрыть критичные бизнес-правила и преобразования данных
  • покрыть обработку ошибок на границах (парсинг CSV/JSON, валидация)
  • не гнаться за 100% на простых “провайдерах” данных, если это не приносит уверенности
  • Обработка ошибок как часть качества

    Код уровня middle отличается тем, что ошибки:

  • ожидаемы
  • контролируемы
  • информативны
  • Подход “границы и ядро”

    Хорошая архитектурная привычка:

  • ядро (бизнес-логика) работает с нормализованными данными и выбрасывает понятные исключения
  • границы (CLI, API, файлы, БД) ловят исключения, добавляют контекст, решают что показать пользователю и что залогировать
  • Пример доменного исключения:

    Тестирование таких ошибок обычно проще и полезнее, чем тестирование “случайных” ValueError без смысла.

    Не проглатывайте исключения молча

    Плохо:

    Если исключение действительно можно игнорировать, это должно быть редким случаем и с явным комментарием или логированием.

    Логирование и тесты

    Для диагностики в продакшене используйте logging, а не print.

    В тестах можно проверять логи через caplog (фикстура pytest), если это важно как часть поведения.

    Как организовать тесты в проекте

    Один из рабочих вариантов структуры:

  • src/app/ содержит код
  • tests/ содержит тесты, повторяющие структуру модулей
  • Пример:

    Практические рекомендации:

  • тестируйте чистые функции и методы без I/O отдельно от интеграции с файловой системой и БД
  • делайте тестовые данные маленькими и выразительными
  • избегайте зависимости тестов друг от друга: каждый тест должен запускаться отдельно
  • Встраивание тестов в ежедневный поток

    Локально:

  • запускать pytest перед коммитом
  • держать тесты быстрыми, чтобы их реально запускали часто
  • В команде:

  • запускать тесты в CI при каждом pull request
  • падение тестов должно блокировать merge
  • Эта практика дополняет инструменты качества из предыдущей темы (линтеры, форматирование, pre-commit): линтеры ловят часть ошибок статически, тесты ловят поведение.

    Итоги

    После этой темы у вас есть база, которая приближает к уровню middle в реальных проектах:

  • понимание уровней тестирования и умение выбрать правильный слой
  • уверенная работа с pytest: assert, raises, параметризация, фикстуры, tmp_path
  • использование mock там, где это оправдано, и предпочтение инъекции зависимостей
  • применение coverage как индикатора “белых пятен” в проверках
  • практичный подход к обработке ошибок через доменные исключения, контекст и логирование
  • 7. Асинхронность и веб: asyncio, HTTP, FastAPI/Flask, Docker и деплой

    Асинхронность и веб: asyncio, HTTP, FastAPI/Flask, Docker и деплой

    Переход от junior к middle часто происходит в момент, когда ваш код перестаёт быть “скриптом” и становится сервисом: принимает HTTP-запросы, ходит в другие сервисы, работает под нагрузкой, разворачивается в контейнере и мониторится.

    Эта статья связывает предыдущие темы курса с веб-разработкой:

  • после тем про структуры данных и сложность важно понимать, что веб-сервис чаще упирается в I/O, а не в CPU
  • после тем про ООП и архитектуру важно разделять слой HTTP (контроллеры) и ядро (бизнес-логика)
  • после темы про файлы/БД важно корректно работать с соединениями, транзакциями и форматами
  • после темы про инструменты и тестирование важно воспроизводимо запускать сервис (Docker) и проверять его поведение (интеграционные тесты)
  • Полезные источники:

  • Документация asyncio
  • Документация httpx
  • Документация aiohttp
  • Документация FastAPI
  • Документация Flask
  • Документация Uvicorn
  • Документация Gunicorn
  • Docker документация
  • The Twelve-Factor App
  • Почему асинхронность важна именно в вебе

    В веб-сервисах большинство времени уходит не на вычисления, а на ожидание:

  • чтение тела запроса
  • запросы в базу данных
  • HTTP-запросы в другие сервисы
  • чтение файлов, очереди, кэш
  • Если вы обрабатываете запросы синхронно, поток (или процесс) “сидит и ждёт”, пока I/O завершится. Асинхронность позволяет во время ожидания переключаться на обработку других запросов.

    Важно: асинхронность не делает CPU-вычисления быстрее. Для CPU-задач используют другие инструменты: оптимизацию алгоритма, процессы, очереди задач.

    !Сравнение поведения синхронного и асинхронного сервера при ожидании I/O

    asyncio: базовые понятия без магии

    Что такое event loop

    Event loop (цикл событий) — это механизм, который запускает и планирует выполнение корутин и переключает их, когда они “ждут” I/O.

    Ключевые строительные блоки:

  • async def — объявляет корутину
  • await — отдаёт управление loop, пока операция не завершится
  • asyncio.run() — запускает корутину верхнего уровня
  • Минимальный пример

    asyncio.sleep() здесь имитирует I/O. В реальности вместо него будут HTTP-запросы, запросы к БД и так далее.

    Конкурентный запуск: gather

    Если у вас несколько независимых I/O-операций, их выгодно выполнять конкурентно.

    Практическая идея: gather запускает несколько корутин так, чтобы пока одна ждёт I/O, другая могла выполняться.

    Ограничение параллелизма: семафор

    Даже если вы умеете запускать 1000 запросов одновременно, это не всегда правильно. Вы можете:

  • “положить” внешний сервис
  • уткнуться в лимиты соединений
  • создать пик памяти
  • Типовой способ ограничить конкуренцию — asyncio.Semaphore.

    Частая ошибка: блокирующий код внутри async

    Если внутри async def вы вызываете блокирующую функцию (например, долгий CPU-цикл или синхронный HTTP-клиент), вы блокируете event loop и теряете смысл асинхронности.

    Если нужно вызвать блокирующую функцию, используйте вынос в поток:

    HTTP: запросы, таймауты и надёжность

    Минимум HTTP, который нужен разработчику

    HTTP-запрос состоит из:

  • метода (GET, POST, PUT, DELETE)
  • URL
  • заголовков
  • тела (не всегда)
  • статуса ответа (например, 200, 404, 500)
  • Практика уровня middle: всегда думать про таймауты, ретраи и предсказуемые ошибки.

    Асинхронный клиент на httpx

    Ключевые детали:

  • используйте один клиент на серию запросов, чтобы переиспользовать соединения
  • задавайте таймауты явно
  • вызывайте raise_for_status(), чтобы 4xx/5xx не “прятались”
  • Ретраи и идемпотентность

    Ретраи полезны при временных ошибках сети, но опасны, если операция не идемпотентна.

  • идемпотентные обычно: GET, PUT, DELETE
  • потенциально неидемпотентный: POST (повтор может создать дубликат)
  • Если вам нужен безопасный retry для создания сущности, используйте идемпотентный ключ на стороне сервиса (например, Idempotency-Key), если это предусмотрено контрактом.

    FastAPI и Flask: что выбрать и как думать архитектурно

    Короткое сравнение

    | Критерий | FastAPI | Flask | |---|---|---| | Стиль | современный, типизированный | минималистичный и классический | | Асинхронность | нативно поддерживает async def | исторически WSGI и синхронность | | Валидация входных данных | удобно через модели | обычно вручную или через расширения | | Документация API | автоматом OpenAPI | обычно вручную или через расширения |

    Практический выбор:

  • FastAPI часто удобнее для JSON API и микросервисов
  • Flask часто хорош для простых сервисов и когда нужна максимальная свобода
  • FastAPI: минимальный сервис

    Запуск для разработки:

    Если вы объявите endpoint как async def, он сможет выполнять await для I/O:

    Flask: минимальный сервис

    Запуск:

    Важно: встроенный сервер Flask подходит для разработки. В продакшене нужен production-сервер.

    Как проектировать веб-приложение, чтобы оно жило долго

    Границы и ядро

    В вебе легко смешать всё в обработчиках. Практика уровня middle:

  • слой HTTP отвечает за парсинг запроса, коды ответов, сериализацию
  • ядро (сервисы и модели) отвечает за бизнес-правила
  • инфраструктура отвечает за БД, очереди, внешние API
  • Это напрямую продолжает архитектурные принципы из темы про ООП.

    Таймауты и отмена задач

    В асинхронном коде важна отмена: если клиент оборвал соединение, сервер может отменить текущую работу.

    Практические рекомендации:

  • ставьте таймауты на внешние HTTP-запросы
  • не делайте бесконечных ожиданий
  • внимательно относитесь к asyncio.CancelledError
  • Жизненный цикл приложения

    В веб-приложениях обычно нужно инициализировать ресурсы:

  • пул соединений к БД
  • HTTP-клиенты
  • кеши
  • И корректно их закрывать при завершении.

    Продакшен-запуск: Uvicorn, Gunicorn и воркеры

    ASGI и WSGI

  • WSGI — классический интерфейс для синхронных Python веб-приложений
  • ASGI — современный интерфейс, поддерживающий асинхронность и долгие соединения
  • FastAPI обычно запускают как ASGI-приложение через Uvicorn.

    Команда запуска FastAPI в продакшене (типовой вариант)

    Смысл флагов:

  • -w 2 — число процессов-воркеров
  • -b — адрес и порт
  • -k — ASGI-воркер для работы с async
  • Число воркеров зависит от CPU и профиля нагрузки. Это место, где помогает понимание, CPU-bound или I/O-bound ваш сервис.

    Docker: воспроизводимость запуска

    Docker помогает упаковать приложение так, чтобы оно одинаково работало локально, в CI и на сервере.

    Минимальный Dockerfile для FastAPI

    Практические детали:

  • --no-cache-dir уменьшает размер образа
  • зависимости ставятся отдельно от копирования кода, чтобы Docker лучше кешировал слои
  • docker-compose для локальной разработки (идея)

    Если вы добавляете БД, обычно выносите её отдельным сервисом и конфигурируете через переменные окружения.

    Деплой и эксплуатация: минимум, который отличает middle

    Конфигурация через окружение

    > The Twelve-Factor App рекомендует хранить конфигурацию в переменных окружения.

    Практика:

  • секреты не должны попадать в Git
  • значения окружения можно менять без пересборки приложения
  • Health checks

    Сделайте endpoint вроде /health, который проверяет базовую готовность сервиса. Это нужно для оркестраторов и балансировщиков.

    Логи

    В продакшене почти всегда собирают логи из stdout/stderr контейнера.

    Практика:

  • используйте модуль logging
  • логируйте контекст ошибки
  • избегайте print в сервисном коде
  • CORS и безопасность на базовом уровне

  • CORS — политика браузера, ограничивающая запросы между доменами
  • если ваш API вызывается из браузера, CORS нужно настроить осознанно
  • Также держите в голове:

  • валидация входных данных
  • ограничение размера запроса
  • защита от слишком частых запросов (rate limiting) на уровне API gateway или reverse proxy
  • Типовые ошибки и как их избегать

    “Сделал async, но стало медленнее”

    Частые причины:

  • внутри async остались блокирующие вызовы
  • слишком много конкуренции без лимитов
  • нет переиспользования соединений (создание клиента на каждый запрос)
  • “Зависает под нагрузкой”

    Частые причины:

  • нет таймаутов на внешние запросы
  • бесконечные ретраи
  • утечки соединений к БД или HTTP-клиенту
  • “Локально работает, на сервере нет”

    Частые причины:

  • отличающееся окружение и зависимости
  • отсутствие переменных окружения
  • запуск через dev-сервер вместо production
  • Docker и воспроизводимая сборка решают эту категорию проблем.

    Итоги

    Теперь у вас есть связная картина веб-части, которая часто требуется на уровне middle:

  • как устроена асинхронность в Python через asyncio: корутины, await, конкурентность и ограничения
  • как аккуратно работать с HTTP-клиентом: таймауты, обработка статусов, ретраи и идемпотентность
  • чем отличаются FastAPI и Flask и как не смешивать HTTP-слой с бизнес-логикой
  • как запускать сервис в продакшене через Uvicorn/Gunicorn и воркеры
  • как упаковать приложение в Docker и какие эксплуатационные практики нужны: env-конфиг, health checks, логи