Python собеседование Senior 2026 — 8 вопросов
S.
Sobes AI

Вопросы по Python на собеседовании: Senior — часть 3/3 с разбором ответов

09.03.2026 | 7 мин чтения | 7 просмотров

Вопросы по Python на собеседовании: Senior — часть 3/3 с разбором ответов

TL;DR: 8 вопросов Senior-уровня: метаклассы, дескрипторы, asyncio, MRO, потоки vs процессы vs asyncio, профилирование, ABC vs Protocol, и что нового в Python 3.13+. Это территория архитектурных решений и глубокого понимания рантайма.

Как пользоваться: Если Middle-вопросы не вызывают затруднений — проверь себя здесь. Senior-собеседование — это не столько правильный ответ, сколько глубина рассуждений и trade-off анализ.

Senior-собеседование по Python — это разговор, а не экзамен. Интервьюер хочет услышать, как ты думаешь, какие компромиссы видишь, и как принимаешь решения. По статистике собеседований за 2024–2026, Python — #1 по количеству технических интервью, и Senior-позиции требуют понимания внутренностей CPython, асинхронности и архитектурных паттернов.

Содержание

Senior


17. Метаклассы — что это и зачем?

Интервьюер проверяет: понимаешь ли ты, что классы в Python — тоже объекты.

В Python всё — объект. Классы — тоже объекты. Метакласс — это «класс класса», то есть то, что создаёт классы. По умолчанию метакласс — type.

# Обычное создание класса
class MyClass:
    pass

# Эквивалент через type
MyClass = type('MyClass', (), {})

# type — это метакласс по умолчанию
type(MyClass)  # <class 'type'>
type(type)     # <class 'type'> — type сам себе метакласс

Свой метакласс:

class AutoRepr(type):
    """Автоматически добавляет __repr__ ко всем классам."""
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        attrs = [k for k in namespace if not k.startswith('_')]

        def __repr__(self):
            values = ', '.join(
                f'{a}={getattr(self, a)!r}' for a in attrs
            )
            return f'{name}({values})'

        cls.__repr__ = __repr__
        return cls

class User(metaclass=AutoRepr):
    def __init__(self, name, age):
        self.name = name
        self.age = age

print(User("Alice", 30))  # User(name='Alice', age=30)

Реальные применения: - ORM (Django models) — метакласс собирает поля из атрибутов класса - API-фреймворки (Django REST, Pydantic v1) — валидация на уровне класса - Реестры — автоматическая регистрация подклассов

Когда НЕ использовать: в 99% случаев. __init_subclass__ (Python 3.6+) или декораторы классов решают те же задачи проще.

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

Follow-up: Чем метакласс отличается от декоратора класса? Когда нужен именно метакласс?


18. Дескрипторы и протокол дескрипторов

Интервьюер проверяет: знаешь ли ты механизм, на котором работают @property, @classmethod, @staticmethod.

Дескриптор — объект, определяющий хотя бы один из методов: __get__, __set__, __delete__.

class Validated:
    """Дескриптор, проверяющий тип значения."""
    def __init__(self, expected_type):
        self.expected_type = expected_type
        self.name = None

    def __set_name__(self, owner, name):
        self.name = name  # Python 3.6+

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj.__dict__.get(self.name)

    def __set__(self, obj, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(
                f"{self.name} must be {self.expected_type.__name__}, "
                f"got {type(value).__name__}"
            )
        obj.__dict__[self.name] = value

class User:
    name = Validated(str)
    age = Validated(int)

    def __init__(self, name, age):
        self.name = name  # вызывает Validated.__set__
        self.age = age

user = User("Alice", 30)   # OK
user = User("Alice", "30") # TypeError!

Два типа дескрипторов: - Data descriptor — определяет __set__ и/или __delete__. Приоритет выше, чем __dict__ экземпляра. - Non-data descriptor — только __get__. Приоритет ниже, чем __dict__.

@property — это дескриптор:

# property — встроенный data descriptor
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def area(self):
        return 3.14159 * self._radius ** 2

# Эквивалентно:
# area = property(fget=lambda self: ...)

Типичная ошибка: не понимать порядок поиска атрибутов (data descriptor → instance dict → non-data descriptor).

Follow-up: Почему @staticmethod и @classmethod работают? Какой протокол дескрипторов они реализуют?


19. asyncio — event loop, корутины, задачи

Интервьюер проверяет: умеешь ли ты проектировать асинхронные системы.

asyncio — однопоточная модель конкурентности через event loop. Корутины (async def) приостанавливаются на await, позволяя другим задачам выполняться.

import asyncio

async def fetch_data(url, delay):
    print(f"Start fetching {url}")
    await asyncio.sleep(delay)  # имитация I/O
    print(f"Done fetching {url}")
    return f"Data from {url}"

async def main():
    # Параллельный запуск через gather
    results = await asyncio.gather(
        fetch_data("api/users", 2),
        fetch_data("api/orders", 1),
        fetch_data("api/products", 3),
    )
    # Все три завершатся за ~3 секунды, не за 6

asyncio.run(main())

Ключевые концепции:

- Coroutine — функция с async def. Вызов возвращает coroutine object, а не результат. - Task — обёртка над coroutine, запланированная для выполнения в event loop. - await — точка, где корутина приостанавливается и отдаёт управление loop.

async def main():
    # Task — запускается сразу, не ждёт await
    task = asyncio.create_task(fetch_data("api/users", 2))
    # ... другая работа ...
    result = await task  # ждём результат

gather vs TaskGroup (Python 3.11+):

# TaskGroup — структурная конкурентность, лучше обработка ошибок
async def main():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(fetch_data("api/users", 2))
        task2 = tg.create_task(fetch_data("api/orders", 1))
    # Если одна задача упала — все отменяются

Типичная ошибка: вызывать блокирующий код (синхронный I/O, time.sleep, тяжёлые вычисления) внутри корутины — это блокирует весь event loop.

# BAD — блокирует event loop
async def bad():
    import requests
    return requests.get("https://api.example.com")  # синхронный!

# GOOD — выносим в executor
async def good():
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(None, requests.get, url)

# BEST — используем async-библиотеку
async def best():
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.json()

Follow-up: Чем asyncio.gather отличается от asyncio.wait? Когда использовать TaskGroup?


Мы разобрали три столпа Senior-знаний: метапрограммирование, дескрипторы и асинхронность. Дальше — наследование, конкурентность, производительность и будущее Python.


20. MRO и проблема ромба

Интервьюер проверяет: понимаешь ли ты, как Python разрешает множественное наследование.

MRO (Method Resolution Order) — порядок, в котором Python ищет методы при множественном наследовании. Используется алгоритм C3-линеаризации.

class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

class C(A):
    def method(self):
        return "C"

class D(B, C):
    pass

d = D()
print(d.method())  # "B"
print(D.__mro__)
# (D, B, C, A, object)

Правила C3-линеаризации: - Дети перед родителями - Порядок наследования сохраняется (B перед C, потому что class D(B, C)) - Монотонность — если B перед C в MRO, то и в любом подклассе

super() и кооперативное наследование:

class A:
    def __init__(self):
        print("A.__init__")
        super().__init__()

class B(A):
    def __init__(self):
        print("B.__init__")
        super().__init__()

class C(A):
    def __init__(self):
        print("C.__init__")
        super().__init__()

class D(B, C):
    def __init__(self):
        print("D.__init__")
        super().__init__()

D()
# D.__init__
# B.__init__
# C.__init__
# A.__init__

super() следует MRO, а не прямому родителю. B.super() вызывает C.__init__, не A.__init__.

Типичная ошибка: думать, что super() в B всегда вызывает A. В кооперативном наследовании super() следует MRO текущего экземпляра.

Follow-up: Когда C3-линеаризация невозможна и Python выбросит TypeError?


21. Потоки vs процессы vs asyncio — когда что?

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

- threading — для I/O-bound задач с shared state. GIL не мешает, потому что потоки ждут I/O. - multiprocessing — для CPU-bound задач. Каждый процесс — свой GIL, настоящий параллелизм. - asyncio — для I/O-bound с большим количеством конкурентных задач (тысячи соединений). Один поток, без overhead потоков.

from concurrent.futures import (
    ThreadPoolExecutor,
    ProcessPoolExecutor
)
import asyncio

# I/O-bound: скачать 100 URL
# Потоки — просто, shared state
with ThreadPoolExecutor(max_workers=10) as ex:
    results = list(ex.map(requests.get, urls))

# I/O-bound: 10 000 WebSocket-соединений
# asyncio — меньше overhead
async def handle_connections():
    tasks = [handle(ws) for ws in websockets]
    await asyncio.gather(*tasks)

# CPU-bound: обработка 1000 изображений
# Процессы — настоящий параллелизм
with ProcessPoolExecutor() as ex:
    results = list(ex.map(process_image, images))

Сравнение:

- Threading: ~8 КБ на поток, shared memory, GIL-ограничение для CPU. Хорош для 10-100 параллельных I/O задач. - Multiprocessing: ~30 МБ на процесс, изолированная память, pickle для IPC. Хорош для CPU-параллелизма. - Asyncio: ~1 КБ на корутину, один поток, нужны async-библиотеки. Хорош для 1000+ конкурентных I/O задач.

Типичная ошибка: использовать asyncio для CPU-bound или multiprocessing для простых HTTP-запросов — overhead не оправдан.

Follow-up: Как комбинировать asyncio и multiprocessing для задачи, которая и I/O-bound, и CPU-bound?


22. Профилирование и оптимизация памяти

Интервьюер проверяет: умеешь ли ты находить узкие места в production-коде.

Профилирование CPU:

# cProfile — встроенный, детерминистический
import cProfile
cProfile.run('my_function()', sort='cumulative')

# line_profiler — построчный анализ
# pip install line_profiler
@profile  # декоратор line_profiler
def slow_function():
    result = [x ** 2 for x in range(10**6)]  # 0.3s
    filtered = [x for x in result if x % 2]  # 0.2s
    return sum(filtered)                       # 0.01s

Профилирование памяти:

# tracemalloc — встроенный, Python 3.4+
import tracemalloc

tracemalloc.start()
data = [x ** 2 for x in range(10**6)]
snapshot = tracemalloc.take_snapshot()

for stat in snapshot.statistics('lineno')[:5]:
    print(stat)

# objgraph — визуализация ссылок (pip install objgraph)
import objgraph
objgraph.show_most_common_types(limit=10)
objgraph.show_backrefs(obj, max_depth=3)

Типичные утечки памяти: - Циклические ссылки с __del__ — GC не может собрать - Глобальные кеши без ограничения размера — functools.lru_cache без maxsize - Замыкания, захватывающие большие объекты - Незакрытые файлы/соединения

# Утечка через неограниченный кеш
from functools import lru_cache

@lru_cache(maxsize=None)  # опасно! растёт без ограничений
def get_user(user_id):
    return db.query(User, user_id)

# Безопасно
@lru_cache(maxsize=1000)
def get_user(user_id):
    return db.query(User, user_id)

Типичная ошибка: оптимизировать без профилирования. «Premature optimization is the root of all evil» — сначала измерь, потом оптимизируй.

Follow-up: Как найти утечку памяти в долго работающем production-сервисе?


23. ABC vs Protocol — когда что использовать?

Интервьюер проверяет: понимаешь ли ты разницу между номинальной и структурной типизацией.

ABC (Abstract Base Class) — номинальная типизация. Класс должен явно наследовать ABC.

from abc import ABC, abstractmethod

class Repository(ABC):
    @abstractmethod
    def get(self, id: int):
        ...

    @abstractmethod
    def save(self, entity):
        ...

class UserRepository(Repository):
    def get(self, id: int):
        return db.query(User, id)

    def save(self, entity):
        db.add(entity)

# Нельзя создать без реализации всех abstractmethod
# Repository()  # TypeError!

Protocol (Python 3.8+, typing.Protocol) — структурная типизация (duck typing со статической проверкой). Класс НЕ наследует Protocol — достаточно реализовать нужные методы.

from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None:
        ...

class Circle:  # НЕ наследует Drawable!
    def draw(self) -> None:
        print("Drawing circle")

def render(shape: Drawable) -> None:
    shape.draw()

render(Circle())  # OK — mypy проверит структуру

Когда что: - ABC — когда нужна гарантия контракта в runtime. Наследование явное, isinstance() работает. - Protocol — когда нужна гибкость без связывания. Для библиотек, плагинов, интеграций.

В Python 3.12+ Protocol стал runtime-checkable по умолчанию:

from typing import Protocol, runtime_checkable

@runtime_checkable
class Closable(Protocol):
    def close(self) -> None:
        ...

import io
isinstance(io.StringIO(), Closable)  # True

Типичная ошибка: использовать ABC для каждого интерфейса. Protocol лучше подходит для Python, потому что сохраняет дух duck typing.

Follow-up: Как совместить ABC и Protocol в одном проекте?


Последний вопрос — про будущее Python. Senior должен понимать, куда движется язык.


24. Что нового в Python 3.12–3.13 и будущее языка

Интервьюер проверяет: следишь ли ты за развитием языка.

Python 3.12 (октябрь 2023):

- Per-interpreter GIL (PEP 684) — каждый субинтерпретатор получает свой GIL. Шаг к настоящему параллелизму. - Type Parameter Syntax (PEP 695):

# До 3.12
from typing import TypeVar
T = TypeVar('T')
def first(lst: list[T]) -> T: ...

# Python 3.12+
def first[T](lst: list[T]) -> T: ...

# Дженерик-классы
class Stack[T]:
    def push(self, item: T) -> None: ...
    def pop(self) -> T: ...

- f-string без ограничений — вложенные кавычки, обратные слеши, комментарии.

Python 3.13 (октябрь 2024):

- Free-threaded mode (PEP 703) — экспериментальная сборка CPython без GIL. Настоящие параллельные потоки.

# Установка free-threaded сборки
python3.13t  # суффикс 't' для free-threaded

# Проверка
import sys
sys._is_gil_enabled()  # False в free-threaded сборке

- JIT-компилятор (PEP 744) — экспериментальный copy-and-patch JIT. Пока +5% производительности, но фундамент для будущих оптимизаций. - Улучшенный REPL — многострочное редактирование, цветной вывод. - Улучшенные сообщения об ошибках — ещё более точные подсказки.

Что это значит для разработчиков:

  • Free-threaded Python меняет парадигму — CPU-bound код на потоках станет реальным
  • Но пока это экспериментально — C-расширения (NumPy, pandas) нужно адаптировать
  • JIT пока минимальный, но в 3.14+ ожидается значительное ускорение

Типичная ошибка: говорить «GIL убрали в Python 3.13». Нет — GIL опциональный, в отдельной сборке, и пока экспериментальный.

Follow-up: Как free-threaded Python повлияет на существующие библиотеки? Что нужно адаптировать?


Итого Senior: 8 вопросов. Фокус — метапрограммирование, асинхронность, внутренности CPython, архитектурные решения. На Senior-собесе важен не столько ответ, сколько глубина рассуждений и способность обсуждать trade-offs.

Чего НЕ спрашивают на Senior

  • Синтаксис базовых конструкций — это Junior-территория.
  • «Напишите сортировку» — от сеньора ждут архитектуру, а не алгоритмы.
  • Конкретные API-методы наизусть — важнее принципы, чем dict.setdefault vs collections.defaultdict.
  • Задачи на время — Senior-собесы обычно в формате дискуссии, не speedrun.

Как готовиться к Senior Python-собеседованию

  • Читай PEP — хотя бы ключевые: 703, 684, 695, 572, 3107. Это показывает глубину.
  • Изучи исходники CPythonObjects/, Python/ceval.c. Не весь, а ключевые модули.
  • Построй что-то с asyncio — не TODO-лист, а реальный concurrency: WebSocket-сервер, crawler.
  • Подготовь истории — «расскажите про сложную проблему» с конкретными метриками и решениями.
  • Пройди mock-интервью — навык объяснять архитектурные решения тренируется только практикой.

Как попробовать Sobes AI

Sobes AI — AI-помощник для технических собеседований. Как он поможет на Senior-уровне:

  1. Скачай приложение на sobesai.app
  2. Выбери Senior-режим — AI задаёт вопросы с глубокими follow-up
  3. Потренируй trade-off дискуссии — «почему asyncio, а не threading?»
  4. Разбери архитектурные кейсы — system design с Python-спецификой
  5. Используй на реальном собесе — AI анализирует вопрос и подсказывает структуру ответа

Готовитесь к собеседованию?

Sobes AI слушает вопросы интервьюера и генерирует ответы в реальном времени.

Скачать Sobes AI