🚀 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
ТЫ ДУМАЕШЬ, ЧТО ЗНАЕШЬ ORM? А ПОЧЕМУ ТВОЙ DJANGO-ПРОЕКТ ТОРМОЗИТ, А SQLALCHEMY ЛЕТАЕТ? ДАВАЙ РАЗБЕРЕМСЯ.
Оба инструмента — это прослойка между твоим кодом и базой данных. Они превращают SQL-запросы в Python-объекты. Но подходы — разные, как молоток и шуруповерт. Оба забивают гвозди, но один делает это быстрее и удобнее для конкретной задачи.
Django ORM — это "всё включено". Он часть монолитного фреймворка. Его главная фишка — магия. Ты пишешь
НО! Как только задача выходит за рамки "выбрать всех пользователей", начинается боль. Хочешь оконную функцию? А сложный подзапрос с CTE? А полный outer join? Django ORM скажет: "Извини, брат, пиши сырой SQL через
Главная ловушка для новичка — N+1 проблема. Пример:
Здесь на каждый заказ летит отдельный запрос к таблице customer. 100 заказов = 101 запрос. База падает. Решение —
SQLAlchemy — это "конструктор". Он не навязывает структуру. Ты сам решаешь, как описать таблицы, какие связи сделать, как оптимизировать запросы. Он даёт тебе полный контроль.
Главное преимущество — выразительная сила. Хочешь оконную функцию? Пожалуйста:
Хочешь рекурсивный запрос? SQLAlchemy поддерживает CTE. Хочешь сложный JOIN? Без проблем.
Но за гибкость приходится платить. Кода больше. Нужно явно создавать сессии, управлять транзакциями, настраивать связи. Для простого блога это оверхед.
Ключевые отличия:
• Подход: Django — "магия", SQLAlchemy — "явность".
• Гибкость: Django ORM ограничен для сложных запросов, SQLAlchemy — швейцарский нож.
• Производительность: SQLAlchemy даёт больше контроля над оптимизацией (ленивая/жадная загрузка, пайплайны).
• Связь с фреймворком: Django ORM — часть Django, SQLAlchemy — независим (можно с FastAPI, Flask, Aiohttp).
• Асинхронность: Django ORM синхронный (async-поддержка незрелая), SQLAlchemy имеет
Когда что выбирать?
• Django ORM — если проект типовой, нужна быстрая разработка, админка "из коробки", и запросы не сложнее "выбрать по id".
• SQLAlchemy — если проект сложный, с нетривиальными запросами, микросервисы, или нужна асинхронность.
Итог: Django ORM — это "Фольксваген Жук": простой, надежный, но для гонок не подходит. SQLAlchemy — это "Tesla": мощный, гибкий, но требует умелого водителя. На собеседовании покажи, что понимаешь разницу, и ты пройдешь.
Оба инструмента — это прослойка между твоим кодом и базой данных. Они превращают SQL-запросы в Python-объекты. Но подходы — разные, как молоток и шуруповерт. Оба забивают гвозди, но один делает это быстрее и удобнее для конкретной задачи.
Django ORM — это "всё включено". Он часть монолитного фреймворка. Его главная фишка — магия. Ты пишешь
User.objects.filter(name='John'), и он сам строит SQL. Он тесно связан с моделями, миграциями и админкой. Это идеально для стандартных CRUD-приложений (блоги, магазины, админки).НО! Как только задача выходит за рамки "выбрать всех пользователей", начинается боль. Хочешь оконную функцию? А сложный подзапрос с CTE? А полный outer join? Django ORM скажет: "Извини, брат, пиши сырой SQL через
RawSQL". И это убивает переносимость между базами.Главная ловушка для новичка — N+1 проблема. Пример:
for order in Order.objects.all():
print(order.customer.name)
Здесь на каждый заказ летит отдельный запрос к таблице customer. 100 заказов = 101 запрос. База падает. Решение —
select_related и prefetch_related, но о них часто забывают.SQLAlchemy — это "конструктор". Он не навязывает структуру. Ты сам решаешь, как описать таблицы, какие связи сделать, как оптимизировать запросы. Он даёт тебе полный контроль.
Главное преимущество — выразительная сила. Хочешь оконную функцию? Пожалуйста:
from sqlalchemy import func
query = session.query(
User,
func.row_number().over(order_by=User.id)
)
Хочешь рекурсивный запрос? SQLAlchemy поддерживает CTE. Хочешь сложный JOIN? Без проблем.
Но за гибкость приходится платить. Кода больше. Нужно явно создавать сессии, управлять транзакциями, настраивать связи. Для простого блога это оверхед.
Ключевые отличия:
• Подход: Django — "магия", SQLAlchemy — "явность".
• Гибкость: Django ORM ограничен для сложных запросов, SQLAlchemy — швейцарский нож.
• Производительность: SQLAlchemy даёт больше контроля над оптимизацией (ленивая/жадная загрузка, пайплайны).
• Связь с фреймворком: Django ORM — часть Django, SQLAlchemy — независим (можно с FastAPI, Flask, Aiohttp).
• Асинхронность: Django ORM синхронный (async-поддержка незрелая), SQLAlchemy имеет
asyncio-версию (через asyncpg).Когда что выбирать?
• Django ORM — если проект типовой, нужна быстрая разработка, админка "из коробки", и запросы не сложнее "выбрать по id".
• SQLAlchemy — если проект сложный, с нетривиальными запросами, микросервисы, или нужна асинхронность.
Итог: Django ORM — это "Фольксваген Жук": простой, надежный, но для гонок не подходит. SQLAlchemy — это "Tesla": мощный, гибкий, но требует умелого водителя. На собеседовании покажи, что понимаешь разницу, и ты пройдешь.
Твой API-тест падает на проде, а локально — зелёный? 😱
Скорее всего, ты просто не умеешь тестировать HTTP. Давай разберём, как делать это правильно с pytest и httpx, чтобы спать спокойно.
Почему httpx, а не requests?
Потому что httpx — это современный стандарт. Он умеет и синхронные, и асинхронные запросы. А requests — legacy, который не развивается. Если на собеседовании скажешь «я использую requests», тебя вежливо попросят на выход.
База: синхронный тест
Ставим httpx:
Пишем тест:
Всё просто. Но это детский сад. В реальном мире тебе нужно:
• Проверять заголовки (Content-Type, Cache-Control).
• Проверять время ответа (чтобы не было > 500 мс).
• Проверять схему ответа (pydantic или jsonschema).
• Использовать фикстуры, чтобы не дублировать клиент.
Правильная структура с фикстурой
Фикстура создаёт клиент один раз на сессию (если scope='session'), а не для каждого теста. Это экономит время и ресурсы.
Асинхронные тесты — когда нужно ускориться
Если у тебя 100 тестов, каждый ждёт ответа по 0.5 секунды — синхронно это 50 секунд. Асинхронно можно запустить несколько запросов параллельно и получить 10 секунд. Но это не магия: нужно ставить
Важно! Асинхронность не делает тесты параллельными в полном смысле. Она просто позволяет переключаться между задачами во время ожидания I/O. Если твой сервер отвечает медленно, асинхронность не поможет — он всё равно будет узким местом.
Подводные камни, о которых молчат
1. Фикстуры тоже должны быть асинхронными. Если ты используешь
2. Не смешивай синхронный и асинхронный код в одном тесте. Либо всё синхронно, либо всё асинхронно. Иначе получишь RuntimeError.
3. Таймауты. В httpx по умолчанию таймаута нет. Если сервер завис, тест будет висеть вечно. Всегда ставь таймаут:
Как это проверят на собеседовании
Тебе дадут кусок кода с багами и попросят найти ошибки. Типичные:
• Нет проверки статус-кода.
• Нет обработки исключений (например,
• Использование глобального клиента без фикстуры.
• Смешивание async и sync.
Резюме
• Для тестов API используй httpx + pytest.
• Синхронные тесты — для простоты, асинхронные — когда нужно ускорение.
• Всегда используй фикстуры и таймауты.
• Проверяй не только статус, но и тело, заголовки, время.
А теперь иди и напиши тест, который не стыдно показать на собеседовании! 🚀
Скорее всего, ты просто не умеешь тестировать HTTP. Давай разберём, как делать это правильно с pytest и httpx, чтобы спать спокойно.
Почему httpx, а не requests?
Потому что httpx — это современный стандарт. Он умеет и синхронные, и асинхронные запросы. А requests — legacy, который не развивается. Если на собеседовании скажешь «я использую requests», тебя вежливо попросят на выход.
База: синхронный тест
Ставим httpx:
pip install httpx pytestПишем тест:
import httpx
def test_get_user():
response = httpx.get('https://api.example.com/users/1')
assert response.status_code == 200
assert response.json()['name'] == 'John'
Всё просто. Но это детский сад. В реальном мире тебе нужно:
• Проверять заголовки (Content-Type, Cache-Control).
• Проверять время ответа (чтобы не было > 500 мс).
• Проверять схему ответа (pydantic или jsonschema).
• Использовать фикстуры, чтобы не дублировать клиент.
Правильная структура с фикстурой
import pytest
import httpx
@pytest.fixture
def client():
with httpx.Client(base_url='https://api.example.com') as client:
yield client
def test_get_user(client):
response = client.get('/users/1')
assert response.status_code == 200
Фикстура создаёт клиент один раз на сессию (если scope='session'), а не для каждого теста. Это экономит время и ресурсы.
Асинхронные тесты — когда нужно ускориться
Если у тебя 100 тестов, каждый ждёт ответа по 0.5 секунды — синхронно это 50 секунд. Асинхронно можно запустить несколько запросов параллельно и получить 10 секунд. Но это не магия: нужно ставить
pytest-asyncio и использовать AsyncClient.import pytest
import httpx
@pytest.mark.asyncio
async def test_get_user_async():
async with httpx.AsyncClient(base_url='https://api.example.com') as client:
response = await client.get('/users/1')
assert response.status_code == 200
Важно! Асинхронность не делает тесты параллельными в полном смысле. Она просто позволяет переключаться между задачами во время ожидания I/O. Если твой сервер отвечает медленно, асинхронность не поможет — он всё равно будет узким местом.
Подводные камни, о которых молчат
1. Фикстуры тоже должны быть асинхронными. Если ты используешь
AsyncClient внутри синхронной фикстуры — получишь ошибку. Фикстура должна быть объявлена как async def и использовать yield.2. Не смешивай синхронный и асинхронный код в одном тесте. Либо всё синхронно, либо всё асинхронно. Иначе получишь RuntimeError.
3. Таймауты. В httpx по умолчанию таймаута нет. Если сервер завис, тест будет висеть вечно. Всегда ставь таймаут:
client = httpx.Client(timeout=10.0)
Как это проверят на собеседовании
Тебе дадут кусок кода с багами и попросят найти ошибки. Типичные:
• Нет проверки статус-кода.
• Нет обработки исключений (например,
httpx.RequestError).• Использование глобального клиента без фикстуры.
• Смешивание async и sync.
Резюме
• Для тестов API используй httpx + pytest.
• Синхронные тесты — для простоты, асинхронные — когда нужно ускорение.
• Всегда используй фикстуры и таймауты.
• Проверяй не только статус, но и тело, заголовки, время.
А теперь иди и напиши тест, который не стыдно показать на собеседовании! 🚀
ТВОЙ
Сколько раз ты писал
Что такое logging?
Это встроенный модуль Python, который позволяет записывать события программы в консоль или файл. В отличие от
- Показывать время события
- Указывать уровень важности (отладка, ошибка, критическая)
- Писать сразу в несколько мест (консоль + файл)
- Фильтровать сообщения (выводить только ошибки, а отладочные прятать)
5 уровней логирования (от спокойного до паники):
•
•
•
•
•
Как это работает?
По умолчанию выводятся только сообщения уровня WARNING и выше. Остальные молчат. Но ты можешь настроить логгер под себя.
Простой пример:
Вывод:
А
Почему это лучше print()?
Представь: ты дебажишь код, вставил 10
Как писать в файл?
Теперь все INFO и выше — в файл
Совет сеньора:
Никогда не используй
А ты уже перешел на logging или всё ещё сидишь на
print() УМИРАЕТ, А ТЫ ДАЖЕ НЕ ЗНАЕШЬ? 😱Сколько раз ты писал
print("тут ошибка") и потом удалял эти строки перед коммитом? А потом на проде баг, и ты не понимаешь, что произошло. Знакомо? Пора переходить на нормальное логирование! Модуль logging — это твой спасательный круг.Что такое logging?
Это встроенный модуль Python, который позволяет записывать события программы в консоль или файл. В отличие от
print(), он умеет:- Показывать время события
- Указывать уровень важности (отладка, ошибка, критическая)
- Писать сразу в несколько мест (консоль + файл)
- Фильтровать сообщения (выводить только ошибки, а отладочные прятать)
5 уровней логирования (от спокойного до паники):
•
DEBUG (10) — мелочи для разработчика: "зашли в функцию, значение переменной = 5"•
INFO (20) — всё ок: "пользователь залогинился"•
WARNING (30) — что-то подозрительное: "диск почти заполнен"•
ERROR (40) — ошибка, но программа жива: "не удалось загрузить файл"•
CRITICAL (50) — всё пропало: "база данных недоступна"Как это работает?
По умолчанию выводятся только сообщения уровня WARNING и выше. Остальные молчат. Но ты можешь настроить логгер под себя.
Простой пример:
import logging
logging.warning("Это предупреждение!")
logging.error("А это ошибка")
Вывод:
WARNING:root:Это предупреждение!
ERROR:root:А это ошибка
А
logging.debug() и logging.info() не покажутся — они ниже порога. Чтобы их увидеть, нужно настроить уровень:logging.basicConfig(level=logging.DEBUG)
logging.debug("Теперь видно!")
Почему это лучше print()?
Представь: ты дебажишь код, вставил 10
print(). Потом забыл один удалить, и на проде пользователь видит "значение x = 42". Кошмар! А с logging ты просто ставишь уровень DEBUG при разработке, а на проде — INFO или WARNING. Отладочные сообщения автоматически исчезают. И никакого мусора.Как писать в файл?
logging.basicConfig(filename='app.log', level=logging.INFO)
logging.info('Программа запущена')
Теперь все INFO и выше — в файл
app.log. Можно анализировать потом.Совет сеньора:
Никогда не используй
print() для логирования в продакшене. Это как лечить перелом пластырем. Используй logging с самого начала — и твой код скажет тебе спасибо.А ты уже перешел на logging или всё ещё сидишь на
print()? Пиши в комментариях! 👇🚀 ТВОЙ КОД ТОРМОЗИТ, А ТЫ НЕ ЗНАЕШЬ ГДЕ? cProfile — твой личный детектив!
Представь: ты написал идеальный, на первый взгляд, скрипт. Но на проде он вдруг начинает «думать» по 10 минут. Ты в панике лезешь в код, ставишь
Знакомься — cProfile. Это встроенный профилировщик Python, который покажет тебе ВСЕ узкие места. Он не гадает, а собирает точные данные: сколько раз вызывалась каждая функция, сколько времени занял каждый вызов и где именно в коде это произошло.
Как это работает? Очень просто. Запускаешь свой скрипт через cProfile, и он, как шпион, записывает каждое событие: вызов функции, возврат результата, исключение. Это называется детерминированное профилирование — он не пропускает ничего. Минус? Накладные расходы. Если у тебя миллион вызовов, профилировщик сам может замедлить работу. Но для поиска «горячих точек» — самое то.
Давай на примере. Допустим, у тебя есть функция, которая что-то считает:
Что мы видим?
Как интерпретировать?
• tottime большой — функция сама по себе тяжелая. Ищи внутри циклы, сложные вычисления, ненужные операции.
• cumtime большой, а tottime маленький — проблема в дочерних вызовах. Например, функция вызывает другую функцию 100500 раз. Оптимизируй количество вызовов.
• ncalls огромное — возможно, ты вызываешь функцию в цикле, хотя мог бы вынести вычисления наружу.
Кстати, в PySpark cProfile тоже работает, но с нюансами. Из-за того, что Spark транслирует Python в JVM через Py4J, профилировать нужно именно Python-часть (UDF, RDD-операции). Для этого используй
Главный совет: не оптимизируй вслепую. Сначала профилируй, потом правь. Иначе рискуешь потратить часы на ускорение того, что и так летает.
А ты уже использовал cProfile? Или всё ещё гадаешь на кофейной гуще? Пиши в комментариях! 👇
Представь: ты написал идеальный, на первый взгляд, скрипт. Но на проде он вдруг начинает «думать» по 10 минут. Ты в панике лезешь в код, ставишь
print() повсюду… Стоп! Есть способ в 100 раз эффективнее.Знакомься — cProfile. Это встроенный профилировщик Python, который покажет тебе ВСЕ узкие места. Он не гадает, а собирает точные данные: сколько раз вызывалась каждая функция, сколько времени занял каждый вызов и где именно в коде это произошло.
Как это работает? Очень просто. Запускаешь свой скрипт через cProfile, и он, как шпион, записывает каждое событие: вызов функции, возврат результата, исключение. Это называется детерминированное профилирование — он не пропускает ничего. Минус? Накладные расходы. Если у тебя миллион вызовов, профилировщик сам может замедлить работу. Но для поиска «горячих точек» — самое то.
Давай на примере. Допустим, у тебя есть функция, которая что-то считает:
import cProfile, pstats
def slow_function():
total = 0
for i in range(1000000):
total += i ** 2
return total
cProfile.run('slow_function()', 'output_stats')
p = pstats.Stats('output_stats')
p.sort_stats('cumtime').print_stats(10)
Что мы видим?
cumtime — это кумулятивное время, то есть общее время работы функции вместе со всеми вложенными вызовами. tottime — время, потраченное только внутри самой функции, без учета вложенных. ncalls — количество вызовов. Если какая-то функция имеет огромное tottime или ncalls — вот она, твоя проблема!Как интерпретировать?
• tottime большой — функция сама по себе тяжелая. Ищи внутри циклы, сложные вычисления, ненужные операции.
• cumtime большой, а tottime маленький — проблема в дочерних вызовах. Например, функция вызывает другую функцию 100500 раз. Оптимизируй количество вызовов.
• ncalls огромное — возможно, ты вызываешь функцию в цикле, хотя мог бы вынести вычисления наружу.
Кстати, в PySpark cProfile тоже работает, но с нюансами. Из-за того, что Spark транслирует Python в JVM через Py4J, профилировать нужно именно Python-часть (UDF, RDD-операции). Для этого используй
spark.sparkContext.setSystemProperty('spark.python.profile', 'true') и потом смотри результаты через spark.sparkContext.show_profiles().Главный совет: не оптимизируй вслепую. Сначала профилируй, потом правь. Иначе рискуешь потратить часы на ускорение того, что и так летает.
А ты уже использовал cProfile? Или всё ещё гадаешь на кофейной гуще? Пиши в комментариях! 👇
ТЫ ДУМАЕШЬ, ЧТО GC В PYTHON — ЭТО МАГИЯ? А ВОТ И НЕТ! 😱
Сколько раз ты писал код, запускал его, а память росла как на дрожжах? Или наоборот — объекты исчезали, когда ты их ещё ждал? Всё дело в сборщике мусора (GC). Давай разберёмся, как он работает и как его настраивать, чтобы твой код летал, а не тормозил.
КАК ЭТО РАБОТАЕТ?
Python использует ДВА механизма:
1️⃣ Подсчёт ссылок — это база. Каждый объект хранит счётчик, сколько раз на него ссылаются. Как только счётчик падает до нуля — объект уничтожается. Просто и быстро. Но есть нюанс: циклические ссылки (когда два объекта ссылаются друг на друга) не удаляются, и память утекает.
2️⃣ Поколенческий GC — спасает от циклических ссылок. Он делит объекты на три поколения:
- Поколение 0: только что созданные объекты. Проверяются чаще всего (каждые 700 созданий).
- Поколение 1: выжившие после первой проверки. Проверяются реже (каждые 10 проверок поколения 0).
- Поколение 2: долгожители. Проверяются ещё реже (каждые 10 проверок поколения 1).
Идея: чем дольше объект живёт, тем меньше вероятность, что он станет мусором. Так GC не тратит время на постоянную проверку старых объектов.
КАК ЭТО НАСТРАИВАТЬ?
Модуль
•
•
•
•
•
ПРИМЕР ИЗ ЖИЗНИ
Представь, что у тебя есть кэш, который хранит ссылки на объекты. Если не очищать его правильно, объекты не удалятся, и память вырастет. Ты можешь вручную вызвать
Или, если ты пишешь высоконагруженный сервис, где важна скорость, можно отключить GC и управлять памятью вручную. Но это рискованно — если где-то закрадётся циклическая ссылка, память утечёт.
ИТОГ
GC в Python — не магия, а инструмент. Понимание его работы поможет тебе писать код, который не жрёт память и не тормозит. На собеседовании это покажет твой уровень. Так что не ленись — покопайся в модуле
А ты уже сталкивался с утечками памяти? Расскажи в комментариях! 👇
Сколько раз ты писал код, запускал его, а память росла как на дрожжах? Или наоборот — объекты исчезали, когда ты их ещё ждал? Всё дело в сборщике мусора (GC). Давай разберёмся, как он работает и как его настраивать, чтобы твой код летал, а не тормозил.
КАК ЭТО РАБОТАЕТ?
Python использует ДВА механизма:
1️⃣ Подсчёт ссылок — это база. Каждый объект хранит счётчик, сколько раз на него ссылаются. Как только счётчик падает до нуля — объект уничтожается. Просто и быстро. Но есть нюанс: циклические ссылки (когда два объекта ссылаются друг на друга) не удаляются, и память утекает.
2️⃣ Поколенческий GC — спасает от циклических ссылок. Он делит объекты на три поколения:
- Поколение 0: только что созданные объекты. Проверяются чаще всего (каждые 700 созданий).
- Поколение 1: выжившие после первой проверки. Проверяются реже (каждые 10 проверок поколения 0).
- Поколение 2: долгожители. Проверяются ещё реже (каждые 10 проверок поколения 1).
Идея: чем дольше объект живёт, тем меньше вероятность, что он станет мусором. Так GC не тратит время на постоянную проверку старых объектов.
КАК ЭТО НАСТРАИВАТЬ?
Модуль
gc даёт полный контроль. Вот что реально пригодится на собеседовании:•
gc.get_threshold() — покажет текущие пороги (по умолчанию (700, 10, 10)).•
gc.set_threshold(threshold0, threshold1, threshold2) — меняет их. Например, если у тебя много временных объектов, увеличь первый порог, чтобы GC реже запускался.•
gc.disable() — отключает поколенческий GC (но не подсчёт ссылок!). Используй, если уверен, что циклических ссылок нет.•
gc.collect() — принудительно запускает сборку мусора. Полезно после создания большого количества объектов.•
gc.get_objects() — возвращает список всех объектов, отслеживаемых GC. Помогает искать утечки.ПРИМЕР ИЗ ЖИЗНИ
Представь, что у тебя есть кэш, который хранит ссылки на объекты. Если не очищать его правильно, объекты не удалятся, и память вырастет. Ты можешь вручную вызвать
gc.collect() после очистки кэша, чтобы GC сразу подчистил.Или, если ты пишешь высоконагруженный сервис, где важна скорость, можно отключить GC и управлять памятью вручную. Но это рискованно — если где-то закрадётся циклическая ссылка, память утечёт.
ИТОГ
GC в Python — не магия, а инструмент. Понимание его работы поможет тебе писать код, который не жрёт память и не тормозит. На собеседовании это покажет твой уровень. Так что не ленись — покопайся в модуле
gc и настрой его под свои задачи.А ты уже сталкивался с утечками памяти? Расскажи в комментариях! 👇
ПОЧЕМУ ТВОЙ async def НЕ РАБОТАЕТ С ОБЫЧНЫМ ДЕКОРАТОРОМ? И как это починить?
Представь: ты написал крутую асинхронную функцию, навесил на неё декоратор, который логирует время выполнения… и всё ломается. Функция возвращает корутину, а не результат. Знакомо? 😱
Давай разберёмся, в чём соль.
Суть проблемы
Обычный декоратор — это функция, которая принимает функцию и возвращает новую. Если ты декорируешь
Решение: асинхронный декоратор
Всё просто: обёртка тоже должна быть асинхронной. Вот как это выглядит:
Теперь
А что с параметрами?
Если твой декоратор сам принимает аргументы (например, уровень логирования), добавляем ещё один уровень вложенности:
Важный нюанс:
Не забывай про
Коротко для интервью
• Обычный декоратор на
• Решение: обёртка тоже
• Если декоратор с параметрами — добавляем ещё одну внешнюю функцию.
• Всегда используй
Теперь ты знаешь, как заставить декораторы дружить с асинхронностью. Иди и покажи это на собеседовании! 💪
Представь: ты написал крутую асинхронную функцию, навесил на неё декоратор, который логирует время выполнения… и всё ломается. Функция возвращает корутину, а не результат. Знакомо? 😱
Давай разберёмся, в чём соль.
Суть проблемы
Обычный декоратор — это функция, которая принимает функцию и возвращает новую. Если ты декорируешь
async def обычным декоратором, то обёртка (wrapper) — это синхронная функция. Она не делает await. В итоге она возвращает корутину, а не результат её выполнения. Твой код превращается в тыкву.Решение: асинхронный декоратор
Всё просто: обёртка тоже должна быть асинхронной. Вот как это выглядит:
from typing import Coroutine
def async_logger(coro: Coroutine):
async def wrapper(*args, **kwargs):
print("Запускаем...")
result = await coro(*args, **kwargs)
print("Готово!")
return result
return wrapper
Теперь
wrapper — это async def, и он корректно awaitит оригинальную корутину.А что с параметрами?
Если твой декоратор сам принимает аргументы (например, уровень логирования), добавляем ещё один уровень вложенности:
from functools import wraps
def async_logger(level: str = "INFO"):
def decorator(coro):
@wraps(coro)
async def wrapper(*args, **kwargs):
print(f"[{level}] Запуск...")
result = await coro(*args, **kwargs)
print(f"[{level}] Готово!")
return result
return wrapper
return decorator
@async_logger(level="DEBUG")
async def fetch_data():
...
Важный нюанс:
functools.wrapsНе забывай про
@wraps(coro)! Он сохраняет метаданные оригинальной функции: имя, докстринг и т.д. Иначе твоя функция потеряет свою «личность», и это может сломать инструменты вроде дебаггера или документации.Коротко для интервью
• Обычный декоратор на
async def — возвращает корутину, а не результат.• Решение: обёртка тоже
async def и внутри await.• Если декоратор с параметрами — добавляем ещё одну внешнюю функцию.
• Всегда используй
@wraps, чтобы сохранить метаданные.Теперь ты знаешь, как заставить декораторы дружить с асинхронностью. Иди и покажи это на собеседовании! 💪
ТЫ ДУМАЕШЬ, ЧТО ЗНАЕШЬ ОБ Observer? А СЛАБО ОБЪЯСНИТЬ, ЧЕМ ОН ОТЛИЧАЕТСЯ ОТ PUB-SUB? 🧐
Вот сидишь ты на собеседовании, и тебя просят: «Реализуй Observer на Python». Ты лепишь класс Subject с списком observers, метод notify, и вроде всё. Но интервьюер хмурится. Почему? Потому что ты не понимаешь главного: Observer — это не про «уведомить всех», это про слабое связывание и стандартный интерфейс.
Давай разберёмся. Представь, что Subject — это твой любимый YouTube-канал, а Observers — подписчики. Канал не знает, кто именно подписан: Вася, Петя или робот. Он просто знает, что у подписчиков есть метод
НО! Есть нюанс, о котором часто забывают: в классическом Observer Subject хранит прямые ссылки на Observers. Это синхронная связь. Subject вызывает метод update() и ждёт, пока observer обработает. Это не асинхронщина, не очереди сообщений. Это просто цикл:
А теперь самое мясо. Чем Observer отличается от Pub-Sub? Вот таблица, которая спасёт тебя на собеседовании:
• Связь: Observer — жёсткая (Subject знает Observer). Pub-Sub — слабая (через брокер).
• Синхронность: Observer — синхронный (обычно). Pub-Sub — асинхронный (часто).
• Масштабирование: Observer — для in-process (внутри одного процесса). Pub-Sub — для распределённых систем.
Как это выглядит в коде?
Видишь? Subject принимает любой объект, который реализует
Когда Observer реально нужен?
• GUI-фреймворки: кнопка (Subject) уведомляет обработчики (Observers) о клике.
• Event-driven системы: например, обновление модели в MVC.
• Логирование: центральный логгер уведомляет фильтры.
А когда НЕ нужен?
• Если тебе нужно гарантировать доставку сообщения (Observer синхронный — если observer упадёт, уведомление потеряется).
• Если у тебя распределённая система — используй Pub-Sub (RabbitMQ, Kafka).
Ловушка для джунов: не путай Observer с Pub-Sub. На собеседовании могут спросить: «А как сделать Observer асинхронным?» Ответ: использовать очередь задач (например, asyncio.Queue) или ThreadPoolExecutor. Но это уже будет не чистый Observer, а гибрид.
Итог: Observer — это простая, но мощная штука, если понимаешь её границы. Запомни: Subject знает Observer, Observer знает Subject (через аргумент update). Это не недостаток, а фича для in-process коммуникации.
Теперь ты готов? Попробуй объяснить это своими словами. Если сможешь — ты прошёл уровень. Если нет — перечитай пост ещё раз. 🔥
Вот сидишь ты на собеседовании, и тебя просят: «Реализуй Observer на Python». Ты лепишь класс Subject с списком observers, метод notify, и вроде всё. Но интервьюер хмурится. Почему? Потому что ты не понимаешь главного: Observer — это не про «уведомить всех», это про слабое связывание и стандартный интерфейс.
Давай разберёмся. Представь, что Subject — это твой любимый YouTube-канал, а Observers — подписчики. Канал не знает, кто именно подписан: Вася, Петя или робот. Он просто знает, что у подписчиков есть метод
update(new_video). И когда выходит новое видео, канал дёргает этот метод у всех в списке. Всё. Никакой магии.НО! Есть нюанс, о котором часто забывают: в классическом Observer Subject хранит прямые ссылки на Observers. Это синхронная связь. Subject вызывает метод update() и ждёт, пока observer обработает. Это не асинхронщина, не очереди сообщений. Это просто цикл:
for observer in self._observers: observer.update(self).А теперь самое мясо. Чем Observer отличается от Pub-Sub? Вот таблица, которая спасёт тебя на собеседовании:
• Связь: Observer — жёсткая (Subject знает Observer). Pub-Sub — слабая (через брокер).
• Синхронность: Observer — синхронный (обычно). Pub-Sub — асинхронный (часто).
• Масштабирование: Observer — для in-process (внутри одного процесса). Pub-Sub — для распределённых систем.
Как это выглядит в коде?
from abc import ABC, abstractmethod
class Observer(ABC):
@abstractmethod
def update(self, subject):
pass
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer: Observer):
self._observers.append(observer)
def detach(self, observer: Observer):
self._observers.remove(observer)
def notify(self):
for observer in self._observers:
observer.update(self)
class ConcreteObserver(Observer):
def update(self, subject):
print(f"Observer: получил уведомление от {subject}")
Видишь? Subject принимает любой объект, который реализует
update(). Это и есть стандартизированный интерфейс. Благодаря этому ты можешь добавлять новых наблюдателей, не меняя код Subject. Это OCP (Open-Closed Principle) в действии.Когда Observer реально нужен?
• GUI-фреймворки: кнопка (Subject) уведомляет обработчики (Observers) о клике.
• Event-driven системы: например, обновление модели в MVC.
• Логирование: центральный логгер уведомляет фильтры.
А когда НЕ нужен?
• Если тебе нужно гарантировать доставку сообщения (Observer синхронный — если observer упадёт, уведомление потеряется).
• Если у тебя распределённая система — используй Pub-Sub (RabbitMQ, Kafka).
Ловушка для джунов: не путай Observer с Pub-Sub. На собеседовании могут спросить: «А как сделать Observer асинхронным?» Ответ: использовать очередь задач (например, asyncio.Queue) или ThreadPoolExecutor. Но это уже будет не чистый Observer, а гибрид.
Итог: Observer — это простая, но мощная штука, если понимаешь её границы. Запомни: Subject знает Observer, Observer знает Subject (через аргумент update). Это не недостаток, а фича для in-process коммуникации.
Теперь ты готов? Попробуй объяснить это своими словами. Если сможешь — ты прошёл уровень. Если нет — перечитай пост ещё раз. 🔥
ТЫ ДУМАЕШЬ, ЧТО ЗНАЕШЬ ВСЁ О КЛАССАХ В PYTHON? А ЧТО, ЕСЛИ Я СКАЖУ, ЧТО КЛАССЫ ТОЖЕ МОЖНО СОЗДАВАТЬ ДИНАМИЧЕСКИ, И ЗА ЭТО ОТВЕЧАЮТ МЕТАКЛАССЫ?
Давай сразу к делу. Метакласс — это класс, который создаёт другие классы. Звучит как магия? На самом деле, это просто механизм, который срабатывает, когда Python встречает ключевое слово
Как это работает?
Каждый раз, когда ты пишешь:
Python на самом деле вызывает метакласс (по умолчанию —
• имя класса (
• кортеж базовых классов (
• словарь атрибутов (
Именно
Зачем это нужно?
На собеседовании тебя спросят: "Приведи пример, где метаклассы реально полезны". Вот классика:
• Синглтон — гарантируем, что у класса будет только один экземпляр.
• Регистрация классов — автоматически добавляем все наследники в реестр.
• Валидация атрибутов — запрещаем создавать методы с неправильными именами.
Пример: простой синглтон через метакласс
НО! Держи ухо востро:
В 90% случаев метаклассы — это избыточное решение. Их часто можно заменить на:
• декораторы классов;
•
• дескрипторы.
Интервьюер оценит, если ты скажешь: "Я знаю, как работают метаклассы, но предпочитаю более простые инструменты, если это возможно".
Итог для собеседования:
1. Метакласс — это класс, который создаёт классы.
2. По умолчанию используется
3. Свой метакласс создаёшь, наследуясь от
4. Применяй только когда реально нужно изменить поведение класса на этапе его создания.
Понял? Тогда держи вопрос: "А что будет, если в метаклассе переопределить
Давай сразу к делу. Метакласс — это класс, который создаёт другие классы. Звучит как магия? На самом деле, это просто механизм, который срабатывает, когда Python встречает ключевое слово
class. Как это работает?
Каждый раз, когда ты пишешь:
class MyClass:
pass
Python на самом деле вызывает метакласс (по умолчанию —
type), передавая ему три аргумента:• имя класса (
'MyClass');• кортеж базовых классов (
());• словарь атрибутов (
{}).Именно
type создаёт объект класса. А ты можешь переопределить это поведение, создав свой метакласс.Зачем это нужно?
На собеседовании тебя спросят: "Приведи пример, где метаклассы реально полезны". Вот классика:
• Синглтон — гарантируем, что у класса будет только один экземпляр.
• Регистрация классов — автоматически добавляем все наследники в реестр.
• Валидация атрибутов — запрещаем создавать методы с неправильными именами.
Пример: простой синглтон через метакласс
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class MyClass(metaclass=SingletonMeta):
pass
a = MyClass()
b = MyClass()
print(a is b) # True
НО! Держи ухо востро:
В 90% случаев метаклассы — это избыточное решение. Их часто можно заменить на:
• декораторы классов;
•
__init_subclass__;• дескрипторы.
Интервьюер оценит, если ты скажешь: "Я знаю, как работают метаклассы, но предпочитаю более простые инструменты, если это возможно".
Итог для собеседования:
1. Метакласс — это класс, который создаёт классы.
2. По умолчанию используется
type.3. Свой метакласс создаёшь, наследуясь от
type и переопределяя __new__ или __init__.4. Применяй только когда реально нужно изменить поведение класса на этапе его создания.
Понял? Тогда держи вопрос: "А что будет, если в метаклассе переопределить
__new__ и вернуть другой класс?" — пиши ответ в комментах!🔥 ТЫ ДУМАЕШЬ, ЧТО ЗНАЕШЬ SWITCH В PYTHON? А ВОТ И НЕТ!
Скажи честно: сколько раз ты писал длиннющую цепочку if-elif-elif-elif и мечтал о нормальном switch? 20 лет ждали — и дождались! Python 3.10 принёс match/case. Но это не просто копия switch из C++ или Java. Это БОМБА — структурное сопоставление (pattern matching).
Давай разберём, как это работает и почему ты полюбишь match.
1️⃣ БАЗА: тот самый switch
case _ — это default. Пока ничего необычного. Но копнём глубже.
2️⃣ РАСПАКОВКА СТРУКТУР В CASE
Ты можешь распаковывать кортежи, списки, словари прямо в условии!
Заметил? Переменные x и y связываются со значениями! Это не сравнение, а распаковка + сопоставление.
3️⃣ СПИСКИ ЛЮБОЙ ДЛИНЫ
Звёздочка *rest — это распаковка внутри паттерна! Гениально.
4️⃣ GUARDS — УСЛОВИЯ ВНУТРИ CASE
Иногда простого паттерна мало. Добавляем if:
Если guard вернул False — Python проверяет следующий case. Удобно!
5️⃣ СОПОСТАВЛЕНИЕ ПО ТИПУ
Обрати внимание: int() — это проверка типа, str() as text — проверка + привязка к переменной, а | — ИЛИ-паттерн.
6️⃣ РАБОТА С КЛАССАМИ И DATACLASS
Вот где match раскрывается на 100%:
Ты можешь сопоставлять по конкретным полям! Это мощнее любых if-elif.
ВЫВОД: match/case — это не просто switch. Это инструмент для написания чистого, декларативного кода. На собеседовании тебя спросят: "Как работает match?" — и теперь ты знаешь ответ. Не просто "как switch", а "как мощный паттерн-матчинг с распаковкой, guards и проверкой типов".
А ты уже используешь match в своих проектах? Или всё ещё сидишь на if-elif? Пиши в комментариях! 👇
Скажи честно: сколько раз ты писал длиннющую цепочку if-elif-elif-elif и мечтал о нормальном switch? 20 лет ждали — и дождались! Python 3.10 принёс match/case. Но это не просто копия switch из C++ или Java. Это БОМБА — структурное сопоставление (pattern matching).
Давай разберём, как это работает и почему ты полюбишь match.
1️⃣ БАЗА: тот самый switch
def handle_status(status):
match status:
case "pending":
print("Ожидает")
case "shipped":
print("В пути")
case _:
print("Неизвестно")
case _ — это default. Пока ничего необычного. Но копнём глубже.
2️⃣ РАСПАКОВКА СТРУКТУР В CASE
Ты можешь распаковывать кортежи, списки, словари прямо в условии!
def process_point(point):
match point:
case (0, 0):
print("Начало координат")
case (x, 0):
print(f"На оси X: {x}")
case (0, y):
print(f"На оси Y: {y}")
case (x, y):
print(f"Точка ({x}, {y})")
Заметил? Переменные x и y связываются со значениями! Это не сравнение, а распаковка + сопоставление.
3️⃣ СПИСКИ ЛЮБОЙ ДЛИНЫ
def process_items(items):
match items:
case []:
print("Пусто")
case [first]:
print(f"Один: {first}")
case [first, second]:
print(f"Два: {first} и {second}")
case [first, *rest]:
print(f"Первый: {first}, остальные: {rest}")
Звёздочка *rest — это распаковка внутри паттерна! Гениально.
4️⃣ GUARDS — УСЛОВИЯ ВНУТРИ CASE
Иногда простого паттерна мало. Добавляем if:
def check_point(point):
match point:
case (x, y) if x == y:
print(f"Диагональ: ({x}, {y})")
case (x, y) if x > 0 and y > 0:
print(f"1-й квадрант: ({x}, {y})")
case (x, y):
print(f"Точка: ({x}, {y})")
Если guard вернул False — Python проверяет следующий case. Удобно!
5️⃣ СОПОСТАВЛЕНИЕ ПО ТИПУ
def process_value(value):
match value:
case int():
print(f"Целое: {value}")
case str() as text if len(text) > 10:
print(f"Длинная строка: {text[:10]}...")
case str() as text:
print(f"Строка: {text}")
case list() | tuple() as seq:
print(f"Последовательность длиной {len(seq)}")
case _:
print("Что-то другое")
Обрати внимание: int() — это проверка типа, str() as text — проверка + привязка к переменной, а | — ИЛИ-паттерн.
6️⃣ РАБОТА С КЛАССАМИ И DATACLASS
Вот где match раскрывается на 100%:
from dataclasses import dataclass
@dataclass
class User:
name: str
role: str
active: bool
def handle_user(user):
match user:
case User(name="admin", role="admin"):
print("Супер-админ")
case User(name=name, role="admin"):
print(f"Админ {name}")
case User(active=False):
print("Неактивный пользователь")
case _:
print("Обычный пользователь")
Ты можешь сопоставлять по конкретным полям! Это мощнее любых if-elif.
ВЫВОД: match/case — это не просто switch. Это инструмент для написания чистого, декларативного кода. На собеседовании тебя спросят: "Как работает match?" — и теперь ты знаешь ответ. Не просто "как switch", а "как мощный паттерн-матчинг с распаковкой, guards и проверкой типов".
А ты уже используешь match в своих проектах? Или всё ещё сидишь на if-elif? Пиши в комментариях! 👇
Твой код падает на проде, а ты не понимаешь почему? ⚡️
Скорее всего, ты забыл про type hints для генераторов и итераторов. Давай разберем эту тему раз и навсегда.
Что такое генератор и итератор?
Итератор — это объект, который умеет выдавать элементы по одному, пока не закончатся. Помнишь цикл for? Он каждый раз просит у списка итератор, а тот по очереди отдает элементы.
Генератор — это частный случай итератора, который создается функцией с
А теперь самое интересное — type hints.
Если ты пишешь функцию, которая возвращает генератор, как это аннотировать?
❌ Неправильно:
Здесь ты создаешь список в памяти — это дорого и неэффективно для больших данных.
✅ Правильно:
Но что означают три параметра в
• YieldType (первый) — тип значений, которые генератор выдает через yield.
• SendType (второй) — тип значений, которые можно отправить в генератор через
• ReturnType (третий) — тип значения, которое возвращается через
Пример с отправкой значений:
Здесь генератор принимает числа через
А что если у тебя просто итератор, а не генератор?
Тогда используй
Важный нюанс, о котором часто забывают:
Генераторное выражение тоже нужно аннотировать! Не пиши просто
Лучше так:
Или используй
Почему это важно на собеседовании?
Интервьюер проверяет, понимаешь ли ты разницу между итератором и генератором, умеешь ли правильно типизировать, знаешь ли про три параметра Generator. Покажи, что ты не просто пишешь код, а осознанно выбираешь инструменты.
Резюме:
• Для функций с yield —
• Для простых итераторов —
• Для асинхронных генераторов —
Сохраняй этот пост в закладки, чтобы не потерять. И поделись с коллегой, который до сих пор пишет
Скорее всего, ты забыл про type hints для генераторов и итераторов. Давай разберем эту тему раз и навсегда.
Что такое генератор и итератор?
Итератор — это объект, который умеет выдавать элементы по одному, пока не закончатся. Помнишь цикл for? Он каждый раз просит у списка итератор, а тот по очереди отдает элементы.
Генератор — это частный случай итератора, который создается функцией с
yield или генераторным выражением. Он не хранит все значения в памяти, а вычисляет их "лениво" — только когда попросят.А теперь самое интересное — type hints.
Если ты пишешь функцию, которая возвращает генератор, как это аннотировать?
❌ Неправильно:
def get_numbers() -> list[int]:
return [x for x in range(10)]Здесь ты создаешь список в памяти — это дорого и неэффективно для больших данных.
✅ Правильно:
def get_numbers() -> Generator[int, None, None]:
for x in range(10):
yield xНо что означают три параметра в
Generator?• YieldType (первый) — тип значений, которые генератор выдает через yield.
• SendType (второй) — тип значений, которые можно отправить в генератор через
.send(). Если не используешь — пиши None.• ReturnType (третий) — тип значения, которое возвращается через
return в генераторе. Обычно тоже None.Пример с отправкой значений:
def accumulator() -> Generator[int, int, None]:
total = 0
while True:
value = yield total
total += valueЗдесь генератор принимает числа через
.send() и возвращает накопленную сумму.А что если у тебя просто итератор, а не генератор?
Тогда используй
Iterator[тип_элемента]:def read_lines(path: str) -> Iterator[str]:
with open(path) as f:
for line in f:
yield line.strip()Важный нюанс, о котором часто забывают:
Генераторное выражение тоже нужно аннотировать! Не пиши просто
gen = (x**2 for x in range(10)).Лучше так:
gen: Generator[int, None, None] = (x**2 for x in range(10))Или используй
Iterator[int] — это более общий тип.Почему это важно на собеседовании?
Интервьюер проверяет, понимаешь ли ты разницу между итератором и генератором, умеешь ли правильно типизировать, знаешь ли про три параметра Generator. Покажи, что ты не просто пишешь код, а осознанно выбираешь инструменты.
Резюме:
• Для функций с yield —
Generator[YieldType, SendType, ReturnType]• Для простых итераторов —
Iterator[Type]• Для асинхронных генераторов —
AsyncGenerator[YieldType, SendType]Сохраняй этот пост в закладки, чтобы не потерять. И поделись с коллегой, который до сих пор пишет
list(dict) вместо нормального генератора 😉Твой FastAPI сервис тормозит? База данных падает под нагрузкой? А ты просто забыл про кэширование с Redis! ⚡️
Давай разберем, как это работает на практике. Без воды, только код и суть.
С чего начать?
Первое — поднимаем асинхронный клиент Redis. FastAPI сам асинхронный, и Redis должен быть таким же. Используем
Вот минимальный шаблон подключения:
Почему это важно?
Соединения не должны умирать!
В продакшене каждое новое соединение — это затраты. Используй Connection Pool. Он переиспользует открытые соединения.
Как теперь кэшировать?
Проще всего — написать декоратор или просто проверять ключ внутри эндпоинта.
Самый наглядный способ — в лоб:
В чем подвох?
Многие ставят TTL (время жизни кэша) на сутки. И потом удивляются, что данные устарели. Ставь разумное время: 5-10 минут для динамических данных, час для справочников.
Совет сеньора:
Если данные обновляются редко, но их часто запрашивают — используй инвалидацию кэша. При обновлении записи в БД сразу удаляй ключ из Redis. Тогда следующий запрос подтянет свежие данные.
Итог:
Кэш на Redis + FastAPI — это:
- Снижение нагрузки на БД в 10-100 раз
- Ответы за миллисекунды
- Масштабирование без боли
Попробуй внедрить это на своем проекте. Первый же нагрузочный тест покажет разницу.
Вопросы? Пиши в комментарии, разберем твой кейс! 👇
Давай разберем, как это работает на практике. Без воды, только код и суть.
С чего начать?
Первое — поднимаем асинхронный клиент Redis. FastAPI сам асинхронный, и Redis должен быть таким же. Используем
redis.asyncio.Вот минимальный шаблон подключения:
import redis.asyncio as redis
from contextlib import asynccontextmanager
redis_client = None
async def init_redis():
global redis_client
redis_client = redis.Redis(
host="localhost",
port=6379,
db=0,
decode_responses=True,
socket_timeout=5.0
)
await redis_client.ping()
return redis_client
@asynccontextmanager
async def lifespan(app):
await init_redis()
yield
await redis_client.aclose()
app = FastAPI(lifespan=lifespan)
Почему это важно?
decode_responses=True — мастхэв. Без него ты будешь получать байты, а не строки. И каждый раз мучительно декодировать.Соединения не должны умирать!
В продакшене каждое новое соединение — это затраты. Используй Connection Pool. Он переиспользует открытые соединения.
pool = redis.ConnectionPool(
host="localhost",
max_connections=50,
decode_responses=True,
retry_on_timeout=True
)
async def get_redis():
client = redis.Redis(connection_pool=pool)
try:
yield client
finally:
await client.aclose()
Как теперь кэшировать?
Проще всего — написать декоратор или просто проверять ключ внутри эндпоинта.
Самый наглядный способ — в лоб:
@app.get("/items/{item_id}")
async def get_item(item_id: str, redis_conn: redis.Redis = Depends(get_redis)):
# Сначала смотрим в кэш
cached = await redis_conn.get(f"item:{item_id}")
if cached:
return {"data": cached, "source": "cache"}
# Если нет — лезем в базу
data = fetch_from_db(item_id)
# Сохраняем на 5 минут
await redis_conn.set(f"item:{item_id}", data, ex=300)
return {"data": data, "source": "database"}
В чем подвох?
Многие ставят TTL (время жизни кэша) на сутки. И потом удивляются, что данные устарели. Ставь разумное время: 5-10 минут для динамических данных, час для справочников.
Совет сеньора:
Если данные обновляются редко, но их часто запрашивают — используй инвалидацию кэша. При обновлении записи в БД сразу удаляй ключ из Redis. Тогда следующий запрос подтянет свежие данные.
Итог:
Кэш на Redis + FastAPI — это:
- Снижение нагрузки на БД в 10-100 раз
- Ответы за миллисекунды
- Масштабирование без боли
Попробуй внедрить это на своем проекте. Первый же нагрузочный тест покажет разницу.
Вопросы? Пиши в комментарии, разберем твой кейс! 👇
🚨 ТЫ ДУМАЕШЬ, ЧТО ЗНАЕШЬ ВСЁ ПРО for? А ПОПРОБУЙ НАПИСАТЬ СВОЙ ИТЕРАТОР!
Сколько раз ты писал
Что такое итератор?
Это объект, который умеет «выдавать» элементы по одному, когда его просят. Представь, что у тебя есть коробка с шарами. Ты можешь доставать их по одному, но не можешь заглянуть внутрь и увидеть все сразу. Вот это и есть итератор.
А что такое итерируемый объект?
Это то, что можно превратить в итератор. Например, список, строка, словарь. У них есть метод
Магия двух методов:
Чтобы сделать свой класс итератором, нужно реализовать всего два метода:
•
•
Живой пример: напишем итератор, который перебирает числа от 1 до N.
Как это работает под капотом?
Когда ты пишешь
1. Вызывает
2. На каждой итерации вызывает
3. Когда ловится
Почему это важно на собеседовании?
Интервьюеры любят спрашивать про итераторы, чтобы проверить, понимаешь ли ты, как работает цикл
Ловушка для новичков:
Многие думают, что
Совет от профи:
Если тебе нужно просто перебрать последовательность — используй генераторы (функции с
Попробуй сам:
Напиши итератор, который возвращает только чётные числа из списка. Или бесконечный итератор, который генерирует числа Фибоначчи. Упражнение — лучший способ запомнить!
Сохраняй пост в «Избранное», чтобы не потерять шпаргалку. И подписывайся, если хочешь стать Python-джедаем! 🚀
Сколько раз ты писал
for x in my_list и даже не задумывался, как это работает? А на собеседовании Senior-разработчика тебя могут попросить: «Реализуй свой итератор». И тут многие впадают в ступор. Давай разберёмся раз и навсегда!Что такое итератор?
Это объект, который умеет «выдавать» элементы по одному, когда его просят. Представь, что у тебя есть коробка с шарами. Ты можешь доставать их по одному, но не можешь заглянуть внутрь и увидеть все сразу. Вот это и есть итератор.
А что такое итерируемый объект?
Это то, что можно превратить в итератор. Например, список, строка, словарь. У них есть метод
__iter__(), который возвращает итератор.Магия двух методов:
Чтобы сделать свой класс итератором, нужно реализовать всего два метода:
•
__iter__() — возвращает сам итератор (обычно return self).•
__next__() — возвращает следующий элемент. Если элементов больше нет, выбрасывает исключение StopIteration.Живой пример: напишем итератор, который перебирает числа от 1 до N.
class CountUp:
def __init__(self, max_count):
self.current = 0
self.max_count = max_count
def __iter__(self):
return self # Итератор возвращает сам себя
def __next__(self):
self.current += 1
if self.current > self.max_count:
raise StopIteration # Сигнал «стоп»
return self.current
# Используем:
for num in CountUp(5):
print(num) # Выведет: 1 2 3 4 5
Как это работает под капотом?
Когда ты пишешь
for num in CountUp(5), Python делает три вещи:1. Вызывает
__iter__() у объекта, чтобы получить итератор.2. На каждой итерации вызывает
__next__().3. Когда ловится
StopIteration — цикл завершается.Почему это важно на собеседовании?
Интервьюеры любят спрашивать про итераторы, чтобы проверить, понимаешь ли ты, как работает цикл
for на самом деле. Если ты можешь написать свой итератор — ты показываешь глубокое знание Python.Ловушка для новичков:
Многие думают, что
__iter__() должен возвращать что-то сложное. Нет! В простейшем случае — это return self. Главная магия — в __next__().Совет от профи:
Если тебе нужно просто перебрать последовательность — используй генераторы (функции с
yield). Они проще и короче. Но если хочешь блеснуть на собеседовании — покажи класс-итератор. Это показывает, что ты понимаешь, как Python работает «под капотом».Попробуй сам:
Напиши итератор, который возвращает только чётные числа из списка. Или бесконечный итератор, который генерирует числа Фибоначчи. Упражнение — лучший способ запомнить!
Сохраняй пост в «Избранное», чтобы не потерять шпаргалку. И подписывайся, если хочешь стать Python-джедаем! 🚀
ВОТ ТЫ ПИШЕШЬ
Давай сразу к делу. Контекстный менеджер — это просто объект, который умеет делать две вещи: настраивать ресурс (открыть файл, захватить блокировку) и гарантированно его освобождать (закрыть файл, отпустить блокировку). В Python это реализуется через оператор
Всё. Когда выходишь из блока — файл закрывается автоматически, даже если внутри произошла ошибка. Без
НО ЭТО ТОЛЬКО НАЧАЛО. Самый частый вопрос на собесе: «Как написать свой контекстный менеджер?» И тут два пути.
Путь первый: класс с магическими методами.
Реализуешь
Пример для временного файла:
Путь второй: декоратор
Это для ленивых (читай: умных). Пишешь функцию-генератор с одним
Теперь используешь:
Красота, правда? Никаких лишних классов, всё читается за секунду.
Почему это важно для собеседования?
Потому что контекстные менеджеры — это не про синтаксический сахар. Это про безопасность ресурсов. Базы данных, сокеты, временные файлы — всё это должно быть закрыто. Если ты покажешь, что понимаешь, как работает
И ещё один лайфхак:
Вместо того чтобы писать try-except-pass. Чисто и понятно.
Запомни: любой объект с
open('file.txt') И ДУМАЕШЬ, ЧТО ВСЁ ПРОСТО? А потом прод падает, потому что файл не закрылся. Или временный файл остался висеть на диске. Знакомо? Добро пожаловать в мир контекстных менеджеров — твоего спасения от утечек ресурсов.Давай сразу к делу. Контекстный менеджер — это просто объект, который умеет делать две вещи: настраивать ресурс (открыть файл, захватить блокировку) и гарантированно его освобождать (закрыть файл, отпустить блокировку). В Python это реализуется через оператор
with. Смотри, как элегантно:with open('data.txt', 'w') as f:
f.write('hello')Всё. Когда выходишь из блока — файл закрывается автоматически, даже если внутри произошла ошибка. Без
with тебе пришлось бы писать try-finally. А это лишний код, который легко забыть.НО ЭТО ТОЛЬКО НАЧАЛО. Самый частый вопрос на собесе: «Как написать свой контекстный менеджер?» И тут два пути.
Путь первый: класс с магическими методами.
Реализуешь
__enter__ и __exit__. __enter__ возвращает ресурс (то, что попадёт в переменную после as). __exit__ принимает три аргумента: тип исключения, значение и traceback. Если в __exit__ вернуть True — исключение будет подавлено. Но так делать не советую, если не уверен на 100%.Пример для временного файла:
class TempFile:
def __init__(self, name):
self.name = name
def __enter__(self):
self.file = open(self.name, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
# удаляем файл после закрытия
import os
os.remove(self.name)
Путь второй: декоратор
@contextmanager из contextlib.Это для ленивых (читай: умных). Пишешь функцию-генератор с одним
yield. Всё до yield — это __enter__, всё после — __exit__.from contextlib import contextmanager
@contextmanager
def temp_file(name):
f = open(name, 'w')
try:
yield f
finally:
f.close()
os.remove(name)
Теперь используешь:
with temp_file('test.txt') as f:
f.write('data')Красота, правда? Никаких лишних классов, всё читается за секунду.
Почему это важно для собеседования?
Потому что контекстные менеджеры — это не про синтаксический сахар. Это про безопасность ресурсов. Базы данных, сокеты, временные файлы — всё это должно быть закрыто. Если ты покажешь, что понимаешь, как работает
with и как написать свой менеджер, интервьюер сразу поймёт: ты не просто кнопки нажимаешь, а думаешь о надёжности.И ещё один лайфхак:
contextlib.suppress — это контекстный менеджер, который глушит исключения. Например:from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('temp.txt')
Вместо того чтобы писать try-except-pass. Чисто и понятно.
Запомни: любой объект с
__enter__ и __exit__ можно использовать в with. Даже блокировки потоков. Это универсальный паттерн, который делает код надёжным и читаемым. Не забывай про него на собеседовании — и на проде тоже.🔥 ТЫ ДУМАЕШЬ, ЧТО ЗНАЕШЬ ЦИКЛЫ? А ЧТО НАСЧЁТ БЕСКОНЕЧНЫХ ПОСЛЕДОВАТЕЛЬНОСТЕЙ И КОМБИНАТОРИКИ?
Представь: ты обрабатываешь гигантский лог-файл (миллионы строк). Тебе нужно сгруппировать записи по дате, сгенерировать все возможные пары ошибок или просто бесконечно повторять паттерн. Обычные циклы сожрут всю память и убьют производительность. И тут на сцену выходит itertools — твой секретный арсенал для работы с итераторами.
ЧТО ЭТО ВООБЩЕ?
Это встроенный модуль Python, который даёт тебе набор мощных функций для создания итераторов. Главная фишка — ленивые вычисления (lazy evaluation). Результат вычисляется только когда ты реально запрашиваешь следующий элемент. Память не забивается, скорость — огонь. Многие функции написаны на C, так что работают очень быстро.
ОСНОВНЫЕ ФУНКЦИИ, КОТОРЫЕ ДОЛЖЕН ЗНАТЬ КАЖДЫЙ SENIOR:
1️⃣ count(start, step) — бесконечный счётчик.
Стартуешь с числа, добавляешь шаг. Итерация бесконечная. Идеально для генерации ID или нумерации строк в потоке.
2️⃣ cycle(iterable) — бесконечный цикл по элементам.
Берёшь список, кортеж или строку и повторяешь их вечно. Супер для паттернов, смены статусов, каруселей.
3️⃣ repeat(object, times=None) — повторяет объект заданное количество раз (или бесконечно).
Удобно для заполнения списка одинаковыми значениями или для тестовых данных.
4️⃣ chain(*iterables) — соединяет несколько итераторов в один.
Вместо вложенных циклов — просто склеиваешь последовательности.
5️⃣ groupby(iterable, key=None) — группировка элементов по ключу.
Классика для агрегации данных. ВАЖНО: перед groupby нужно отсортировать данные по тому же ключу, иначе группы будут некорректными!
6️⃣ combinations(iterable, r) и permutations(iterable, r=None) — комбинаторика.
Генерируют все возможные комбинации (порядок не важен) и перестановки (порядок важен) длины r. Бесценно для тестирования, подбора паролей, анализа вариантов.
7️⃣ product(*iterables, repeat=1) — декартово произведение.
Все возможные комбинации из нескольких множеств. Замена вложенным циклам.
ПОЧЕМУ ЭТО ВАЖНО НА СОБЕСЕДОВАНИИ?
Интервьюеры обожают проверять, умеешь ли ты писать эффективный код. Если ты вместо вложенных циклов используешь
ТИПИЧНЫЙ ПОДВОХ:
Запомни: итераторы из itertools — одноразовые. После того как ты их полностью проитерировал, они пусты. Если нужно пройтись дважды — сохрани результат в список.
ИТОГ:
Модуль itertools — это твой швейцарский нож для работы с данными. Он экономит память, ускоряет код и делает его чище. Начни использовать его сегодня — и твой код станет на уровень выше.
А теперь вопрос к тебе: какую функцию из itertools ты чаще всего используешь в реальных проектах?
Представь: ты обрабатываешь гигантский лог-файл (миллионы строк). Тебе нужно сгруппировать записи по дате, сгенерировать все возможные пары ошибок или просто бесконечно повторять паттерн. Обычные циклы сожрут всю память и убьют производительность. И тут на сцену выходит itertools — твой секретный арсенал для работы с итераторами.
ЧТО ЭТО ВООБЩЕ?
Это встроенный модуль Python, который даёт тебе набор мощных функций для создания итераторов. Главная фишка — ленивые вычисления (lazy evaluation). Результат вычисляется только когда ты реально запрашиваешь следующий элемент. Память не забивается, скорость — огонь. Многие функции написаны на C, так что работают очень быстро.
ОСНОВНЫЕ ФУНКЦИИ, КОТОРЫЕ ДОЛЖЕН ЗНАТЬ КАЖДЫЙ SENIOR:
1️⃣ count(start, step) — бесконечный счётчик.
Стартуешь с числа, добавляешь шаг. Итерация бесконечная. Идеально для генерации ID или нумерации строк в потоке.
from itertools import count
for i in count(10, 2):
if i > 20: break
print(i) # 10, 12, 14, 16, 18, 202️⃣ cycle(iterable) — бесконечный цикл по элементам.
Берёшь список, кортеж или строку и повторяешь их вечно. Супер для паттернов, смены статусов, каруселей.
from itertools import cycle
colors = ['red', 'green', 'blue']
for color in cycle(colors):
# бесконечно перебираем цвета
pass3️⃣ repeat(object, times=None) — повторяет объект заданное количество раз (или бесконечно).
Удобно для заполнения списка одинаковыми значениями или для тестовых данных.
from itertools import repeat
list(repeat('test', 3)) # ['test', 'test', 'test']4️⃣ chain(*iterables) — соединяет несколько итераторов в один.
Вместо вложенных циклов — просто склеиваешь последовательности.
from itertools import chain
list(chain([1,2,3], [4,5], [6])) # [1,2,3,4,5,6]5️⃣ groupby(iterable, key=None) — группировка элементов по ключу.
Классика для агрегации данных. ВАЖНО: перед groupby нужно отсортировать данные по тому же ключу, иначе группы будут некорректными!
from itertools import groupby
data = [('a', 1), ('a', 2), ('b', 3)]
for key, group in groupby(data, lambda x: x[0]):
print(key, list(group)) # a [('a',1),('a',2)] b [('b',3)]6️⃣ combinations(iterable, r) и permutations(iterable, r=None) — комбинаторика.
Генерируют все возможные комбинации (порядок не важен) и перестановки (порядок важен) длины r. Бесценно для тестирования, подбора паролей, анализа вариантов.
from itertools import combinations, permutations
list(combinations('ABC', 2)) # [('A','B'),('A','C'),('B','C')]
list(permutations('ABC', 2)) # [('A','B'),('A','C'),('B','A'),('B','C'),('C','A'),('C','B')]7️⃣ product(*iterables, repeat=1) — декартово произведение.
Все возможные комбинации из нескольких множеств. Замена вложенным циклам.
from itertools import product
list(product('AB', repeat=2)) # [('A','A'),('A','B'),('B','A'),('B','B')]ПОЧЕМУ ЭТО ВАЖНО НА СОБЕСЕДОВАНИИ?
Интервьюеры обожают проверять, умеешь ли ты писать эффективный код. Если ты вместо вложенных циклов используешь
product или chain, это показывает твой уровень. Плюс, понимание ленивых вычислений — признак сеньора.ТИПИЧНЫЙ ПОДВОХ:
Запомни: итераторы из itertools — одноразовые. После того как ты их полностью проитерировал, они пусты. Если нужно пройтись дважды — сохрани результат в список.
ИТОГ:
Модуль itertools — это твой швейцарский нож для работы с данными. Он экономит память, ускоряет код и делает его чище. Начни использовать его сегодня — и твой код станет на уровень выше.
А теперь вопрос к тебе: какую функцию из itertools ты чаще всего используешь в реальных проектах?
🚀 ДЕКОРАТОРЫ ДЛЯ КЛАССОВ: ТЫ ТОЧНО ЗНАЕШЬ, КАК ОНИ РАБОТАЮТ?
Вчера на собеседовании меня спросили: «Что такое декоратор для класса?» Я начал мямлить про @staticmethod… и провалился. Не повторяй мою ошибку! Разбираемся раз и навсегда.
🔹 СНАЧАЛА БАЗА: ЧТО ТАКОЕ ДЕКОРАТОР ВООБЩЕ?
Декоратор — это функция, которая принимает другую функцию (или класс) и возвращает её изменённую версию. Всё. Никакой магии. Просто «обёртка», которая добавляет код ДО и ПОСЛЕ выполнения исходного объекта.
Пример для функции:
🔹 А ТЕПЕРЬ ДЕКОРАТОРЫ ДЛЯ КЛАССОВ
Тут два принципиально разных сценария. Не путай их, иначе на собеседовании будет стыдно.
1. Декоратор, который применяется к классу (как к объекту)
Ты берёшь класс, оборачиваешь его в функцию-декоратор, и на выходе получаешь изменённый класс. Это мощный инструмент для добавления методов, атрибутов или логики всем экземплярам сразу.
Пример: добавим всем объектам класса атрибут created_at:
Видишь? Мы не трогали сам класс User, а просто «обернули» его декоратором. Все новые объекты теперь автоматически получают дату создания. Удобно для аудита, кэширования, логирования.
2. Декораторы внутри класса: @staticmethod, @classmethod, @property
Это встроенные декораторы Python, которые меняют поведение методов. Они применяются к методам, а не ко всему классу.
• @staticmethod — метод, который не получает ни self, ни cls. Просто функция внутри класса. Нужен для группировки логики.
• @classmethod — получает cls (класс) вместо self. Используется для альтернативных конструкторов.
• @property — позволяет обращаться к методу как к атрибуту. Геттер без скобок.
Пример:
🔹 КАКОЙ ДЕКОРАТОР ВЫБРАТЬ?
— Если нужно добавить функциональность ВСЕМ объектам класса (логирование, кэширование, валидация) → декоратор класса (как add_timestamp).
— Если нужно изменить поведение конкретного метода (сделать его свойством, фабрикой или просто функцией) → встроенные декораторы.
🔹 ПОДВОДНЫЕ КАМНИ
• Декоратор класса возвращает новый класс. Если ты используешь наследование, будь осторожен: декорированный класс может «потерять» родительские методы, если декоратор их не сохраняет.
• @property не работает с атрибутами, начинающимися с __ (двойное подчеркивание) — будет конфликт имен.
• Статический метод не имеет доступа к self, но может принимать аргументы. Не путай с методами класса.
🔹 ИТОГ
Декораторы для классов — это суперсила Python. Они позволяют писать чистый, переиспользуемый код без дублирования. На собеседовании покажи, что понимаешь разницу между декоратором класса и декоратором метода. И обязательно приведи пример из реальной практики — например, как ты добавлял логирование всем методам через декоратор класса.
А теперь вопрос к тебе: какой декоратор ты используешь чаще всего? Пиши в комментариях, обсудим! 👇
Вчера на собеседовании меня спросили: «Что такое декоратор для класса?» Я начал мямлить про @staticmethod… и провалился. Не повторяй мою ошибку! Разбираемся раз и навсегда.
🔹 СНАЧАЛА БАЗА: ЧТО ТАКОЕ ДЕКОРАТОР ВООБЩЕ?
Декоратор — это функция, которая принимает другую функцию (или класс) и возвращает её изменённую версию. Всё. Никакой магии. Просто «обёртка», которая добавляет код ДО и ПОСЛЕ выполнения исходного объекта.
Пример для функции:
def logger(func):
def wrapper(*args, **kwargs):
print(f"Вызов {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logger
def say_hello():
print("Привет!")
say_hello() # Выведет: Вызов say_hello / Привет!
🔹 А ТЕПЕРЬ ДЕКОРАТОРЫ ДЛЯ КЛАССОВ
Тут два принципиально разных сценария. Не путай их, иначе на собеседовании будет стыдно.
1. Декоратор, который применяется к классу (как к объекту)
Ты берёшь класс, оборачиваешь его в функцию-декоратор, и на выходе получаешь изменённый класс. Это мощный инструмент для добавления методов, атрибутов или логики всем экземплярам сразу.
Пример: добавим всем объектам класса атрибут created_at:
import datetime
def add_timestamp(cls):
original_init = cls.__init__
def new_init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
self.created_at = datetime.datetime.now()
cls.__init__ = new_init
return cls
@add_timestamp
class User:
def __init__(self, name):
self.name = name
u = User("Анна")
print(u.created_at) # 2025-03-30 12:00:00.123456
Видишь? Мы не трогали сам класс User, а просто «обернули» его декоратором. Все новые объекты теперь автоматически получают дату создания. Удобно для аудита, кэширования, логирования.
2. Декораторы внутри класса: @staticmethod, @classmethod, @property
Это встроенные декораторы Python, которые меняют поведение методов. Они применяются к методам, а не ко всему классу.
• @staticmethod — метод, который не получает ни self, ни cls. Просто функция внутри класса. Нужен для группировки логики.
• @classmethod — получает cls (класс) вместо self. Используется для альтернативных конструкторов.
• @property — позволяет обращаться к методу как к атрибуту. Геттер без скобок.
Пример:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def area(self):
return 3.14 * self._radius ** 2
@classmethod
def from_diameter(cls, diameter):
return cls(diameter / 2)
@staticmethod
def description():
return "Это круг"
🔹 КАКОЙ ДЕКОРАТОР ВЫБРАТЬ?
— Если нужно добавить функциональность ВСЕМ объектам класса (логирование, кэширование, валидация) → декоратор класса (как add_timestamp).
— Если нужно изменить поведение конкретного метода (сделать его свойством, фабрикой или просто функцией) → встроенные декораторы.
🔹 ПОДВОДНЫЕ КАМНИ
• Декоратор класса возвращает новый класс. Если ты используешь наследование, будь осторожен: декорированный класс может «потерять» родительские методы, если декоратор их не сохраняет.
• @property не работает с атрибутами, начинающимися с __ (двойное подчеркивание) — будет конфликт имен.
• Статический метод не имеет доступа к self, но может принимать аргументы. Не путай с методами класса.
🔹 ИТОГ
Декораторы для классов — это суперсила Python. Они позволяют писать чистый, переиспользуемый код без дублирования. На собеседовании покажи, что понимаешь разницу между декоратором класса и декоратором метода. И обязательно приведи пример из реальной практики — например, как ты добавлял логирование всем методам через декоратор класса.
А теперь вопрос к тебе: какой декоратор ты используешь чаще всего? Пиши в комментариях, обсудим! 👇