🚀 Ты на собеседовании на Senior Python Developer. Звучит вопрос: «Расскажи про контекстные менеджеры для работы с базами данных». Не мямли, не говори «ну, это такая штука, чтобы соединение закрывать». Покажи глубину. Поехали!
1. База: зачем они вообще нужны?
Представь: ты открываешь соединение с БД, делаешь запросы, а потом… забываешь его закрыть. Или происходит исключение, и соединение «виснет». Это утечка ресурсов, и на продакшене такое аукнется пулом занятых соединений. Контекстный менеджер (через
2. Как это выглядит в коде?
Самый простой способ — использовать готовый менеджер из библиотеки:
3. А если библиотека не поддерживает
Тогда ты пишешь свой контекстный менеджер. Это два магических метода:
4. Что важно для Senior?
Ты должен понимать:
• Транзакции: внутри
• Пул соединений: контекстный менеджер не обязан каждый раз создавать новое соединение. Он может брать готовое из пула (например,
• Вложенные менеджеры: один
• Исключения: в
5. Пример для продакшена (на
6. Главный совет для собеса:
Не просто расскажи про
Теперь ты готов. Иди и закрой эту тему 🔥
#python #senior #собеседование
1. База: зачем они вообще нужны?
Представь: ты открываешь соединение с БД, делаешь запросы, а потом… забываешь его закрыть. Или происходит исключение, и соединение «виснет». Это утечка ресурсов, и на продакшене такое аукнется пулом занятых соединений. Контекстный менеджер (через
with) гарантирует, что ресурс будет освобожден даже при ошибке. Это как автоматический «уборщик».2. Как это выглядит в коде?
Самый простой способ — использовать готовый менеджер из библиотеки:
import sqlite3
# Плохо: открыли, забыли закрыть
conn = sqlite3.connect('db.sqlite')
cursor = conn.cursor()
cursor.execute('SELECT 1')
# conn.close() # а если забыл?
# Хорошо: with сам закроет
with sqlite3.connect('db.sqlite') as conn:
cursor = conn.cursor()
cursor.execute('SELECT 1')
# conn закроется автоматически, даже если была ошибка
3. А если библиотека не поддерживает
with?Тогда ты пишешь свой контекстный менеджер. Это два магических метода:
class DBConnection:
def __init__(self, db_name):
self.db_name = db_name
def __enter__(self):
print('Открываю соединение')
self.conn = sqlite3.connect(self.db_name)
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
print('Закрываю соединение')
self.conn.close()
# Если вернуть False (или None), исключение пробросится дальше
# Если вернуть True — исключение «съедается»
return False
with DBConnection('db.sqlite') as conn:
cursor = conn.cursor()
cursor.execute('SELECT 1')
4. Что важно для Senior?
Ты должен понимать:
• Транзакции: внутри
with можно управлять commit/rollback. Например, если в __exit__ проверить exc_type, можно откатить транзакцию при ошибке.• Пул соединений: контекстный менеджер не обязан каждый раз создавать новое соединение. Он может брать готовое из пула (например,
psycopg2.pool) и возвращать его обратно.• Вложенные менеджеры: один
with внутри другого — норм. Но следи за порядком: сначала открываем внешний ресурс, потом внутренний.• Исключения: в
__exit__ ты получаешь тип, значение и traceback. Если вернешь True, исключение будет подавлено — делай это осознанно, только если ты его обработал.5. Пример для продакшена (на
psycopg2):from contextlib import contextmanager
import psycopg2
@contextmanager
def get_db_connection(dsn):
conn = psycopg2.connect(dsn)
try:
yield conn
conn.commit() # если всё ок
except Exception:
conn.rollback() # если ошибка
raise
finally:
conn.close()
6. Главный совет для собеса:
Не просто расскажи про
with. Скажи: «Контекстный менеджер — это паттерн для управления ресурсами. Для БД он гарантирует закрытие соединения и корректную обработку транзакций. Я использую его везде, где есть внешние ресурсы: файлы, сокеты, БД». И добавь про contextlib.contextmanager — это упрощает написание.Теперь ты готов. Иди и закрой эту тему 🔥
#python #senior #собеседование
🚀 WebSockets в FastAPI: Реальное время для твоего чата!
Представь, что ты звонишь другу: один раз набрал номер, и вы общаетесь без перерыва. WebSocket — это как такой звонок, только между браузером и сервером. Никаких постоянных проверок «а есть ли новое письмо?». Всё мгновенно!
❓ Что такое WebSocket?
Это протокол, который создаёт постоянный канал связи. Клиент (твой браузер или приложение) и сервер могут обмениваться данными в реальном времени. Инициатором может быть кто угодно — не только клиент, но и сервер. Круто, правда?
🆚 Сравним с классическим HTTP:
• HTTP: как почта. Ты отправляешь запрос, сервер отвечает. Чтобы получить новые сообщения, клиент должен постоянно «заглядывать в почтовый ящик» (делать новые запросы). Это медленно, нагружает сервер, тратит трафик.
• WebSocket: как телефонный разговор. Один раз установили соединение — и общаетесь без задержек. Сервер сам может отправить данные, когда они появляются.
🔧 Как это работает в FastAPI?
FastAPI поддерживает WebSocket «из коробки». Нужно просто создать endpoint с
Пример кода (очень простой):
Что здесь происходит?
1. Клиент подключается к
2. Сервер принимает соединение (
3. В бесконечном цикле сервер ждёт сообщение от клиента и отправляет его обратно (эхо).
💡 Для чата с комнатами нужно чуть больше логики: хранить подключения, рассылать сообщения всем участникам комнаты. Но основа та же.
🔥 Почему это важно для Senior?
На собеседовании спросят:
• Как управлять подключениями? (словарь с websocket-ами)
• Как обрабатывать отключения? (try/except, закрытие соединения)
• Как масштабировать? (через Redis Pub/Sub, чтобы несколько серверов знали друг о друге)
📌 Итог: WebSocket — must-have для real-time фич. FastAPI делает работу с ним простой и элегантной. Учись, и твой чат будет летать!
#Python #FastAPI #WebSocket
Представь, что ты звонишь другу: один раз набрал номер, и вы общаетесь без перерыва. WebSocket — это как такой звонок, только между браузером и сервером. Никаких постоянных проверок «а есть ли новое письмо?». Всё мгновенно!
❓ Что такое WebSocket?
Это протокол, который создаёт постоянный канал связи. Клиент (твой браузер или приложение) и сервер могут обмениваться данными в реальном времени. Инициатором может быть кто угодно — не только клиент, но и сервер. Круто, правда?
🆚 Сравним с классическим HTTP:
• HTTP: как почта. Ты отправляешь запрос, сервер отвечает. Чтобы получить новые сообщения, клиент должен постоянно «заглядывать в почтовый ящик» (делать новые запросы). Это медленно, нагружает сервер, тратит трафик.
• WebSocket: как телефонный разговор. Один раз установили соединение — и общаетесь без задержек. Сервер сам может отправить данные, когда они появляются.
🔧 Как это работает в FastAPI?
FastAPI поддерживает WebSocket «из коробки». Нужно просто создать endpoint с
websocket.Пример кода (очень простой):
from fastapi import FastAPI, WebSocket
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Эхо: {data}")
Что здесь происходит?
1. Клиент подключается к
/ws.2. Сервер принимает соединение (
accept()).3. В бесконечном цикле сервер ждёт сообщение от клиента и отправляет его обратно (эхо).
💡 Для чата с комнатами нужно чуть больше логики: хранить подключения, рассылать сообщения всем участникам комнаты. Но основа та же.
🔥 Почему это важно для Senior?
На собеседовании спросят:
• Как управлять подключениями? (словарь с websocket-ами)
• Как обрабатывать отключения? (try/except, закрытие соединения)
• Как масштабировать? (через Redis Pub/Sub, чтобы несколько серверов знали друг о друге)
📌 Итог: WebSocket — must-have для real-time фич. FastAPI делает работу с ним простой и элегантной. Учись, и твой чат будет летать!
#Python #FastAPI #WebSocket
🔥 Когда учишь Python, говорят: «это язык с динамической типизацией». В переменную x можно записать сначала число 5, потом строку "привет" — и всё заработает. Но на уровне Senior такая свобода часто приводит к ошибкам. Для борьбы с этим придумали аннотации типов.
Что такое аннотация типа?
Это как наклейка «ХРУПКОЕ» на коробке: заранее знаешь, что внутри. Аннотация не мешает программе работать, но подсказывает, что ожидается в переменной или функции.
Без подсказок:
def add(a, b):
return a + b
add(2, 3) — ок, add("кошка", "собака") — получится "кошкасобака". Ошибка вылезет только во время работы.
С аннотациями:
def add(a: int, b: int) -> int:
return a + b
После имени — двоеточие и тип, стрелка -> показывает тип возврата.
Важно: Python всё равно не проверяет типы при запуске. Это делает mypy — он анализирует код без запуска и заранее предупреждает о несоответствиях, как проверка орфографии для кода.
Union Types — «или то, или другое»
Возраст — то ли число 25, то ли строка "25". С Python 3.10:
age: int | str = 25
Mypy понимает: сюда можно int или str, но не список.
Списки с чётким содержимым
def sum_numbers(numbers: list[int | float]) -> float:
return sum(numbers)
Список, где каждый элемент int или float. Подсунешь строки — mypy предупредит заранее.
Словари с известной структурой (TypedDict)
Обычный словарь легко перепутать ключами в большом проекте. TypedDict описывает структуру чётко:
from typing import TypedDict, NotRequired
class User(TypedDict):
id: int
name: str
email: NotRequired[str]
def print_user(user: User) -> None:
print(user["name"])
IDE подскажет ключи словаря, а mypy предупредит при обращении к несуществующему полю.
Почему Senior любят аннотации типов
— Документация, которая не устаревает.
— Защита от глупых ошибок: mypy ругнётся ещё до боевого сервера.
— Быстрый рефакторинг: IDE точно знает, где какие типы меняются.
Что сказать на собеседовании
Не пересказывай синтаксис — объясни пользу: «Данные из API могут прийти как число или строка, поэтому используем int | str. А конфиги описываем через TypedDict — новый разработчик сразу видит структуру».
Итог
1. Добавляй типы в новые функции.
2. Поставь mypy (pip install mypy), запускай mypy файл.py.
3. Освоишься — пробуй list[int] и TypedDict.
Ошибки находятся на этапе написания, код становится понятнее.
Удачи! 😎
#Python #Типизация #Senior #Новичкам
Что такое аннотация типа?
Это как наклейка «ХРУПКОЕ» на коробке: заранее знаешь, что внутри. Аннотация не мешает программе работать, но подсказывает, что ожидается в переменной или функции.
Без подсказок:
def add(a, b):
return a + b
add(2, 3) — ок, add("кошка", "собака") — получится "кошкасобака". Ошибка вылезет только во время работы.
С аннотациями:
def add(a: int, b: int) -> int:
return a + b
После имени — двоеточие и тип, стрелка -> показывает тип возврата.
Важно: Python всё равно не проверяет типы при запуске. Это делает mypy — он анализирует код без запуска и заранее предупреждает о несоответствиях, как проверка орфографии для кода.
Union Types — «или то, или другое»
Возраст — то ли число 25, то ли строка "25". С Python 3.10:
age: int | str = 25
Mypy понимает: сюда можно int или str, но не список.
Списки с чётким содержимым
def sum_numbers(numbers: list[int | float]) -> float:
return sum(numbers)
Список, где каждый элемент int или float. Подсунешь строки — mypy предупредит заранее.
Словари с известной структурой (TypedDict)
Обычный словарь легко перепутать ключами в большом проекте. TypedDict описывает структуру чётко:
from typing import TypedDict, NotRequired
class User(TypedDict):
id: int
name: str
email: NotRequired[str]
def print_user(user: User) -> None:
print(user["name"])
IDE подскажет ключи словаря, а mypy предупредит при обращении к несуществующему полю.
Почему Senior любят аннотации типов
— Документация, которая не устаревает.
— Защита от глупых ошибок: mypy ругнётся ещё до боевого сервера.
— Быстрый рефакторинг: IDE точно знает, где какие типы меняются.
Что сказать на собеседовании
Не пересказывай синтаксис — объясни пользу: «Данные из API могут прийти как число или строка, поэтому используем int | str. А конфиги описываем через TypedDict — новый разработчик сразу видит структуру».
Итог
1. Добавляй типы в новые функции.
2. Поставь mypy (pip install mypy), запускай mypy файл.py.
3. Освоишься — пробуй list[int] и TypedDict.
Ошибки находятся на этапе написания, код становится понятнее.
Удачи! 😎
#Python #Типизация #Senior #Новичкам
🚀 ТЫ ДУМАЕШЬ, ЧТО ЗНАЕШЬ ПРО ПУЛ СОЕДИНЕНИЙ? 🚀
Сеньор-разработчик отличается от джуна не только количеством кода, но и пониманием того, как этот код работает под капотом. Сегодня разберем одну из таких тем — пул соединений (connection pool). Это то, что превращает твое приложение из «черепахи» в «гепарда».
Что такое пул соединений и зачем он нужен?
Представь, что каждое обращение к базе данных — это как зайти в кафе и каждый раз заново искать столик, делать заказ и ждать. Соединение с БД — дорогая операция (по времени и ресурсам). Пул соединений — это как забронировать несколько столиков заранее. Ты просто подходишь, садишься и сразу получаешь кофе. Экономия времени — колоссальная!
Как это выглядит в коде?
В Python, особенно при работе с PostgreSQL через библиотеку
Видишь? Вместо того чтобы каждый раз вызывать
Какие бывают типы пулов?
В
• AbstractConnectionPool — это «скелет», абстрактный класс. На его основе можно сделать свой пул.
• SimpleConnectionPool — для однопоточных приложений. Самый простой и понятный.
• ThreadedConnectionPool — для многопоточных. Пул можно смело использовать из разных потоков.
• PersistentConnectionPool — для многопоточных приложений, где каждому потоку нужно «свое» постоянное соединение. Чаще используется со старыми фреймворками вроде Zope.
Главные методы, которые нужно знать:
•
•
•
Совет от сеньора:
Не используй
Почему это важно на собеседовании?
Потому что это показывает, что ты понимаешь, как оптимизировать работу с ресурсами. Сеньор — это не просто «код писать», это «делать так, чтобы код не упал под нагрузкой». А пул соединений — один из ключевых инструментов для этого.
#Python #Senior #БазыДанных
Сеньор-разработчик отличается от джуна не только количеством кода, но и пониманием того, как этот код работает под капотом. Сегодня разберем одну из таких тем — пул соединений (connection pool). Это то, что превращает твое приложение из «черепахи» в «гепарда».
Что такое пул соединений и зачем он нужен?
Представь, что каждое обращение к базе данных — это как зайти в кафе и каждый раз заново искать столик, делать заказ и ждать. Соединение с БД — дорогая операция (по времени и ресурсам). Пул соединений — это как забронировать несколько столиков заранее. Ты просто подходишь, садишься и сразу получаешь кофе. Экономия времени — колоссальная!
Как это выглядит в коде?
В Python, особенно при работе с PostgreSQL через библиотеку
psycopg2, есть встроенные инструменты. Давай посмотрим на самый простой пример:from psycopg2 import pool
# Создаем пул: минимум 2 соединения, максимум 10
connection_pool = pool.SimpleConnectionPool(
2, 10,
user="myuser",
password="mypass",
host="localhost",
port="5432",
database="mydb"
)
# Берем соединение из пула
conn = connection_pool.getconn()
# Работаем с базой
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
# Возвращаем соединение обратно в пул
connection_pool.putconn(conn)
# Когда всё сделано — закрываем все соединения
connection_pool.closeall()
Видишь? Вместо того чтобы каждый раз вызывать
psycopg2.connect(), мы один раз создаем пул и потом просто берем готовые соединения. Это даёт огромный прирост производительности.Какие бывают типы пулов?
В
psycopg2 есть четыре класса. Не пугайся, всё просто:• AbstractConnectionPool — это «скелет», абстрактный класс. На его основе можно сделать свой пул.
• SimpleConnectionPool — для однопоточных приложений. Самый простой и понятный.
• ThreadedConnectionPool — для многопоточных. Пул можно смело использовать из разных потоков.
• PersistentConnectionPool — для многопоточных приложений, где каждому потоку нужно «свое» постоянное соединение. Чаще используется со старыми фреймворками вроде Zope.
Главные методы, которые нужно знать:
•
getconn(key=None) — взять соединение из пула. Если передать key, то вернется соединение, привязанное к этому ключу (актуально для PersistentConnectionPool).•
putconn(connection, key=None, close=False) — вернуть соединение обратно. Если close=True, то соединение удалится из пула.•
closeall() — закрыть все соединения в пуле. Обязательно вызывай при завершении работы приложения!Совет от сеньора:
Не используй
SimpleConnectionPool в веб-приложениях с многопоточностью (например, Flask или Django с WSGI). Там нужно ThreadedConnectionPool. Иначе получишь race condition и странные ошибки.Почему это важно на собеседовании?
Потому что это показывает, что ты понимаешь, как оптимизировать работу с ресурсами. Сеньор — это не просто «код писать», это «делать так, чтобы код не упал под нагрузкой». А пул соединений — один из ключевых инструментов для этого.
#Python #Senior #БазыДанных
🚀 Что такое Dependency Injection в Django и как его настроить? Разбор для Senior!
Вы на собеседовании. Вас спрашивают: "Как вы управляете зависимостями в Django? Используете ли Dependency Injection?"
Если вы ответите: "Ну, я просто создаю объекты внутри view..." — это провал. Senior должен мыслить архитектурно.
Давайте разберем, что это за зверь и как его приручить.
🔍 Суть Dependency Injection (DI) — это когда объект не создает свои зависимости сам, а получает их "снаружи".
Представьте: ваш view должен отправить email. Вместо того чтобы писать:
Вы делаете так:
Зачем это нужно?
• Тестируемость: вы легко подменяете реальный EmailSender на заглушку (mock).
• Гибкость: можно переключиться с одного сервиса на другой без изменения кода view.
• Чистота кода: view не знает, как создаются зависимости — это его не касается.
🛠 Как реализовать DI в Django?
Django из коробки не имеет встроенного DI-контейнера (как Spring в Java). Но это не проблема — мы можем сделать это сами.
Самый простой и элегантный способ — через декораторы или фабрики.
Рассмотрим подход с ServiceInjector (класс, который регистрирует зависимости и внедряет их в функции).
Пример:
Как это работает?
1. Регистрация: декоратор
2. Внедрение: декоратор
3. Внутри view вы просто берете нужную зависимость из
Где регистрировать зависимости? Лучше в отдельном модуле
🔥 Альтернатива для простых случаев — передача зависимостей через конструктор класса-контроллера (как в примере из Stack Overflow).
Важно: DI — это не серебряная пуля. В маленьких проектах он может быть избыточен. Но для больших, тестируемых систем — это must have.
На собеседовании покажите, что вы понимаете не только механику, но и зачем это нужно. Senior отличает умение выбирать правильный инструмент под задачу.
#python #django #senior
Вы на собеседовании. Вас спрашивают: "Как вы управляете зависимостями в Django? Используете ли Dependency Injection?"
Если вы ответите: "Ну, я просто создаю объекты внутри view..." — это провал. Senior должен мыслить архитектурно.
Давайте разберем, что это за зверь и как его приручить.
🔍 Суть Dependency Injection (DI) — это когда объект не создает свои зависимости сам, а получает их "снаружи".
Представьте: ваш view должен отправить email. Вместо того чтобы писать:
def my_view(request):
mailer = EmailSender() # ❌ Жесткая связь
mailer.send("Hello")Вы делаете так:
def my_view(request, mailer): # ✅ Зависимость приходит извне
mailer.send("Hello")Зачем это нужно?
• Тестируемость: вы легко подменяете реальный EmailSender на заглушку (mock).
• Гибкость: можно переключиться с одного сервиса на другой без изменения кода view.
• Чистота кода: view не знает, как создаются зависимости — это его не касается.
🛠 Как реализовать DI в Django?
Django из коробки не имеет встроенного DI-контейнера (как Spring в Java). Но это не проблема — мы можем сделать это сами.
Самый простой и элегантный способ — через декораторы или фабрики.
Рассмотрим подход с ServiceInjector (класс, который регистрирует зависимости и внедряет их в функции).
Пример:
from functools import wraps
class ServiceInjector:
def __init__(self):
self.deps = {}
def register(self, name=None):
def decorator(thing):
nonlocal name
if not name:
if not hasattr(thing, '__name__'):
raise Exception("no name")
name = thing.__name__
self.deps[name] = thing
return thing
return decorator
def inject(self, func):
@wraps(func)
def decorated(*args, **kwargs):
# Добавляем словарь зависимостей как последний аргумент
new_args = args + (self.deps,)
return func(*new_args, **kwargs)
return decorated
# Использование:
si = ServiceInjector()
@si.register()
def send_email(msg):
print(f"Sending: {msg}")
@si.register(name="PI")
PI = 3.14159
@si.inject
def my_view(request, _deps):
email_sender = _deps['send_email']
pi = _deps['PI']
email_sender("Hello from DI!")
print(pi)
Как это работает?
1. Регистрация: декоратор
@si.register() добавляет функцию или класс в словарь deps под её именем (или под кастомным именем, если указать name).2. Внедрение: декоратор
@si.inject автоматически добавляет словарь _deps последним аргументом в функцию.3. Внутри view вы просто берете нужную зависимость из
_deps.Где регистрировать зависимости? Лучше в отдельном модуле
di_config.py или прямо в urls.py.🔥 Альтернатива для простых случаев — передача зависимостей через конструктор класса-контроллера (как в примере из Stack Overflow).
class ApiController:
def __init__(self, **deps):
self.deps = deps
def get_all_posts(self, request):
svc = self.deps['post_service']
# ...
# В urls.py:
urlpatterns = [
path("posts/", ApiController(post_service=PostService()).get_all_posts),
]
Важно: DI — это не серебряная пуля. В маленьких проектах он может быть избыточен. Но для больших, тестируемых систем — это must have.
На собеседовании покажите, что вы понимаете не только механику, но и зачем это нужно. Senior отличает умение выбирать правильный инструмент под задачу.
#python #django #senior
🔥 Что такое миграции в Django и как их создавать? Разбираем вопрос для собеседования на Senior Python Developer!
Представь: ты работаешь над интернет-магазином. Внезапно нужно добавить поле «скидка» к модели товара. Ты просто дописываешь код, заливаешь на сервер… и сайт падает с ошибкой! 😱 Почему? Потому что база данных не знает о новом поле — её структура устарела.
Вот тут и приходят на помощь миграции. Это как система контроля версий для твоей базы данных. Они хранят инструкции по изменению схемы БД (добавить колонку, создать таблицу и т.д.) и позволяют применять эти изменения без потери данных. 🚀
Как это работает?
1. Ты меняешь модели в
2. Запускаешь команду:
Django сравнивает текущие модели с последним сохранённым состоянием и создаёт файл миграции (в папке
3. Применяешь миграцию:
Django выполняет инструкции из файла и обновляет базу данных. Все применённые миграции записываются в специальную таблицу
Важные детали для Senior-уровня:
• Каждая миграция содержит два метода:
• Django строит граф зависимостей между миграциями разных приложений, чтобы применять их в правильном порядке. 🧩
• Команда
•
•
•
Пример из жизни:
Вместо того чтобы сразу выкатывать новый код, опытный разработчик сначала создаёт миграцию, применяет её (отдельным деплоем), и только потом заливает код, который использует новое поле. Сайт работает без простоев! 👌
Главный совет:
Миграции — это твой друг. Не бойся их, а используй осознанно. На собеседовании покажи, что понимаешь не только команды, но и внутреннее устройство: таблицу
Прокачайся и стань тем Senior, который не ломает прод! 💪
#Django #Python #Senior
Представь: ты работаешь над интернет-магазином. Внезапно нужно добавить поле «скидка» к модели товара. Ты просто дописываешь код, заливаешь на сервер… и сайт падает с ошибкой! 😱 Почему? Потому что база данных не знает о новом поле — её структура устарела.
Вот тут и приходят на помощь миграции. Это как система контроля версий для твоей базы данных. Они хранят инструкции по изменению схемы БД (добавить колонку, создать таблицу и т.д.) и позволяют применять эти изменения без потери данных. 🚀
Как это работает?
1. Ты меняешь модели в
models.py.2. Запускаешь команду:
python manage.py makemigrations
Django сравнивает текущие модели с последним сохранённым состоянием и создаёт файл миграции (в папке
migrations/ твоего приложения). Это обычный Python-файл с инструкциями: «добавить поле», «создать индекс» и т.п.3. Применяешь миграцию:
python manage.py migrate
Django выполняет инструкции из файла и обновляет базу данных. Все применённые миграции записываются в специальную таблицу
django_migrations — так система знает, что уже сделано.Важные детали для Senior-уровня:
• Каждая миграция содержит два метода:
forwards() (как применить) и backwards() (как откатить).• Django строит граф зависимостей между миграциями разных приложений, чтобы применять их в правильном порядке. 🧩
• Команда
makemigrations имеет полезные опции:•
--name NAME — задать имя миграции (например, add_discount_field).•
--empty — создать пустую миграцию для ручного написания сложных операций.•
--dry-run — показать, что будет создано, но не создавать файлы.Пример из жизни:
Вместо того чтобы сразу выкатывать новый код, опытный разработчик сначала создаёт миграцию, применяет её (отдельным деплоем), и только потом заливает код, который использует новое поле. Сайт работает без простоев! 👌
Главный совет:
Миграции — это твой друг. Не бойся их, а используй осознанно. На собеседовании покажи, что понимаешь не только команды, но и внутреннее устройство: таблицу
django_migrations, граф зависимостей, ручное редактирование миграций.Прокачайся и стань тем Senior, который не ломает прод! 💪
#Django #Python #Senior
🚀 **КАК РЕАЛИЗОВАТЬ АСИНХРОННЫЕ ЗАДАЧИ С ПОМОЩЬЮ CELERY?**
Представь: пользователь регистрируется, загружает фото и ждет... и ждет... пока сервер обработает изображение и отправит письмо. 🙈 Это путь в никуда. На собеседовании Senior тебя спросят: «Как вынести тяжелые задачи из основного потока?» — и тут твой звездный час!
**Что такое Celery?**
Это библиотека для создания очередей задач. Она берет на себя «тяжелую работу» (отправка писем, обработка фото, ML-модели) и выполняет ее в фоне, не блокируя ответ пользователю.
**Как это работает (простыми словами):**
1. Пользователь отправляет запрос (например, POST на загрузку фото).
2. FastAPI (или Flask) не обрабатывает фото сам, а кладет задачу в очередь (Redis или RabbitMQ).
3. Celery Worker (отдельный процесс) забирает задачу из очереди и выполняет ее.
4. Пользователь сразу получает ответ: «Задача принята, ID: 123». И может спокойно заниматься своими делами.
5. Клиент (фронтенд) может периодически спрашивать сервер: «Ну что, задача готова?» (это называется polling).
**Когда использовать Celery, а не
-
- Celery — нужен для:
• CPU-интенсивных задач (обработка видео, генерация отчетов).
• Задач, которые должны выполняться гарантированно (с повторными попытками).
• Когда нужно отслеживать статус задачи (выполнена/ошибка).
• Когда задач много и нужна очередь с приоритетами.
**Пример кода (очень простой):**
**Как это запустить?**
1. Установить Redis (брокер сообщений) — можно через Docker.
2. Установить Celery:
3. Запустить воркер:
4. Запустить FastAPI приложение.
**Почему это важно для Senior?**
Senior должен понимать архитектуру: как отделить синхронный код от асинхронного, как масштабировать воркеры, как мониторить задачи (через Flower), как обрабатывать ошибки и повторные попытки. Celery — это стандарт индустрии.
**Коротко: что сказать на собеседовании?**
- Celery + Redis — это очередь задач.
- Используем для тяжелых фоновых процессов.
- Worker — отдельный процесс, который выполняет задачи.
- Клиент получает ID задачи и может опрашивать статус.
Готовься, это один из ключевых вопросов! 💪
#Python #Celery #Senior
Представь: пользователь регистрируется, загружает фото и ждет... и ждет... пока сервер обработает изображение и отправит письмо. 🙈 Это путь в никуда. На собеседовании Senior тебя спросят: «Как вынести тяжелые задачи из основного потока?» — и тут твой звездный час!
**Что такое Celery?**
Это библиотека для создания очередей задач. Она берет на себя «тяжелую работу» (отправка писем, обработка фото, ML-модели) и выполняет ее в фоне, не блокируя ответ пользователю.
**Как это работает (простыми словами):**
1. Пользователь отправляет запрос (например, POST на загрузку фото).
2. FastAPI (или Flask) не обрабатывает фото сам, а кладет задачу в очередь (Redis или RabbitMQ).
3. Celery Worker (отдельный процесс) забирает задачу из очереди и выполняет ее.
4. Пользователь сразу получает ответ: «Задача принята, ID: 123». И может спокойно заниматься своими делами.
5. Клиент (фронтенд) может периодически спрашивать сервер: «Ну что, задача готова?» (это называется polling).
**Когда использовать Celery, а не
BackgroundTasks из FastAPI?**-
BackgroundTasks — легковесный, работает в том же процессе. Подходит для быстрых задач (отправить уведомление).- Celery — нужен для:
• CPU-интенсивных задач (обработка видео, генерация отчетов).
• Задач, которые должны выполняться гарантированно (с повторными попытками).
• Когда нужно отслеживать статус задачи (выполнена/ошибка).
• Когда задач много и нужна очередь с приоритетами.
**Пример кода (очень простой):**
# tasks.py
from celery import Celery
app = Celery('tasks', broker='redis://localhost:6379/0')
@app.task
def send_email(email, message):
# тут логика отправки
print(f"Отправляем {message} на {email}")
return "OK"
# main.py (FastAPI)
from fastapi import FastAPI
from tasks import send_email
app = FastAPI()
@app.post("/send")
def send_email_endpoint(email: str):
task = send_email.delay(email, "Привет!")
return {"task_id": task.id, "status": "Queued"}
**Как это запустить?**
1. Установить Redis (брокер сообщений) — можно через Docker.
2. Установить Celery:
pip install celery[redis].3. Запустить воркер:
celery -A tasks worker --loglevel=info.4. Запустить FastAPI приложение.
**Почему это важно для Senior?**
Senior должен понимать архитектуру: как отделить синхронный код от асинхронного, как масштабировать воркеры, как мониторить задачи (через Flower), как обрабатывать ошибки и повторные попытки. Celery — это стандарт индустрии.
**Коротко: что сказать на собеседовании?**
- Celery + Redis — это очередь задач.
- Используем для тяжелых фоновых процессов.
- Worker — отдельный процесс, который выполняет задачи.
- Клиент получает ID задачи и может опрашивать статус.
Готовься, это один из ключевых вопросов! 💪
#Python #Celery #Senior
🔥 СЛОЖНАЯ ТЕМА, КОТОРУЮ СПРАШИВАЮТ НА КАЖДОМ СОБЕСЕДОВАНИИ: SAGA PATTERN В PYTHON. РАЗБИРАЕМСЯ, ПОКА ГОРИТО! 🔥
Представь: ты — архитектор огромного интернет-магазина. У тебя есть отдельные сервисы: заказы, платежи, склад, доставка. И вот клиент оформляет заказ. Ты создаёшь запись в БД заказов, списываешь деньги, резервируешь товар, отправляешь доставку. Всё бы ничего, но КАЖДЫЙ СЕРВИС ХРАНИТ ДАННЫЕ В СВОЕЙ БАЗЕ. И тут склад падает — товара нет. Деньги списаны, а заказ не выполнен. КАК ОТКАТИТЬ ПЛАТЁЖ? 🤯
В монолите ты бы просто сделал ROLLBACK, и все изменения отменились бы. Но в микросервисах ACID-транзакции между разными БД НЕ РАБОТАЮТ. И тут на сцену выходит SAGA PATTERN — твой спаситель! 🦸♂️
ЧТО ТАКОЕ SAGA?
Это паттерн, который разбивает большую распределённую транзакцию на последовательность маленьких локальных транзакций. Каждый шаг — это отдельная операция в одном сервисе. Если что-то пошло не так, запускаются КОМПЕНСИРУЮЩИЕ ТРАНЗАКЦИИ — они отменяют уже сделанные шаги и возвращают систему в консистентное состояние.
ЕСТЬ ДВА ПОДХОДА:
1️⃣ CHOREOGRAPHY (ХОРЕОГРАФИЯ) — децентрализованный танец без дирижёра. Каждый сервис слушает события (например, через Kafka) и сам решает, что делать дальше. Создал заказ → опубликовал событие OrderCreated → платёжный сервис услышал и списал деньги → опубликовал PaymentReserved → склад услышал и зарезервировал товар... И так по цепочке. Если шаг упал, сервис публикует событие об ошибке, и предыдущие сервисы делают компенсацию.
Плюсы: сервисы полностью независимы, нет единой точки отказа.
Минусы: при длинных цепочках сложно понять, что вообще происходит. А если сервисы зациклились — пиши пропало! 😅
2️⃣ ORCHESTRATION (ОРКЕСТРОВКА) — есть центральный координатор (оркестратор), который управляет всеми шагами. Он говорит: «Платеж, спиши деньги!», «Склад, зарезервируй товар!». Если что-то падает, оркестратор сам запускает компенсации.
Плюсы: весь процесс виден в одном месте, легко отлаживать.
Минусы: оркестратор — это потенциальное узкое место и единая точка отказа.
КАК РЕАЛИЗОВАТЬ SAGA В PYTHON?
Для хореографии — используй Kafka или RabbitMQ. Каждый сервис — отдельное приложение на FastAPI или Django, которое слушает события. Примерно так:
Для оркестрации — можно использовать библиотеку
ВАЖНЫЙ НЮАНС, О КОТОРОМ ЧАСТО ВРУТ:
Некоторые говорят, что Saga — это просто «транзакция с откатом». НЕТ! Компенсации — это НЕ откат. Они выполняются уже ПОСЛЕ того, как локальная транзакция закоммичена. И они должны быть идемпотентными — если компенсация выполнится дважды, не должно быть ошибки.
КОГДА ЧТО ВЫБРАТЬ?
- Хореографию — для простых цепочек (2-4 шага) и когда важна независимость сервисов.
- Оркестрацию — для сложных процессов с ветвлениями и когда нужен чёткий контроль.
А теперь вопрос к тебе: какой подход ты бы выбрал для своего проекта и почему? Пиши в комментариях! 👇
#python #saga #микросервисы #собеседование #архитектура
Представь: ты — архитектор огромного интернет-магазина. У тебя есть отдельные сервисы: заказы, платежи, склад, доставка. И вот клиент оформляет заказ. Ты создаёшь запись в БД заказов, списываешь деньги, резервируешь товар, отправляешь доставку. Всё бы ничего, но КАЖДЫЙ СЕРВИС ХРАНИТ ДАННЫЕ В СВОЕЙ БАЗЕ. И тут склад падает — товара нет. Деньги списаны, а заказ не выполнен. КАК ОТКАТИТЬ ПЛАТЁЖ? 🤯
В монолите ты бы просто сделал ROLLBACK, и все изменения отменились бы. Но в микросервисах ACID-транзакции между разными БД НЕ РАБОТАЮТ. И тут на сцену выходит SAGA PATTERN — твой спаситель! 🦸♂️
ЧТО ТАКОЕ SAGA?
Это паттерн, который разбивает большую распределённую транзакцию на последовательность маленьких локальных транзакций. Каждый шаг — это отдельная операция в одном сервисе. Если что-то пошло не так, запускаются КОМПЕНСИРУЮЩИЕ ТРАНЗАКЦИИ — они отменяют уже сделанные шаги и возвращают систему в консистентное состояние.
ЕСТЬ ДВА ПОДХОДА:
1️⃣ CHOREOGRAPHY (ХОРЕОГРАФИЯ) — децентрализованный танец без дирижёра. Каждый сервис слушает события (например, через Kafka) и сам решает, что делать дальше. Создал заказ → опубликовал событие OrderCreated → платёжный сервис услышал и списал деньги → опубликовал PaymentReserved → склад услышал и зарезервировал товар... И так по цепочке. Если шаг упал, сервис публикует событие об ошибке, и предыдущие сервисы делают компенсацию.
Плюсы: сервисы полностью независимы, нет единой точки отказа.
Минусы: при длинных цепочках сложно понять, что вообще происходит. А если сервисы зациклились — пиши пропало! 😅
2️⃣ ORCHESTRATION (ОРКЕСТРОВКА) — есть центральный координатор (оркестратор), который управляет всеми шагами. Он говорит: «Платеж, спиши деньги!», «Склад, зарезервируй товар!». Если что-то падает, оркестратор сам запускает компенсации.
Плюсы: весь процесс виден в одном месте, легко отлаживать.
Минусы: оркестратор — это потенциальное узкое место и единая точка отказа.
КАК РЕАЛИЗОВАТЬ SAGA В PYTHON?
Для хореографии — используй Kafka или RabbitMQ. Каждый сервис — отдельное приложение на FastAPI или Django, которое слушает события. Примерно так:
# Сервис заказов (публикует событие)
from kafka import KafkaProducer
producer = KafkaProducer(bootstrap_servers='localhost:9092')
producer.send('orders', key=b'order-123', value=b'OrderCreated')
# Сервис платежей (слушает событие)
from kafka import KafkaConsumer
consumer = KafkaConsumer('orders', bootstrap_servers='localhost:9092')
for msg in consumer:
if msg.value == b'OrderCreated':
# списываем деньги
# публикуем PaymentReserved или PaymentFailed
Для оркестрации — можно использовать библиотеку
saga-pattern или написать свой координатор на asyncio. Например:async def create_order_saga():
try:
await create_order()
await reserve_payment()
await reserve_inventory()
await create_shipping()
except Exception:
await cancel_payment()
await cancel_order()
ВАЖНЫЙ НЮАНС, О КОТОРОМ ЧАСТО ВРУТ:
Некоторые говорят, что Saga — это просто «транзакция с откатом». НЕТ! Компенсации — это НЕ откат. Они выполняются уже ПОСЛЕ того, как локальная транзакция закоммичена. И они должны быть идемпотентными — если компенсация выполнится дважды, не должно быть ошибки.
КОГДА ЧТО ВЫБРАТЬ?
- Хореографию — для простых цепочек (2-4 шага) и когда важна независимость сервисов.
- Оркестрацию — для сложных процессов с ветвлениями и когда нужен чёткий контроль.
А теперь вопрос к тебе: какой подход ты бы выбрал для своего проекта и почему? Пиши в комментариях! 👇
#python #saga #микросервисы #собеседование #архитектура
ТВОЙ asyncio-КОД МОЖЕТ ТИХО УПАСТЬ НА ПРОДЕ, И ТЫ ДАЖЕ НЕ ПОЙМЁШЬ ПОЧЕМУ!
Сколько раз ты писал что-то вроде «async with lock:» и надеялся, что всё будет работать? А потом — бац! — и данные в кэше дублируются, или 1000 запросов летит на сервер, хотя нужно было всего 5. Знакомо? Тогда этот разбор — для тебя.
ДАВАЙ РАЗБЕРЁМСЯ, КАК РЕАЛЬНО РАБОТАЮТ БЛОКИРОВКИ В ASYNCIO.
1️⃣ ПРОБЛЕМА: ПОЧЕМУ ОНИ ВООБЩЕ НУЖНЫ?
asyncio — однопоточный. Казалось бы, откуда взяться гонкам? Но вот в чём фишка: задачи выполняются ПООЧЕРЁДНО, переключаясь в момент await. Представь: две задачи одновременно проверяют кэш. Обе видят, что данных нет. Обе делают await на запрос к сайту. И вот уже ДВА запроса улетели вместо одного!
Это классическая гонка (race condition). Она возникает, когда между проверкой условия и изменением состояния есть точка переключения контекста.
2️⃣ РЕШЕНИЕ: asyncio.Lock
Lock — это как «туалет с замком»: один зашёл, закрылся, другие ждут снаружи. Пока первый не выйдет и не откроет, никто не войдёт.
Вот как это выглядит в коде:
⚠️ КРИТИЧЕСКИЙ МОМЕНТ: блокировка должна захватываться ДО проверки условия! Если ты поставишь async with lock только вокруг самого запроса, гонка останется. Запомни: защищаем ВЕСЬ критический участок, от проверки до обновления.
3️⃣ НО ЕСТЬ НЮАНС: Lock НЕ ограничивает количество
Lock пропускает только ОДНУ задачу. А что если нам нужно разрешить, скажем, 5 одновременных подключений к базе? Тут на сцену выходит Semaphore.
4️⃣ asyncio.Semaphore: ОГРАНИЧИТЕЛЬ ПОТОКА
Semaphore — это как «шлагбаум на парковке»: внутри счётчик. Каждый async with уменьшает счётчик на 1, выход — увеличивает. Когда счётчик = 0, все ждут.
ВОТ ТАК МЫ ОГРАНИЧИВАЕМ НАГРУЗКУ НА ВНЕШНИЙ СЕРВИС И НЕ ЛОВИМ 429-ю ошибку!
5️⃣ ЧАСТЫЕ ОШИБКИ, КОТОРЫЕ ВАЛЯТ НА СОБЕСЕДОВАНИИ
❌ Захват блокировки ВНУТРИ проверки — гонка остаётся.
❌ Забыл освободить Lock (не через async with) — дедлок. Все задачи вечно ждут.
❌ Использование Lock вместо Semaphore, когда нужно N одновременных.
❌ Создание нового Lock на каждую задачу — блокировка не работает вообще.
6️⃣ КАК ОТВЕЧАТЬ НА СОБЕСЕДОВАНИИ?
Расскажи так:
«В asyncio гонки возникают из-за переключения контекста в точках await. Lock гарантирует эксклюзивный доступ к ресурсу, а Semaphore — ограничение количества одновременных доступов. Главное — защищать весь критический участок, а не только его часть».
И добавь пример с кэшем — это покажет, что ты понимаешь, а не зазубрил.
А ТЫ УЖЕ ЛОВИЛ ТАКИЕ БАГИ? ДЕЛИСЬ В КОММЕНТАРИЯХ! 👇
#python #asyncio #собеседование #блокировки #senior
Сколько раз ты писал что-то вроде «async with lock:» и надеялся, что всё будет работать? А потом — бац! — и данные в кэше дублируются, или 1000 запросов летит на сервер, хотя нужно было всего 5. Знакомо? Тогда этот разбор — для тебя.
ДАВАЙ РАЗБЕРЁМСЯ, КАК РЕАЛЬНО РАБОТАЮТ БЛОКИРОВКИ В ASYNCIO.
1️⃣ ПРОБЛЕМА: ПОЧЕМУ ОНИ ВООБЩЕ НУЖНЫ?
asyncio — однопоточный. Казалось бы, откуда взяться гонкам? Но вот в чём фишка: задачи выполняются ПООЧЕРЁДНО, переключаясь в момент await. Представь: две задачи одновременно проверяют кэш. Обе видят, что данных нет. Обе делают await на запрос к сайту. И вот уже ДВА запроса улетели вместо одного!
Это классическая гонка (race condition). Она возникает, когда между проверкой условия и изменением состояния есть точка переключения контекста.
2️⃣ РЕШЕНИЕ: asyncio.Lock
Lock — это как «туалет с замком»: один зашёл, закрылся, другие ждут снаружи. Пока первый не выйдет и не откроет, никто не войдёт.
Вот как это выглядит в коде:
lock = asyncio.Lock()
async def get_value(key):
async with lock: # ждём, пока освободится
if key not in cache:
value = await request_remote()
cache[key] = value
else:
value = cache[key]
return value
⚠️ КРИТИЧЕСКИЙ МОМЕНТ: блокировка должна захватываться ДО проверки условия! Если ты поставишь async with lock только вокруг самого запроса, гонка останется. Запомни: защищаем ВЕСЬ критический участок, от проверки до обновления.
3️⃣ НО ЕСТЬ НЮАНС: Lock НЕ ограничивает количество
Lock пропускает только ОДНУ задачу. А что если нам нужно разрешить, скажем, 5 одновременных подключений к базе? Тут на сцену выходит Semaphore.
4️⃣ asyncio.Semaphore: ОГРАНИЧИТЕЛЬ ПОТОКА
Semaphore — это как «шлагбаум на парковке»: внутри счётчик. Каждый async with уменьшает счётчик на 1, выход — увеличивает. Когда счётчик = 0, все ждут.
sem = asyncio.Semaphore(5) # максимум 5 одновременных
async def fetch(url):
async with sem:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
ВОТ ТАК МЫ ОГРАНИЧИВАЕМ НАГРУЗКУ НА ВНЕШНИЙ СЕРВИС И НЕ ЛОВИМ 429-ю ошибку!
5️⃣ ЧАСТЫЕ ОШИБКИ, КОТОРЫЕ ВАЛЯТ НА СОБЕСЕДОВАНИИ
❌ Захват блокировки ВНУТРИ проверки — гонка остаётся.
❌ Забыл освободить Lock (не через async with) — дедлок. Все задачи вечно ждут.
❌ Использование Lock вместо Semaphore, когда нужно N одновременных.
❌ Создание нового Lock на каждую задачу — блокировка не работает вообще.
6️⃣ КАК ОТВЕЧАТЬ НА СОБЕСЕДОВАНИИ?
Расскажи так:
«В asyncio гонки возникают из-за переключения контекста в точках await. Lock гарантирует эксклюзивный доступ к ресурсу, а Semaphore — ограничение количества одновременных доступов. Главное — защищать весь критический участок, а не только его часть».
И добавь пример с кэшем — это покажет, что ты понимаешь, а не зазубрил.
А ТЫ УЖЕ ЛОВИЛ ТАКИЕ БАГИ? ДЕЛИСЬ В КОММЕНТАРИЯХ! 👇
#python #asyncio #собеседование #блокировки #senior
ТЫ ДУМАЕШЬ, ЧТО ЗНАЕШЬ SOLID? А СЛАБО ОБЪЯСНИТЬ, ПОЧЕМУ ТВОЙ DJANGO-ПРОЕКТ РАЗВАЛИВАЕТСЯ, КОГДА ТЫ МЕНЯЕШЬ ОДНУ СТРОЧКУ В МОДЕЛИ? 😱
Скорее всего, ты нарушаешь Dependency Inversion Principle (DIP) — принцип инверсии зависимостей. И сегодня мы разберем его так, что на собеседовании ты будешь сыпать терминами и примерами, как сеньор с 10-летним стажем. Поехали!
СУТЬ ПРИНЦИПА ОДНОЙ ФРАЗОЙ
Это правило говорит: НЕ ЗАВИСИ ОТ КОНКРЕТИКИ, ЗАВИСИ ОТ АБСТРАКЦИИ. Твой высокоуровневый код (бизнес-логика) не должен знать, как именно работает низкоуровневый код (база данных, API). Они оба должны общаться через интерфейс (абстракцию).
ПОЧЕМУ ЭТО ВАЖНО?
Представь, что твой сервис отправки писем — это розетка. А конкретная реализация (SMTP, Slack, Telegram) — это вилка. Если ты в коде жёстко прописал «шлём через SMTP», то при смене провайдера тебе придётся переписывать всю бизнес-логику. А если ты воткнул переходник (интерфейс), то просто меняешь вилку — и всё работает. 🔌
КАК ЭТО ВЫГЛЯДИТ В DJANGO?
Самый частый анти-паттерн — когда ты в
А теперь — правильный подход. Мы создаём абстракцию (интерфейс) для работы с заказами:
Теперь твоя бизнес-логика зависит от интерфейса
КАК ПРИМЕНИТЬ ЭТО В РЕАЛЬНОМ DJANGO-ПРОЕКТЕ?
1. Выдели интерфейсы для работы с данными. Не таскай
2. Используй внедрение зависимостей (DI). Передавай зависимости в конструктор или метод, а не создавай их внутри. В Django для этого часто используют
3. Для сложной бизнес-логики — отдельный слой. Не пиши всё во
ЧАСТАЯ ОШИБКА НА СОБЕСЕДОВАНИИ
Многие путают DIP с Dependency Injection (DI). Запомни: DIP — это принцип (ЧТО делать), а DI — это паттерн (КАК делать). DIP говорит «зависи от абстракций», а DI предлагает способ это реализовать — передавать зависимости извне.
ПОЧЕМУ ЭТО СПАСЁТ ТВОЙ ПРОЕКТ?
• Тестируемость. Ты можешь подменить реальный репозиторий на фейковый (mock) в тестах, не трогая базу данных.
• Гибкость. Смена технологий становится простой заменой одной реализации на другую.
• Читаемость. Код становится чище, потому что бизнес-логика не засорена деталями работы с БД.
ИТОГ
DIP — это не просто абстрактная теория из книжек. Это реальный инструмент, который делает твой код на Django гибким, тестируемым и готовым к изменениям. Начни с малого — вынеси работу с моделями в репозитории, и ты почувствуешь разницу.
А теперь вопрос к тебе: сколько мест в твоём проекте напрямую обращаются к
#Python #Django #SOLID #DIP #Собеседование #SeniorPython
Скорее всего, ты нарушаешь Dependency Inversion Principle (DIP) — принцип инверсии зависимостей. И сегодня мы разберем его так, что на собеседовании ты будешь сыпать терминами и примерами, как сеньор с 10-летним стажем. Поехали!
СУТЬ ПРИНЦИПА ОДНОЙ ФРАЗОЙ
Это правило говорит: НЕ ЗАВИСИ ОТ КОНКРЕТИКИ, ЗАВИСИ ОТ АБСТРАКЦИИ. Твой высокоуровневый код (бизнес-логика) не должен знать, как именно работает низкоуровневый код (база данных, API). Они оба должны общаться через интерфейс (абстракцию).
ПОЧЕМУ ЭТО ВАЖНО?
Представь, что твой сервис отправки писем — это розетка. А конкретная реализация (SMTP, Slack, Telegram) — это вилка. Если ты в коде жёстко прописал «шлём через SMTP», то при смене провайдера тебе придётся переписывать всю бизнес-логику. А если ты воткнул переходник (интерфейс), то просто меняешь вилку — и всё работает. 🔌
КАК ЭТО ВЫГЛЯДИТ В DJANGO?
Самый частый анти-паттерн — когда ты в
views.py или services.py напрямую дёргаешь модель и её методы. Вот так делать НЕЛЬЗЯ:
# ❌ ПЛОХО: жёсткая зависимость от модели
from .models import Order
def send_invoice(order_id):
order = Order.objects.get(id=order_id)
# ... логика, которая знает про поля модели
А теперь — правильный подход. Мы создаём абстракцию (интерфейс) для работы с заказами:
# ✅ ХОРОШО: зависим от абстракции
from abc import ABC, abstractmethod
class OrderRepository(ABC):
@abstractmethod
def get_by_id(self, order_id):
pass
class DjangoOrderRepository(OrderRepository):
def get_by_id(self, order_id):
from .models import Order
return Order.objects.get(id=order_id)
Теперь твоя бизнес-логика зависит от интерфейса
OrderRepository, а не от конкретной модели. И если завтра ты перейдёшь с PostgreSQL на MongoDB или вообще на внешний API — ты просто создашь новую реализацию интерфейса, и всё продолжит работать! 🚀КАК ПРИМЕНИТЬ ЭТО В РЕАЛЬНОМ DJANGO-ПРОЕКТЕ?
1. Выдели интерфейсы для работы с данными. Не таскай
Model.objects.filter() по всему проекту. Создай репозитории или сервисы.2. Используй внедрение зависимостей (DI). Передавай зависимости в конструктор или метод, а не создавай их внутри. В Django для этого часто используют
django-injector или просто передают зависимости через аргументы функций.3. Для сложной бизнес-логики — отдельный слой. Не пиши всё во
views.py. Вынеси логику в services.py, который будет зависеть от абстракций.ЧАСТАЯ ОШИБКА НА СОБЕСЕДОВАНИИ
Многие путают DIP с Dependency Injection (DI). Запомни: DIP — это принцип (ЧТО делать), а DI — это паттерн (КАК делать). DIP говорит «зависи от абстракций», а DI предлагает способ это реализовать — передавать зависимости извне.
ПОЧЕМУ ЭТО СПАСЁТ ТВОЙ ПРОЕКТ?
• Тестируемость. Ты можешь подменить реальный репозиторий на фейковый (mock) в тестах, не трогая базу данных.
• Гибкость. Смена технологий становится простой заменой одной реализации на другую.
• Читаемость. Код становится чище, потому что бизнес-логика не засорена деталями работы с БД.
ИТОГ
DIP — это не просто абстрактная теория из книжек. Это реальный инструмент, который делает твой код на Django гибким, тестируемым и готовым к изменениям. Начни с малого — вынеси работу с моделями в репозитории, и ты почувствуешь разницу.
А теперь вопрос к тебе: сколько мест в твоём проекте напрямую обращаются к
Model.objects? Если больше десяти — пора рефакторить! 🔥#Python #Django #SOLID #DIP #Собеседование #SeniorPython
ТЕБЕ ГОВОРИЛИ, ЧТО SQLAlchemy САМ ВСЁ СОХРАНЯЕТ? А ПОТОМ ПРОД «ПАДАЕТ» ИЗ-ЗА ПОЛУСОХРАНЁННЫХ ДАННЫХ. 😱
Разберём паттерн Unit of Work — то, что спасает твою базу от хаоса. Это как официант, который запоминает весь заказ и приносит его разом, а не бегает по одному блюду. 🍽️
ЧТО ЭТО ТАКОЕ?
Unit of Work (UoW) — это паттерн, который отслеживает все изменения объектов в памяти и применяет их к базе данных ОДНИМ коммитом. Если что-то пошло не так — делает rollback, и база остаётся в целости. В SQLAlchemy этот паттерн реализован через сессию.
КАК ЭТО РАБОТАЕТ В SQLALCHEMY?
Сессия — твой главный инструмент. Она создаётся через фабрику sessionmaker, привязанную к engine (подключение к БД):
Сессия следит за объектами. Изменил атрибут — она пометила объект как «грязный». Добавил новый — пометила как «новый». И всё это копится, пока ты не вызовешь commit().
ЖИЗНЕННЫЙ ЦИКЛ ОБЪЕКТА
У объектов в сессии есть состояния:
• Transient — создан, но не в сессии и не в БД.
• Pending — добавлен через add(), но не сохранён.
• Persistent — в сессии и в БД.
• Detached — был в сессии, но она закрылась.
Вот как это выглядит на практике:
ПОЧЕМУ ЭТО ВАЖНО?
Представь: ты сохраняешь заказ с товарами. Без UoW ты бы писал каждый товар отдельно, и если бы один не сохранился — получил бы битые данные. А с UoW всё или ничего. Это гарантия атомарности.
КАК ПРАВИЛЬНО РАБОТАТЬ С СЕССИЕЙ?
Всегда используй try/except/finally:
UOW + REPOSITORY = ИДЕАЛЬНАЯ ПАРА
Repository скрывает детали запросов, а UoW управляет транзакциями. Вместе они дают чистую архитектуру: бизнес-логика не знает, как устроена БД, а данные всегда консистентны.
ЧАСТЫЕ ОШИБКИ
• Забываешь закрыть сессию — утечка соединений.
• Делаешь commit после каждой мелочи — теряешь смысл UoW.
• Используешь одну сессию на всё приложение — рискуешь конфликтами.
ИТОГ
Unit of Work — это не просто паттерн, а твой щит от прод-инцидентов. Освой его — и будешь спать спокойно. 😉
#Python #SQLAlchemy #UnitOfWork #Собеседование
Разберём паттерн Unit of Work — то, что спасает твою базу от хаоса. Это как официант, который запоминает весь заказ и приносит его разом, а не бегает по одному блюду. 🍽️
ЧТО ЭТО ТАКОЕ?
Unit of Work (UoW) — это паттерн, который отслеживает все изменения объектов в памяти и применяет их к базе данных ОДНИМ коммитом. Если что-то пошло не так — делает rollback, и база остаётся в целости. В SQLAlchemy этот паттерн реализован через сессию.
КАК ЭТО РАБОТАЕТ В SQLALCHEMY?
Сессия — твой главный инструмент. Она создаётся через фабрику sessionmaker, привязанную к engine (подключение к БД):
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('postgresql://user:pass@localhost/db')
Session = sessionmaker(bind=engine)
session = Session()
Сессия следит за объектами. Изменил атрибут — она пометила объект как «грязный». Добавил новый — пометила как «новый». И всё это копится, пока ты не вызовешь commit().
ЖИЗНЕННЫЙ ЦИКЛ ОБЪЕКТА
У объектов в сессии есть состояния:
• Transient — создан, но не в сессии и не в БД.
• Pending — добавлен через add(), но не сохранён.
• Persistent — в сессии и в БД.
• Detached — был в сессии, но она закрылась.
Вот как это выглядит на практике:
new_user = User(name='Alice')
# Transient
session.add(new_user)
# Pending
session.commit()
# Persistent
session.close()
# Detached
ПОЧЕМУ ЭТО ВАЖНО?
Представь: ты сохраняешь заказ с товарами. Без UoW ты бы писал каждый товар отдельно, и если бы один не сохранился — получил бы битые данные. А с UoW всё или ничего. Это гарантия атомарности.
КАК ПРАВИЛЬНО РАБОТАТЬ С СЕССИЕЙ?
Всегда используй try/except/finally:
try:
session.commit()
except:
session.rollback()
raise
finally:
session.close()
UOW + REPOSITORY = ИДЕАЛЬНАЯ ПАРА
Repository скрывает детали запросов, а UoW управляет транзакциями. Вместе они дают чистую архитектуру: бизнес-логика не знает, как устроена БД, а данные всегда консистентны.
ЧАСТЫЕ ОШИБКИ
• Забываешь закрыть сессию — утечка соединений.
• Делаешь commit после каждой мелочи — теряешь смысл UoW.
• Используешь одну сессию на всё приложение — рискуешь конфликтами.
ИТОГ
Unit of Work — это не просто паттерн, а твой щит от прод-инцидентов. Освой его — и будешь спать спокойно. 😉
#Python #SQLAlchemy #UnitOfWork #Собеседование
Твой Python-сервис жив? А ты УВЕРЕН? 😱
Вот ты деплоишь контейнер, все порты открыты, логи пишутся... Но твой сервис может быть МЕРТВ, а Docker об этом даже не узнает! Почему? Потому что контейнер запущен, но приложение внутри — упало или зависло. И вот тут на сцену выходит он — HEALTHCHECK! 🦸♂️
ПРЕДСТАВЬ: ты заказал пиццу. Курьер приехал, отдал коробку — и уехал. А пицца внутри — холодная и невкусная. Docker без healthcheck — это курьер, который привез коробку и не проверил, что внутри. А healthcheck — это курьер, который открывает коробку и проверяет: «Пицца горячая? Сыр тянется?» Если нет — бьет тревогу! 🚨
ЧТО ТАКОЕ HEALTHCHECK?
Это команда в Dockerfile, которая говорит Docker: «Периодически выполняй вот эту проверку. Если она провалится N раз подряд — контейнер болен, лечи или перезапускай!»
СИНТАКСИС ПРОСТОЙ:
ГЛАВНЫЕ ПАРАМЕТРЫ:
•
•
•
•
КАК НАСТРОИТЬ ДЛЯ PYTHON-СЕРВИСА?
Допустим, у тебя FastAPI или Flask. Самый простой и надежный способ — проверить, отвечает ли HTTP-эндпоинт. Например, специальный
ВОТ ТВОЙ DOCKERFILE:
РАЗБИРАЕМ ПО КОСТОЧКАМ:
1.
2.
3. Если запрос упал (сервис не отвечает) — Python выбросит исключение, и команда завершится с ошибкой. А это значит — контейнер нездоров!
4.
А ЧТО НА СЧЕТ /health В КОДЕ?
В FastAPI это одна строка:
НО! Не делай проверку «просто верни 200». Проверяй то, что реально важно! Например, доступность базы данных:
ВОТ ЭТО УЖЕ ПО-ВЗРОСЛОМУ! 😎
КАК DOCKER РЕАГИРУЕТ?
У контейнера три состояния:
•
•
•
ВАЖНЫЙ МОМЕНТ! Docker сам НЕ перезапускает больной контейнер! Он просто помечает его как unhealthy. Чтобы он перезапустился, нужен оркестратор (Kubernetes, Docker Swarm) или настройки в docker-compose.
В DOCKER-COMPOSE ЭТО ВЫГЛЯДИТ ТАК:
ВОТ ТЕПЕРЬ ТВОЙ СЕРВИС ПОД НАДЕЖНОЙ ЗАЩИТОЙ! 🛡️
ЗАПОМНИ ГЛАВНОЕ:
• Healthcheck — это не про «процесс запущен», а про «сервис реально работает»
• Проверяй зависимости (БД, кэш, внешние API)
• Не забывай про
А теперь вопрос: у тебя в проде настроен healthcheck? Если нет — БЕГОМ ИСПРАВЛЯТЬ! 🏃♂️💨
#docker #python #devops #healthcheck #собеседование
Вот ты деплоишь контейнер, все порты открыты, логи пишутся... Но твой сервис может быть МЕРТВ, а Docker об этом даже не узнает! Почему? Потому что контейнер запущен, но приложение внутри — упало или зависло. И вот тут на сцену выходит он — HEALTHCHECK! 🦸♂️
ПРЕДСТАВЬ: ты заказал пиццу. Курьер приехал, отдал коробку — и уехал. А пицца внутри — холодная и невкусная. Docker без healthcheck — это курьер, который привез коробку и не проверил, что внутри. А healthcheck — это курьер, который открывает коробку и проверяет: «Пицца горячая? Сыр тянется?» Если нет — бьет тревогу! 🚨
ЧТО ТАКОЕ HEALTHCHECK?
Это команда в Dockerfile, которая говорит Docker: «Периодически выполняй вот эту проверку. Если она провалится N раз подряд — контейнер болен, лечи или перезапускай!»
СИНТАКСИС ПРОСТОЙ:
HEALTHCHECK [OPTIONS] CMD команда_проверки
ГЛАВНЫЕ ПАРАМЕТРЫ:
•
--interval=30s — как часто проверяем (по умолчанию 30 сек)•
--timeout=5s — сколько ждем ответа от проверки (по умолчанию 30 сек)•
--retries=3 — сколько провалов подряд, чтобы признать контейнер больным (по умолчанию 3)•
--start-period=10s — даем приложению время на запуск, проверки не начинаются (по умолчанию 0)КАК НАСТРОИТЬ ДЛЯ PYTHON-СЕРВИСА?
Допустим, у тебя FastAPI или Flask. Самый простой и надежный способ — проверить, отвечает ли HTTP-эндпоинт. Например, специальный
/health.ВОТ ТВОЙ DOCKERFILE:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# Добавляем healthcheck!
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
РАЗБИРАЕМ ПО КОСТОЧКАМ:
1.
python -c "..." — выполняем Python-код прямо в команде2.
urllib.request.urlopen(...) — делаем GET-запрос к нашему эндпоинту3. Если запрос упал (сервис не отвечает) — Python выбросит исключение, и команда завершится с ошибкой. А это значит — контейнер нездоров!
4.
|| exit 1 — на всякий случай явно выходим с кодом 1, чтобы Docker точно понял: все плохоА ЧТО НА СЧЕТ /health В КОДЕ?
В FastAPI это одна строка:
@app.get("/health")
async def health():
return {"status": "ok"}НО! Не делай проверку «просто верни 200». Проверяй то, что реально важно! Например, доступность базы данных:
@app.get("/health")
async def health():
try:
# Проверяем, что БД отвечает
await db.execute("SELECT 1")
return {"status": "ok"}
except Exception:
# Если БД лежит — сервис болен!
raise HTTPException(status_code=503)ВОТ ЭТО УЖЕ ПО-ВЗРОСЛОМУ! 😎
КАК DOCKER РЕАГИРУЕТ?
У контейнера три состояния:
•
healthy — все ок•
unhealthy — проверка провалена•
starting — идет старт, проверки еще не начались (в новых версиях Docker)ВАЖНЫЙ МОМЕНТ! Docker сам НЕ перезапускает больной контейнер! Он просто помечает его как unhealthy. Чтобы он перезапустился, нужен оркестратор (Kubernetes, Docker Swarm) или настройки в docker-compose.
В DOCKER-COMPOSE ЭТО ВЫГЛЯДИТ ТАК:
services:
app:
build: .
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped
ВОТ ТЕПЕРЬ ТВОЙ СЕРВИС ПОД НАДЕЖНОЙ ЗАЩИТОЙ! 🛡️
ЗАПОМНИ ГЛАВНОЕ:
• Healthcheck — это не про «процесс запущен», а про «сервис реально работает»
• Проверяй зависимости (БД, кэш, внешние API)
• Не забывай про
start_period, иначе получишь ложные срабатывания при стартеА теперь вопрос: у тебя в проде настроен healthcheck? Если нет — БЕГОМ ИСПРАВЛЯТЬ! 🏃♂️💨
#docker #python #devops #healthcheck #собеседование
ДРУГ, ТЫ ДО СИХ ПОР ДУМАЕШЬ, ЧТО async/await В ТЕСТАХ — ЭТО МАГИЧЕСКАЯ КНОПКА УСКОРЕНИЯ? 🚀
Спойлер: нет. Но когда это реально нужно и как правильно настроить — сейчас разберём. Готовься, будет горячо!
СНАЧАЛА — ЗАЧЕМ ВООБЩЕ АСИНХРОННЫЕ ТЕСТЫ?
Представь: твой тест ждёт ответ от API 2 секунды. В это время процессор простаивает. Асинхронность позволяет в это время выполнять другой код. Но ВАЖНО: async/await в Python работает в ОДНОМ потоке! Это не параллелизм, а кооперативная многозадачность. Пока одна корутина ждёт ответа, другая начинает выполняться.
НО ЕСТЬ НЮАНС: если твой код синхронный (например, requests вместо httpx), то асинхронность НЕ ДАСТ НИЧЕГО. Всё упрётся в блокирующий вызов. Поэтому для асинхронных тестов нужны асинхронные библиотеки: httpx.AsyncClient, aiohttp, asyncpg и т.д.
КАК ЭТО ВЫГЛЯДИТ НА ПРАКТИКЕ?
Ставим плагин:
Теперь пишем тест:
Всё просто? Да! Но есть подводные камни.
ПОДВОДНЫЙ КАМЕНЬ №1: ФИКСТУРЫ
Если у тебя синхронные фикстуры, они будут блокировать event loop. Решение — асинхронные фикстуры:
Обрати внимание: фикстура помечена @pytest.fixture, а не @pytest_asyncio.fixture. Но если тест асинхронный, pytest-asyncio сам подхватит асинхронную фикстуру. Хотя для ясности лучше использовать @pytest_asyncio.fixture.
ПОДВОДНЫЙ КАМЕНЬ №2: EVENT LOOP
По умолчанию pytest-asyncio создаёт новый event loop для каждого теста. Это безопасно, но медленно. Можно переиспользовать loop, задав в pytest.ini:
Или:
В режиме auto тесты с async автоматически помечаются, не нужно писать @pytest.mark.asyncio. Но будь осторожен: в strict нужно явно указывать.
ПОДВОДНЫЙ КАМЕНЬ №3: ПАРАЛЛЕЛИЗМ
Асинхронность ≠ параллельность. Если ты хочешь реально ускорить прогон, используй pytest-xdist для параллельного запуска. Но тогда каждый воркер имеет свой event loop. Это ок.
КОГДА АСИНХРОННОСТЬ РЕАЛЬНО НУЖНА?
- Если ты тестируешь асинхронный код (FastAPI, aiohttp, asyncpg).
- Если у тебя много IO-bound операций (сетевые запросы, чтение файлов) и ты хочешь выполнять их конкурентно внутри одного теста.
- Если ты используешь Playwright для UI — он имеет async API.
А ЕСЛИ НЕТ? НЕ ПАРЬСЯ!
Если твой код синхронный, асинхронные тесты только добавят головной боли. Лучше использовать обычные тесты и параллелить их через pytest-xdist. Это даст реальный прирост.
ИТОГ: КАК ОТВЕТИТЬ НА СОБЕСЕДОВАНИИ?
1. Скажи: "Асинхронные тесты полезны, когда тестируемый код сам асинхронный, или когда нужно конкурентно выполнять IO-операции. Для этого использую pytest-asyncio и httpx.AsyncClient."
2. Покажи пример с фикстурой.
3. Добавь: "Но важно помнить, что async/await не делает тесты параллельными, это кооперативная многозадачность в одном потоке. Для реального параллелизма использую pytest-xdist."
ВОТ ТАК! ТЕПЕРЬ ТЫ ВООРУЖЁН. А КАКИЕ ПОДВОДНЫЕ КАМНИ ВСТРЕЧАЛ ТЫ? ПИШИ В КОММЕНТАРИЯХ! 👇
#python #pytest #async #тестирование #собеседование
Спойлер: нет. Но когда это реально нужно и как правильно настроить — сейчас разберём. Готовься, будет горячо!
СНАЧАЛА — ЗАЧЕМ ВООБЩЕ АСИНХРОННЫЕ ТЕСТЫ?
Представь: твой тест ждёт ответ от API 2 секунды. В это время процессор простаивает. Асинхронность позволяет в это время выполнять другой код. Но ВАЖНО: async/await в Python работает в ОДНОМ потоке! Это не параллелизм, а кооперативная многозадачность. Пока одна корутина ждёт ответа, другая начинает выполняться.
НО ЕСТЬ НЮАНС: если твой код синхронный (например, requests вместо httpx), то асинхронность НЕ ДАСТ НИЧЕГО. Всё упрётся в блокирующий вызов. Поэтому для асинхронных тестов нужны асинхронные библиотеки: httpx.AsyncClient, aiohttp, asyncpg и т.д.
КАК ЭТО ВЫГЛЯДИТ НА ПРАКТИКЕ?
Ставим плагин:
pip install pytest-asyncio httpx
Теперь пишем тест:
import pytest
import httpx
@pytest.mark.asyncio
async def test_get_user():
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/user/1")
assert response.status_code == 200
assert response.json()["name"] == "Alice"
Всё просто? Да! Но есть подводные камни.
ПОДВОДНЫЙ КАМЕНЬ №1: ФИКСТУРЫ
Если у тебя синхронные фикстуры, они будут блокировать event loop. Решение — асинхронные фикстуры:
@pytest.fixture
async def client():
async with httpx.AsyncClient() as c:
yield c
@pytest.mark.asyncio
async def test_get_user(client):
response = await client.get("/user/1")
assert response.status_code == 200
Обрати внимание: фикстура помечена @pytest.fixture, а не @pytest_asyncio.fixture. Но если тест асинхронный, pytest-asyncio сам подхватит асинхронную фикстуру. Хотя для ясности лучше использовать @pytest_asyncio.fixture.
ПОДВОДНЫЙ КАМЕНЬ №2: EVENT LOOP
По умолчанию pytest-asyncio создаёт новый event loop для каждого теста. Это безопасно, но медленно. Можно переиспользовать loop, задав в pytest.ini:
[pytest]
asyncio_mode = auto
Или:
asyncio_mode = strict
В режиме auto тесты с async автоматически помечаются, не нужно писать @pytest.mark.asyncio. Но будь осторожен: в strict нужно явно указывать.
ПОДВОДНЫЙ КАМЕНЬ №3: ПАРАЛЛЕЛИЗМ
Асинхронность ≠ параллельность. Если ты хочешь реально ускорить прогон, используй pytest-xdist для параллельного запуска. Но тогда каждый воркер имеет свой event loop. Это ок.
КОГДА АСИНХРОННОСТЬ РЕАЛЬНО НУЖНА?
- Если ты тестируешь асинхронный код (FastAPI, aiohttp, asyncpg).
- Если у тебя много IO-bound операций (сетевые запросы, чтение файлов) и ты хочешь выполнять их конкурентно внутри одного теста.
- Если ты используешь Playwright для UI — он имеет async API.
А ЕСЛИ НЕТ? НЕ ПАРЬСЯ!
Если твой код синхронный, асинхронные тесты только добавят головной боли. Лучше использовать обычные тесты и параллелить их через pytest-xdist. Это даст реальный прирост.
ИТОГ: КАК ОТВЕТИТЬ НА СОБЕСЕДОВАНИИ?
1. Скажи: "Асинхронные тесты полезны, когда тестируемый код сам асинхронный, или когда нужно конкурентно выполнять IO-операции. Для этого использую pytest-asyncio и httpx.AsyncClient."
2. Покажи пример с фикстурой.
3. Добавь: "Но важно помнить, что async/await не делает тесты параллельными, это кооперативная многозадачность в одном потоке. Для реального параллелизма использую pytest-xdist."
ВОТ ТАК! ТЕПЕРЬ ТЫ ВООРУЖЁН. А КАКИЕ ПОДВОДНЫЕ КАМНИ ВСТРЕЧАЛ ТЫ? ПИШИ В КОММЕНТАРИЯХ! 👇
#python #pytest #async #тестирование #собеседование