🔥 Что такое миграции в 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. Они позволяют писать чистый, переиспользуемый код без дублирования. На собеседовании покажи, что понимаешь разницу между декоратором класса и декоратором метода. И обязательно приведи пример из реальной практики — например, как ты добавлял логирование всем методам через декоратор класса.
А теперь вопрос к тебе: какой декоратор ты используешь чаще всего? Пиши в комментариях, обсудим! 👇
Твой Django сайт тормозит? База данных падает под нагрузкой?
Знакомая боль? А ведь решение лежит на поверхности — КЭШИРОВАНИЕ. И сегодня мы разберем, как подружить Django с Redis, чтобы твой проект летал.
ПОЧЕМУ REDIS, А НЕ ПРОСТО ФАЙЛЫ?
Redis — это хранилище ключ-значение в оперативной памяти. Это значит, что данные читаются МГНОВЕННО, а не с диска. Для кэша — идеальный вариант.
ЧТО НАМ НУЖНО СДЕЛАТЬ?
1. Установить Redis и библиотеку для Python:
2. Настроить Django, чтобы он знал, где наш Redis. В файле
Всё! Теперь Django будет хранить кэш в Redis.
КАК ЭТИМ ПОЛЬЗОВАТЬСЯ?
Есть три главных способа:
• Кэширование всего сайта — для простых проектов. Включается в
• Кэширование отдельных страниц — декоратор
• Кэширование фрагментов шаблона — в шаблоне:
НО САМЫЙ ГИБКИЙ СПОСОБ — ЭТО КЭШИРОВАНИЕ ДАННЫХ ВРУЧНУЮ.
Представь, у тебя есть список последних статей, который ты тянешь из базы при каждом запросе. А зачем? Давай сохраним его в Redis:
Первый раз — запрос в базу. Все последующие 15 минут — ответ из Redis. Мгновенно.
А ЧТО НАСЧЕТ СЕССИЙ?
По умолчанию Django хранит сессии в базе данных. Это медленно. Перенеси их в Redis — и аутентификация станет незаметной.
ГОТОВЬСЯ К ПОДВОДНЫМ КАМНЯМ!
• Устаревание данных — всегда ставь время жизни кэша (TTL). Иначе пользователи увидят старую информацию.
• Инвалидация кэша — если данные изменились, удали старый кэш:
• Redis не бесконечен — следи за памятью. Настрой политику вытеснения (например,
ИТОГ: Redis + Django = производительность, о которой ты мечтал. База отдыхает, пользователи счастливы, а ты — герой.
А ты уже используешь Redis в своих проектах? Или только планируешь? Пиши в комментариях!
Знакомая боль? А ведь решение лежит на поверхности — КЭШИРОВАНИЕ. И сегодня мы разберем, как подружить Django с Redis, чтобы твой проект летал.
ПОЧЕМУ REDIS, А НЕ ПРОСТО ФАЙЛЫ?
Redis — это хранилище ключ-значение в оперативной памяти. Это значит, что данные читаются МГНОВЕННО, а не с диска. Для кэша — идеальный вариант.
ЧТО НАМ НУЖНО СДЕЛАТЬ?
1. Установить Redis и библиотеку для Python:
pip install django-redis2. Настроить Django, чтобы он знал, где наш Redis. В файле
settings.py добавляем:CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}Всё! Теперь Django будет хранить кэш в Redis.
КАК ЭТИМ ПОЛЬЗОВАТЬСЯ?
Есть три главных способа:
• Кэширование всего сайта — для простых проектов. Включается в
settings.py добавлением django.middleware.cache.UpdateCacheMiddleware и FetchFromCacheMiddleware в MIDDLEWARE.• Кэширование отдельных страниц — декоратор
@cache_page(60 * 15) над view-функцией. Запоминает результат на 15 минут.• Кэширование фрагментов шаблона — в шаблоне:
{% load cache %}
{% cache 500 'sidebar' %}
... тяжелый контент ...
{% endcache %}НО САМЫЙ ГИБКИЙ СПОСОБ — ЭТО КЭШИРОВАНИЕ ДАННЫХ ВРУЧНУЮ.
Представь, у тебя есть список последних статей, который ты тянешь из базы при каждом запросе. А зачем? Давай сохраним его в Redis:
from django.core.cache import cache
def get_latest_articles():
articles = cache.get('latest_articles')
if not articles:
articles = Article.objects.order_by('-published')[:10]
cache.set('latest_articles', articles, 60 * 15) # на 15 минут
return articles
Первый раз — запрос в базу. Все последующие 15 минут — ответ из Redis. Мгновенно.
А ЧТО НАСЧЕТ СЕССИЙ?
По умолчанию Django хранит сессии в базе данных. Это медленно. Перенеси их в Redis — и аутентификация станет незаметной.
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
ГОТОВЬСЯ К ПОДВОДНЫМ КАМНЯМ!
• Устаревание данных — всегда ставь время жизни кэша (TTL). Иначе пользователи увидят старую информацию.
• Инвалидация кэша — если данные изменились, удали старый кэш:
cache.delete('latest_articles').• Redis не бесконечен — следи за памятью. Настрой политику вытеснения (например,
allkeys-lru).ИТОГ: Redis + Django = производительность, о которой ты мечтал. База отдыхает, пользователи счастливы, а ты — герой.
А ты уже используешь Redis в своих проектах? Или только планируешь? Пиши в комментариях!
ТЫ ДУМАЕШЬ, ЧТО ЗНАЕШЬ ГЕНЕРАТОРЫ? А ЧТО НАСЧЁТ АСИНХРОННЫХ? 🔥
Вот вопрос с реального собеседования: «Есть асинхронный генератор. Что будет, если вызвать его через async for? А если просто for?» — и кандидат впадает в ступор. Не будь таким! Разбираемся раз и навсегда.
Асинхронный генератор — это корутина, которая умеет yield-ить значения, не блокируя event loop. Обычный генератор блокирует поток, пока не дойдёт до следующего yield. Асинхронный — отдаёт управление обратно в event loop, позволяя выполняться другим задачам.
Как это выглядит?
Видишь
Как его потреблять?
Только через
Если попытаешься использовать обычный
А если нужно получить все значения разом?
Используй
Или
Главное отличие от обычного генератора:
1. Не блокирует поток — внутри можно использовать
2. Работает только в асинхронном контексте — внутри async функции.
3. Тип возвращаемого объекта —
Типичный сценарий использования:
Представь, что ты читаешь строки из большого файла по сети. Вместо того чтобы загрузить весь файл в память (и упасть на 10 ГБ), ты читаешь по кусочку, и пока ждёшь данные от диска — event loop обрабатывает запросы других пользователей.
Красота, правда?
Важный нюанс: асинхронный генератор сам по себе не запускается. Он ленивый. Пока не вызовешь
Как проверить на собеседовании, что кандидат шарит?
Спроси: «Что вернёт
Итог:
Асинхронные генераторы — это мощный инструмент для потоковой обработки данных без блокировок. Они позволяют писать эффективные I/O-bound приложения, не тратя память на гигантские списки.
Запомни:
А теперь вопрос к тебе: пробовал уже писать асинхронные генераторы в бою? Или только читаешь? Пиши в комментариях! 👇
Вот вопрос с реального собеседования: «Есть асинхронный генератор. Что будет, если вызвать его через async for? А если просто for?» — и кандидат впадает в ступор. Не будь таким! Разбираемся раз и навсегда.
Асинхронный генератор — это корутина, которая умеет yield-ить значения, не блокируя event loop. Обычный генератор блокирует поток, пока не дойдёт до следующего yield. Асинхронный — отдаёт управление обратно в event loop, позволяя выполняться другим задачам.
Как это выглядит?
import asyncio
async def fetch_data():
for i in range(5):
await asyncio.sleep(1) # имитация долгой операции
yield i
Видишь
async def и yield? Это и есть асинхронный генератор. Он возвращает асинхронный итератор.Как его потреблять?
Только через
async for:
async def main():
async for value in fetch_data():
print(value)
asyncio.run(main())
Если попытаешься использовать обычный
for — получишь TypeError: 'async_generator' object is not iterable. Жёстко, но честно.А если нужно получить все значения разом?
Используй
async list comprehension:
result = [value async for value in fetch_data()]
Или
asyncio.gather с обёрткой, если нужен конкурентный запуск нескольких генераторов.Главное отличие от обычного генератора:
1. Не блокирует поток — внутри можно использовать
await.2. Работает только в асинхронном контексте — внутри async функции.
3. Тип возвращаемого объекта —
async_generator, а не generator.Типичный сценарий использования:
Представь, что ты читаешь строки из большого файла по сети. Вместо того чтобы загрузить весь файл в память (и упасть на 10 ГБ), ты читаешь по кусочку, и пока ждёшь данные от диска — event loop обрабатывает запросы других пользователей.
async def read_lines(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
async for line in resp.content:
yield line
Красота, правда?
Важный нюанс: асинхронный генератор сам по себе не запускается. Он ленивый. Пока не вызовешь
__anext__() (что делает async for), код внутри не выполняется.Как проверить на собеседовании, что кандидат шарит?
Спроси: «Что вернёт
type(fetch_data())?» Правильный ответ: <class 'async_generator'>. А если спросить про inspect.isasyncgen — это вообще уровень Senior.Итог:
Асинхронные генераторы — это мощный инструмент для потоковой обработки данных без блокировок. Они позволяют писать эффективные I/O-bound приложения, не тратя память на гигантские списки.
Запомни:
async def + yield = асинхронный генератор. Используй только с async for.А теперь вопрос к тебе: пробовал уже писать асинхронные генераторы в бою? Или только читаешь? Пиши в комментариях! 👇
ПОЧЕМУ ТВОЙ PYTHON ЖРЁТ ПАМЯТЬ НА ПРОДЕ, А ТЫ ДУМАЕШЬ, ЧТО ВСЁ НОРМАЛЬНО?
Слушай, это классика. Сидишь такой, написал крутой высоконагруженный сервис, всё летает. А через час — бац! — память улетела в космос, и процесс убили OOM-killer'ом. И ты такой: «Я же всё чищу, в чём проблема?»
А проблема в том, что ты не понимаешь, как работает Garbage Collection (GC) в Python. Давай разбираться, чтобы на собеседовании не краснеть, а на проде не тушить пожары.
1. ДВА МИРА GC В CPYTHON
В Python (CPython) живут два сборщика мусора, и они работают параллельно:
• Подсчёт ссылок (Reference Counting) — это база. Он не отключается. Каждый объект хранит счётчик: сколько переменных на него ссылаются. Как только счётчик падает до нуля — объект уничтожается мгновенно. Всё просто и быстро.
• Поколенческий GC (Generational GC) — это дополнительный механизм, который решает главную проблему подсчёта ссылок: циклические ссылки.
Представь: два объекта ссылаются друг на друга, но больше никто на них не ссылается. Счётчики ссылок у каждого = 1. Подсчёт ссылок никогда не обнулит их! И вот тут в игру вступает поколенческий GC.
2. КАК РАБОТАЕТ ПОКОЛЕНЧЕСКИЙ GC?
Он делит объекты на три поколения (0, 1, 2). Новые объекты попадают в поколение 0. Если объект переживает сборку мусора, он переезжает в следующее поколение. Чем старше поколение, тем реже его проверяют.
GC запускается, когда количество выделенных объектов минус количество освобождённых превышает порог (по умолчанию 700 для поколения 0). Он находит циклические ссылки и уничтожает их.
3. А ЗАЧЕМ ЕГО ОТКЛЮЧАТЬ?
Для критичных по производительности участков! GC — это дополнительная работа. Он может запуститься в самый неподходящий момент и вызвать stop-the-world паузу. Если тебе нужно выдать ответ за 10 мс, а GC решил почистить память — привет, таймаут.
4. КАК ОТКЛЮЧИТЬ GC?
Всё просто. Используй модуль
Но! Запомни: подсчёт ссылок не отключается никогда. Если у тебя есть циклические ссылки, они станут утечкой памяти. Поэтому:
• Либо убедись, что в критическом участке нет циклических ссылок.
• Либо используй
• Либо вызывай
5. КОГДА ЭТО ДЕЙСТВИТЕЛЬНО НУЖНО?
• Высокочастотная торговля.
• Игровые серверы в реальном времени.
• Обработка видео/аудио потоков.
• Любой код, где каждая миллисекунда на счету.
Но для 99% приложений отключать GC не нужно. Просто знай, что такая возможность есть.
ИТОГ:
На собеседовании тебя спросят: «Как работает GC в Python?» — ты отвечаешь: два механизма, подсчёт ссылок (фундаментальный) и поколенческий GC (для циклов). И добавляешь: «Могу отключить поколенческий GC для критичных участков через
Понял? Теперь иди и расскажи это интервьюеру. 🚀
Слушай, это классика. Сидишь такой, написал крутой высоконагруженный сервис, всё летает. А через час — бац! — память улетела в космос, и процесс убили OOM-killer'ом. И ты такой: «Я же всё чищу, в чём проблема?»
А проблема в том, что ты не понимаешь, как работает Garbage Collection (GC) в Python. Давай разбираться, чтобы на собеседовании не краснеть, а на проде не тушить пожары.
1. ДВА МИРА GC В CPYTHON
В Python (CPython) живут два сборщика мусора, и они работают параллельно:
• Подсчёт ссылок (Reference Counting) — это база. Он не отключается. Каждый объект хранит счётчик: сколько переменных на него ссылаются. Как только счётчик падает до нуля — объект уничтожается мгновенно. Всё просто и быстро.
• Поколенческий GC (Generational GC) — это дополнительный механизм, который решает главную проблему подсчёта ссылок: циклические ссылки.
Представь: два объекта ссылаются друг на друга, но больше никто на них не ссылается. Счётчики ссылок у каждого = 1. Подсчёт ссылок никогда не обнулит их! И вот тут в игру вступает поколенческий GC.
2. КАК РАБОТАЕТ ПОКОЛЕНЧЕСКИЙ GC?
Он делит объекты на три поколения (0, 1, 2). Новые объекты попадают в поколение 0. Если объект переживает сборку мусора, он переезжает в следующее поколение. Чем старше поколение, тем реже его проверяют.
GC запускается, когда количество выделенных объектов минус количество освобождённых превышает порог (по умолчанию 700 для поколения 0). Он находит циклические ссылки и уничтожает их.
3. А ЗАЧЕМ ЕГО ОТКЛЮЧАТЬ?
Для критичных по производительности участков! GC — это дополнительная работа. Он может запуститься в самый неподходящий момент и вызвать stop-the-world паузу. Если тебе нужно выдать ответ за 10 мс, а GC решил почистить память — привет, таймаут.
4. КАК ОТКЛЮЧИТЬ GC?
Всё просто. Используй модуль
gc:import gc
gc.disable() # Отключаем поколенческий GC
# Твой критичный код
# ...
gc.enable() # Включаем обратно
Но! Запомни: подсчёт ссылок не отключается никогда. Если у тебя есть циклические ссылки, они станут утечкой памяти. Поэтому:
• Либо убедись, что в критическом участке нет циклических ссылок.
• Либо используй
weakref (слабые ссылки) для предотвращения циклов.• Либо вызывай
gc.collect() вручную после критического участка.5. КОГДА ЭТО ДЕЙСТВИТЕЛЬНО НУЖНО?
• Высокочастотная торговля.
• Игровые серверы в реальном времени.
• Обработка видео/аудио потоков.
• Любой код, где каждая миллисекунда на счету.
Но для 99% приложений отключать GC не нужно. Просто знай, что такая возможность есть.
ИТОГ:
На собеседовании тебя спросят: «Как работает GC в Python?» — ты отвечаешь: два механизма, подсчёт ссылок (фундаментальный) и поколенческий GC (для циклов). И добавляешь: «Могу отключить поколенческий GC для критичных участков через
gc.disable(), но с осторожностью из-за циклических ссылок». — И ты уже сеньор.Понял? Теперь иди и расскажи это интервьюеру. 🚀
Ты пишешь код, и твой IDE подсвечивает: "Cannot access attribute 'x' for class 'A'"? Или ты хочешь, чтобы твой type checker наконец-то понимал, что после твоей проверки тип изменился? Тогда встречай — TypeGuard! Это не просто очередная фича, это твой ключ к безопасному коду. Давай разберёмся, как это работает и почему без него твой код — как кот Шрёдингера: и жив, и мёртв одновременно.
СУТЬ ПРОБЛЕМЫ
Представь: у тебя есть функция, которая проверяет, является ли объект, скажем, собакой. Ты пишешь:
Вот тут и начинается боль. Python — язык с динамической типизацией, но мы хотим, чтобы статический анализатор (mypy, Pyright) понимал, что внутри if объект — точно собака. Обычная аннотация -> bool не даёт анализатору этой информации. Он видит только "вернётся bool", а не "если True, то объект — Dog".
ВОТ ТУТ И ПОЯВЛЯЕТСЯ TYPEGUARD
TypeGuard — это специальная конструкция из модуля typing (Python 3.10+), которая говорит type checker'у: "Слушай, если эта функция вернула True, то переданный аргумент можно считать вот этим типом". Это как волшебный пропуск для анализатора типов.
Синтаксис простой:
Теперь, если ты напишешь:
Анализатор понимает: внутри блока if переменная some_obj имеет тип dict. Магия? Нет, просто TypeGuard!
КАК ЭТО РАБОТАЕТ ВНУТРИ?
На самом деле, в рантайме TypeGuard ничего не делает. Это чисто "подсказка" для статических анализаторов. Твоя функция is_dog по-прежнему возвращает обычный bool. Но когда type checker видит аннотацию TypeGuard[dict], он включает логику сужения типов (type narrowing).
Это как если бы ты сказал другу: "Если я кивну, значит, это точно пицца". Твой друг (type checker) теперь знает, что после кивка можно смело есть.
ВАЖНЫЙ НЮАНС, О КОТОРОМ ВСЕ ЗАБЫВАЮТ
TypeGuard требует, чтобы функция принимала хотя бы один аргумент. И сужение типа работает только для этого аргумента. Нельзя написать TypeGuard[dict] для функции без параметров — будет ошибка. Также помни: TypeGuard не проверяет тип возвращаемого значения в рантайме. Если ты соврёшь в аннотации (скажешь TypeGuard[dict], а вернёшь True для списка), то type checker тебе поверит, и ты получишь ошибку уже в рантайме. Так что будь честен со своим анализатором!
ГДЕ ЭТО ПРИМЕНЯТЬ?
1. Парсинг данных: проверка, что JSON-ответ от API имеет нужную структуру.
2. Работа с внешними библиотеками: когда данные приходят в виде object, а ты знаешь, что внутри.
3. Валидация пользовательского ввода: перед тем как работать с данными, убедись, что они нужного типа.
Вот пример из реальной жизни — парсинг ответа API:
ЧТО НА ИНТЕРВЬЮ?
Если спросят "Что такое TypeGuard?" — отвечай: "Это инструмент для сужения типов в статической типизации. Он позволяет функции-проверке сообщить анализатору, что при возврате True аргумент имеет определённый тип". И обязательно упомяни, что это работает только на уровне анализа, а не в рантайме.
Помни: TypeGuard — это не про производительность, а про безопасность и читаемость кода. Твой код становится самодокументируемым, а баги с типами ловятся ещё до запуска.
Так что вперёд — переписывай свои проверки на TypeGuard и удивляй коллег на code review! А если хочешь больше таких разборов — ставь реакцию и подписывайся, чтобы не пропустить следующую тему!
СУТЬ ПРОБЛЕМЫ
Представь: у тебя есть функция, которая проверяет, является ли объект, скажем, собакой. Ты пишешь:
def is_dog(obj):
return hasattr(obj, 'gav')
if is_dog(some_obj):
some_obj.gav() # type checker ругается!
Вот тут и начинается боль. Python — язык с динамической типизацией, но мы хотим, чтобы статический анализатор (mypy, Pyright) понимал, что внутри if объект — точно собака. Обычная аннотация -> bool не даёт анализатору этой информации. Он видит только "вернётся bool", а не "если True, то объект — Dog".
ВОТ ТУТ И ПОЯВЛЯЕТСЯ TYPEGUARD
TypeGuard — это специальная конструкция из модуля typing (Python 3.10+), которая говорит type checker'у: "Слушай, если эта функция вернула True, то переданный аргумент можно считать вот этим типом". Это как волшебный пропуск для анализатора типов.
Синтаксис простой:
from typing import TypeGuard
def is_dog(obj: object) -> TypeGuard[dict]:
return isinstance(obj, dict) and 'gav' in obj
Теперь, если ты напишешь:
if is_dog(some_obj):
some_obj['gav']() # type checker больше не ругается!
Анализатор понимает: внутри блока if переменная some_obj имеет тип dict. Магия? Нет, просто TypeGuard!
КАК ЭТО РАБОТАЕТ ВНУТРИ?
На самом деле, в рантайме TypeGuard ничего не делает. Это чисто "подсказка" для статических анализаторов. Твоя функция is_dog по-прежнему возвращает обычный bool. Но когда type checker видит аннотацию TypeGuard[dict], он включает логику сужения типов (type narrowing).
Это как если бы ты сказал другу: "Если я кивну, значит, это точно пицца". Твой друг (type checker) теперь знает, что после кивка можно смело есть.
ВАЖНЫЙ НЮАНС, О КОТОРОМ ВСЕ ЗАБЫВАЮТ
TypeGuard требует, чтобы функция принимала хотя бы один аргумент. И сужение типа работает только для этого аргумента. Нельзя написать TypeGuard[dict] для функции без параметров — будет ошибка. Также помни: TypeGuard не проверяет тип возвращаемого значения в рантайме. Если ты соврёшь в аннотации (скажешь TypeGuard[dict], а вернёшь True для списка), то type checker тебе поверит, и ты получишь ошибку уже в рантайме. Так что будь честен со своим анализатором!
ГДЕ ЭТО ПРИМЕНЯТЬ?
1. Парсинг данных: проверка, что JSON-ответ от API имеет нужную структуру.
2. Работа с внешними библиотеками: когда данные приходят в виде object, а ты знаешь, что внутри.
3. Валидация пользовательского ввода: перед тем как работать с данными, убедись, что они нужного типа.
Вот пример из реальной жизни — парсинг ответа API:
from typing import TypeGuard, Any
def is_user_data(data: dict[str, Any]) -> TypeGuard[dict[str, str]]:
return all(isinstance(v, str) for v in data.values())
# Теперь безопасно:
def process(data: dict[str, Any]):
if is_user_data(data):
# Здесь data уже dict[str, str]
print(data['name'].upper())
ЧТО НА ИНТЕРВЬЮ?
Если спросят "Что такое TypeGuard?" — отвечай: "Это инструмент для сужения типов в статической типизации. Он позволяет функции-проверке сообщить анализатору, что при возврате True аргумент имеет определённый тип". И обязательно упомяни, что это работает только на уровне анализа, а не в рантайме.
Помни: TypeGuard — это не про производительность, а про безопасность и читаемость кода. Твой код становится самодокументируемым, а баги с типами ловятся ещё до запуска.
Так что вперёд — переписывай свои проверки на TypeGuard и удивляй коллег на code review! А если хочешь больше таких разборов — ставь реакцию и подписывайся, чтобы не пропустить следующую тему!