Python для технического собеседования: от Junior к Middle

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

1. Продвинутые структуры данных и алгоритмы в Python

Продвинутые структуры данных и алгоритмы в Python

Почему на собеседовании вас просят реализовать стек на списках, а не просто сказать «я знаю, что это такое»? Потому что знание структур данных — это не зубрёжка определений, а умение выбирать правильный инструмент под задачу. Ошибка в выборе структуры данных превращает быстрый код в тормозящий монстр. А интервьюер именно это и проверяет: понимаете ли вы, почему используете dict, а не list.

Сложность операций: единственная таблица, которую нужно знать наизусть

Прежде чем разбирать конкретные структуры, зафиксируем базу — асимптотическую сложность операций. На собеседовании вас спросят: «Какова сложность поиска элемента в set?» — и ждут ответа в среднем случае.

| Структура | Доступ по индексу | Поиск элемента | Вставка | Удаление | |-----------|-------------------|----------------|---------|----------| | list | | | в середину, в конец | | | dict | по ключу | по ключу | | | | set | — | | | | | deque | | | с обоих концов | с обоих концов |

> Главное правило: если вам нужен быстрый поиск по значению — set или dict. Если важен порядок и доступ по индексу — list. Если нужны вставки и удаления с обоих концов — deque.

Стек и очередь: когда list — плохой выбор

Стек — это структура «последний вошёл — первый вышел» (LIFO). Реализация на list проста:

Очередь — «первый вошёл — первый вышел» (FIFO). Если реализовать её на list, удаление из начала будет , потому что все элементы сдвигаются. Именно поэтому для очереди используют collections.deque:

На собеседовании часто просят реализовать стек с получением минимума за . Идея: хранить второй стек, в котором на каждом уровне — текущий минимум:

Словари: defaultdict, Counter и хитрости подсчёта

Самая частая задача на собеседовании, связанная со словарями, — подсчёт элементов. Без Counter вы пишете:

С Counter — одну строку:

Counter умеет больше: most_common(n) вернёт самых частых элементов, а арифметика между Counter работает как с мультимножествами:

defaultdict избавляет от проверки наличия ключа. Типичный кейс — группировка элементов:

Без defaultdict пришлось бы писать if department not in groups: groups[department] = [] — лишний код, лишние ошибки.

Куча и приоритетная очередь

Куча (heap) — структура данных, которая за возвращает минимальный элемент и за выполняет вставку и удаление. В Python это модуль heapq, работающий с обычным list:

> Python реализует min-heap. Если нужна max-heap — вставляйте отрицательные значения: heapq.heappush(heap, -value).

Классическая задача: найти наибольших элементов. В лоб — сортировка за . С кучей — , что выгодно при малом :

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

Хэш-таблицы и задачи на Two Sum

Хэш-таблица — это то, как dict и set устроены внутри. Знание этого принципа критично, потому что целый класс задач решается именно через «жертвуем памятью ради скорости».

Классика — Two Sum: найти два числа в массиве, сумма которых равна целевой. Перебор всех пар — . Через dict — :

Этот паттерн — «пройти один раз, запомнить в dict» — встречается в десятках задач: Contains Duplicate, Group Anagrams, Longest Consecutive Sequence.

Сортировка и поиск: встроенные инструменты

Python даёт sorted() и list.sort(). Разница: sorted() возвращает новый список, sort() сортирует на месте. Оба используют Timsort — гибрид сортировки слиянием и вставками со сложностью .

Ключевой параметр — key:

Бинарный поиск — на отсортированных данных. В Python — bisect:

На собеседовании бинарный поиск часто просят реализовать вручную — и это тот случай, где важно не сбиться с границами (left, right, mid):

Графы: обходы в ширину и глубину

Графы на собеседовании встречаются реже, но если встречаются — это обычно BFS (поиск в ширину) или DFS (поиск в глубину). Представление графа — словарь смежности:

BFS использует очередь и находит кратчайший путь в невзвешенном графе:

DFS использует стек (или рекурсию) и полезен для задач на связность, топологическую сортировку, поиск циклов:

> На собеседовании всегда уточняйте: «Граф взвешенный или нет? Ориентированный? Есть ли циклы?» — это показывает системное мышление.

Что запомнить

Каждая структура данных — это компромисс между скоростью и памятью. dict и set дают за счёт дополнительной памяти. deque даёт на обоих концах, но на доступ по индексу. Куча даёт быстрый доступ к минимуму, но не к произвольному элементу. Задача интервьюера — убедиться, что вы понимаете эти компромиссы, а не просто знаете API.

2. Функции, декораторы и генераторы на практике

Функции, декораторы и генераторы на практике

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

Функции как объекты первого класса

В Python функции — это объекты. Их можно присваивать переменным, передавать в другие функции и возвращать из них. Это фундамент, на котором строятся декораторы:

Именно поэтому sorted(data, key=len) работает: len передаётся как функция, а sorted вызывает её для каждого элемента.

Замыкания: функция внутри функции

Замыкание (closure) — это внутренняя функция, которая «запоминает» переменные из внешней области видимости, даже после того, как внешняя функция завершилась:

Замыкания — это механизм, на котором построены декораторы. Без понимания замыканий декораторы будут казаться магией.

Декораторы: функции, которые оборачивают функции

Декоратор — это функция, которая принимает другую функцию и возвращает новую функцию с дополнительным поведением. Синтаксис @decorator — просто сахар:

Запись @log_calls над функцией add эквивалентна add = log_calls(add).

Ловушка: потеря метаданных функции

После оборачивания add.__name__ станет 'wrapper', а add.__doc__None. Это ломает документацию и отладку. Решение — functools.wraps:

> На собеседовании, если пишете декоратор без @wraps, интервьюер почти наверняка спросит: «А что будет с __name__ обёрнутой функции?» — и это будет проверка вашего внимания к деталям.

Декораторы с параметрами

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

Паттерн «три уровня функций» — это стандарт. Первый уровень принимает параметры декоратора, второй — оборачиваемую функцию, третий — аргументы вызова.

Практический кейс: кеширование

Один из самых частых вопросов на собеседовании — реализация мемоизации (кеширования результатов функции):

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

Генераторы: ленивые вычисления через yield

Генератор — это функция, которая вместо return использует yield. При каждом вызове yield функция приостанавливается и сохраняет своё состояние, а при следующем обращении — продолжает с того же места:

Главное преимущество — ленивость. Генератор не хранит все значения в памяти, а вычисляет их по одному. Для обработки миллионов записей это критично:

Генераторные выражения

Краткая форма генератора — аналог генератора списков, но без создания списка в памяти:

> Если результат нужен один раз и данных много — генераторное выражение. Если нужен доступ по индексу или несколько проходов — список.

Цепочка генераторов

Генераторы можно соединять в конвейер, где каждый этап обрабатывает данные и передаёт дальше:

Этот паттерн — основа обработки данных в потоковом режиме, и он встречается в реальных проектах при работе с ETL-пайплайнами.

send() и двустороннее взаимодействие

Генератор может не только отдавать значения, но и принимать их через send():

На собеседовании это редко просят написать, но могут спросить «как генератор принимает данные извне» — и правильный ответ — через send().

itertools: готовые генераторные инструменты

Модуль itertools содержит набор генераторов для стандартных операций. Три самых полезных на собеседовании:

itertools.chain соединяет несколько итерируемых объектов в один поток, а itertools.groupby группирует последовательные элементы — оба часто используются при обработке данных.

Что запомнить

Декораторы — это не магия, а замыкания с синтаксическим сахаром. Генераторы — это не «продвинутые списки», а способ ленивых вычислений, экономящих память. На собеседовании вас проверяют не на знание синтаксиса, а на понимание механизма: что происходит с __name__ при оборачивании, почему yield приостанавливает функцию, как работает тройная вложенность в декораторе с параметрами.

3. Объектно-ориентированное программирование на практике

Объектно-ориентированное программирование на практике

Почему на собеседовании вас просят спроектировать систему парковки или шахмат, а не просто перечислить принципы ООП? Потому что ООП — это не набор определений, а способ организации кода, который должен решать реальные проблемы: расширяемость, переиспользуемость, понятность. Если вы знаете слова «наследование» и «полиморфизм», но не можете объяснить, когда composition лучше inheritance, — собеседование пройдёт тяжело.

Классы: больше чем шаблоны объектов

Класс — это не просто «чертёж объекта». Это пространство имён, которое объединяет данные и поведение. Минимальный класс:

__init__ — это не конструктор в привычном смысле. Настоящий конструктор — __new__, который создаёт объект. __init__ лишь инициализирует уже созданный объект. На практике __new__ нужен редко — в основном для паттерна Singleton или при работе с неизменяемыми типами.

Атрибуты класса vs атрибуты экземпляра

Частая ловушка на собеседовании:

Проблема: members — общий список. Если создать два экземпляра Team и добавить элемент в один, он появится и в другом. Правильный подход:

Магические методы: делаем объекты «питоничными»

Магические методы (dunder methods) — это методы с двойным подчёркиванием, которые Python вызывает неявно при определённых операциях. Их реализация — признак зрелого кода:

> Разница между __repr__ и __str__: __repr__ — для разработчика (однозначное представление), __str__ — для пользователя (читаемое). Если __str__ не определён, Python использует __repr__.

С магическими методами объекты Money ведут себя естественно:

Наследование: когда оно уместно

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

Обратите внимание: метод speak в базовом классе выбрасывает NotImplementedError. Это явный контракт — «каждый наследник обязан реализовать этот метод». Это лучше, чем оставить пустой метод или pass, потому что ошибка проявится сразу при вызове, а не молча.

super() и порядок разрешения методов

При множественном наследовании Python использует MRO (Method Resolution Order) — линеаризацию иерархии классов по алгоритму C3:

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

Composition over Inheritance

На собеседовании часто спрашивают: «Когда использовать наследование, а когда композицию?» Ответ: наследование — это отношение «является» (is-a), композиция — «имеет» (has-a).

Проблема глубокой иерархии наследования — хрупкость. Если изменить родительский класс, могут сломаться все потомки. Композиция гибче: вы можете заменить Engine на ElectricEngine, не трогая Car.

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

Абстрактные классы и интерфейсы

Python не имеет ключевого слова interface, но модуль abc даёт абстрактные базовые классы (ABC), которые нельзя инстанцировать и которые强制 требуют реализации определённых методов:

Абстрактные классы полезны, когда вы хотите задать контракт для группы связанных классов. На собеседовании это часто применяется в задачах на проектирование: «Спроектируйте систему оплаты с разными способами (карта, PayPal, криптовалюта)».

Инкапсуляция: _ и __ на практике

В Python нет настоящей приватности — всё доступно. Но соглашения работают:

  • _prefix — «protected» по договорённости: «не трогай снаружи, если не уверен»
  • __prefixname mangling: Python переименовывает атрибут в _ClassName__prefix, затрудняя доступ извне
  • @property — это способ контролировать доступ к атрибуту без изменения интерфейса. Вы можете начать с простого атрибута, а потом добавить валидацию через property, не ломая существующий код.

    Dataclasses: меньше boilerplate

    Для классов, которые в основном хранят данные, @dataclass генерирует __init__, __repr__, __eq__ автоматически:

    @dataclass(frozen=True) делает объект неизменяемым — как кортеж, но с именованными полями. Это полезно для ключей словаря и потокобезопасности.

    Что запомнить

    ООП — это не цель, а инструмент. Наследование — не «лучше» композиции, а другой инструмент. Магические методы — не декоративная фишка, а способ заставить ваши объекты интегрироваться с языком. Абстрактные классы — не академическая выдумка, а способ задать контракт. На собеседовании вас оценивают не по количеству паттернов, которые вы знаете, а по способности выбрать правильный подход под конкретную задачу.

    4. Обработка исключений и работа с файлами

    Обработка исключений и работа с файлами

    Почему продакшен-код падает в 3 часа ночи, а на тестах всё работало? Потому что разработчик обрабатывал только те ошибки, которые мог предвидеть. Реальный мир полон неожиданностей: файл не найден, сеть оборвалась, база данных вернула None вместо строки. Обработка исключений — это не «написать try-except», а стратегия того, как ваш код реагирует на непредвиденное.

    Иерархия исключений: что ловить и когда

    Все исключения в Python наследуются от BaseException. Но ловить его — почти всегда ошибка, потому что он включает SystemExit и KeyboardInterrupt, которые не должны перехватываться:

    > Золотое правило: ловите самое конкретное исключение, которое можете обработать. except Exception — это последний рубеж, а не стандартный приём.

    Try/Except/Else/Finally: полная картина

    Большинство разработчиков знают try и except. Но есть ещё else и finally, и они важны:

  • else — отделяет код, который не должен вызывать исключение, от кода, который может. Если исключение возникнет в else, оно не будет поймано except выше — это помогает избежать ловли не тех ошибок.
  • finally — гарантирует очистку ресурсов, независимо от исхода.
  • Context managers и with

    Ручное управление ресурсами (открытие/закрытие файла, соединение/разрыв с БД) чревато утечками. Context manager автоматизирует это через протокол __enter__ / __exit__:

    Создание собственного context manager

    Два способа: класс и генератор.

    Через класс:

    Через contextlib — проще:

    Генераторный вариант короче и читаемее. yield разделяет код на «до» (аналог __enter__) и «после» (аналог __exit__).

    Пользовательские исключения

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

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

    Вызывающий код может обрабатывать ошибки на разных уровнях детализации:

    > На собеседовании вопрос «когда создавать собственные исключения?» — правильный ответ: когда встроенные не несут достаточно контекста о вашем домене. ValueError говорит «что-то не так со значением», а InsufficientFundsError говорит именно то, что произошло.

    Антипаттерны обработки исключений

    Голый except

    Это скрывает реальные проблемы. Код «работает», но данные могут быть повреждены, а вы об этом не узнаете.

    Ловить и молча игнорировать

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

    Слишком большой try-блок

    Если упадёт send_notification, вы не сможете отличить эту ошибку от ошибки загрузки данных. Каждая потенциально опасная операция — в свой try.

    Работа с файлами: нюансы, которые спрашивают

    Кодировка

    По умолчанию Python использует кодировку системы. На Windows это может быть cp1252, а не utf-8 — и русский текст прочитается криво. Всегда указывайте кодировку явно:

    Режимы открытия

    | Режим | Описание | |-------|----------| | r | Чтение (по умолчанию) | | w | Запись, файл создаётся или перезаписывается | | a | Добавление в конец файла | | r+ | Чтение и запись, файл должен существовать | | rb / wb | Бинарный режим |

    > w стирает содержимое файла при открытии. Если вы хотели дополнить файл, а открыли с w — данные потеряны. Для дополнения используйте a.

    Чтение больших файлов

    Не используйте f.read() для огромных файлов — он загрузит всё в память. Читайте построчно или блоками:

    Работа с JSON и CSV

    На собеседовании часто просят прочитать данные из файла и обработать:

    Стратегия обработки ошибок в реальном проекте

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

  • Низкий уровень — конкретные except для ожидаемых ошибок (файл не найден, невалидные данные)
  • Средний уровень — обобщённая обработка с логированием и, возможно, retry
  • Высокий уровень — «последний рубеж»: логирование критической ошибки и корректное завершение
  • Что запомнить

    Обработка исключений — это не «обернуть всё в try-except». Это стратегия: какие ошибки ожидаемы, какие — нет, на каком уровне их ловить, что делать после ловли. finally гарантирует очистку, with делает её автоматической, пользовательские исключения делают код самодокументируемым. На собеседовании вас оценивают не по знанию синтаксиса try-except, а по умению проектировать устойчивые системы.

    5. Подготовка к техническому интервью и написание чистого кода

    Подготовка к техническому интервью и написание чистого кода

    Почему кандидат с идеально решённой задачей получает отказ, а кандидат с «почти решённой» — оффер? Потому что техническое собеседование — это не экзамен по алгоритмам. Это симуляция совместной работы: интервьюер оценивает, как вы думаете, как общаетесь и как пишете код, который будут поддерживать другие люди. Чистый код и умение вести диалог важнее, чем знание редких структур данных.

    PEP 8: не формальность, а язык команды

    PEP 8 — это стиль оформления кода Python. На собеседовании несоблюдение PEP 8 не автоматический отказ, но сигнал: «этот человек не работал в команде». Вот правила, которые точно нужно соблюдать:

    Именование

    Отступы и длина строк

    4 пробела (не табуляция), максимум 79 символов в строке. Если строка длинная — переносите осмысленно:

    Пробелы

    > На собеседовании пишите код в редакторе с автоформатированием (Black, autopep8). Если пишете на бумаге или в текстовом поле — держите PEP 8 в голове.

    Чистый код: принципы, которые оценивают на интервью

    Имена говорят за себя

    Переменная d ничего не говорит. days_until_expiry — говорит всё. Функция process — абстрактна. validate_email_format — конкретна.

    Функции делают одно дело

    Принцип единственной ответственности (Single Responsibility Principle) — функция должна делать одну вещь и делать её хорошо:

    Ранний возврат вместо вложенных условий

    Вложенные if — это «arrow antipattern» (пирамида дума). Ранний возврат (early return) делает код плоским:

    Магические числа — в константы

    Как проходит техническое собеседование

    Типичный сценарий — 45–60 минут, одна или две задачи. Этапы:

  • Постановка задачи — интервьюер описывает проблему
  • Уточнение — вы задаёте вопросы (и это обязательно)
  • Планирование — вы предлагаете подход, обсуждаете сложность
  • Кодирование — вы пишете код, комментируя ход мыслей
  • Тестирование — вы проверяете на примерах, включая edge cases
  • Оптимизация — если есть время, улучшаете решение
  • Уточняющие вопросы — ваше главное оружие

    Когда вам дают задачу, не прыгайте в код. Сначала уточните:

  • «Массив всегда содержит целые числа?»
  • «Могут ли быть отрицательные значения?»
  • «Что возвращать, если результат пуст?»
  • «Какой ожидаемый размер входных данных?»
  • Это показывает системное мышление и экономит время: вы не будете писать обработку случая, которого нет.

    Думайте вслух

    Интервьюер оценивает процесс, а не только результат. Говорите:

  • «Сначала приходит в голову наивное решение за — перебор всех пар»
  • «Можно улучшить до , если использовать хэш-таблицу»
  • «Подождите, здесь есть edge case — пустой массив. Нужно вернуть...»
  • Даже если вы не решите задачу полностью, интервьюер увидит, как вы рассуждаете — и это часто важнее правильного ответа.

    Типичные задачи и подходы к ним

    Работа со строками

    Частая задача — проверить, является ли строка палиндромом. Два подхода:

    На собеседовании начните с простого решения, затем предложите оптимизацию — это показывает прогрессию мышления.

    Работа с коллекциями

    Частый вопрос: «Найти пересечение двух массивов с учётом дубликатов»:

    Рекурсия и динамическое программирование

    Задача на рекурсию: «Посчитать количество способов подняться по лестнице из ступенек, шагая на 1 или 2 ступеньки»:

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

    Тестирование решения

    После написания кода не говорите «готово». Проверьте:

  • Нормальный случай — стандартный вход
  • Пустой вход — пустая строка, пустой список
  • Граничные значения — один элемент, ноль, отрицательные числа
  • Дубликаты — если вход может содержать повторы
  • > Демонстрация тестирования на собеседовании — сильный сигнал: вы пишете код, который проверяете, а не просто надеетесь, что он работает.

    Что запомнить

    Собеседование — это не проверка знаний, а проверка мышления. Чистый код — это не эстетика, а уважение к тем, кто будет его читать. PEP 8 — не догма, а общий язык команды. Уточняющие вопросы — не признак некомпетентности, а признак профессионализма. И самое главное: лучше честно сказать «я не знаю, но вот как я бы подошёл», чем молча сидеть перед монитором.