1Какие встроенные типы данных есть в Python и какие из них изменяемые?
В Python есть скалярные типы NoneType, bool, int, float и complex; текстовые и бинарные типы str, bytes и bytearray; последовательности list, tuple и range; коллекции dict, set и frozenset. Ключевое различие для собеседования — mutability: list, dict, set и bytearray меняются на месте, а числа, bool, str, bytes, tuple, range, frozenset и None неизменяемы. Важный нюанс: сам tuple всегда хранит фиксированный набор ссылок, но внутреннее состояние изменяемого объекта по такой ссылке может меняться.
2Чем list, tuple, set и dict отличаются в реальном коде?
list — упорядоченная изменяемая последовательность, удобная для накопления результатов и данных, которые меняются. tuple — упорядоченная неизменяемая структура для фиксированных записей и возврата нескольких значений. set хранит уникальные hashable-объекты и подходит для membership checks и deduplication. dict отображает hashable-ключи в значения и обычно используется для индексов, lookup и JSON-подобных данных. В современных версиях dict сохраняет порядок вставки, но его главная роль — доступ по ключу.
items = ["a", "b", "a"]
unique = set(items)
index = {"user_id": 42}
point = (10, 20)
== проверяет равенство по значению, обычно через __eq__. is проверяет идентичность: две ссылки указывают на один и тот же объект. В production-коде is обычно используют для singleton-проверок, например value is None, или для специальных sentinel-объектов. Нельзя полагаться на кеширование маленьких чисел или interning строк: в маленьких примерах is может случайно сработать, но семантически это неправильная проверка.
Hashable-объект имеет стабильный hash на протяжении жизни и равенство, согласованное с этим hash. Это важно, потому что ключи dict и элементы set зависят от hash. Большинство неизменяемых built-ins hashable, но неизменяемый контейнер не делает вложенные объекты hashable автоматически: tuple со списком внутри не hashable. Если объект используется как ключ, поля, участвующие в __eq__ или __hash__, не должны меняться после вставки, иначе объект может потеряться в dict или set.
Срез sequence[start:stop:step] включает start, исключает stop и движется с шагом step. Пропущенные границы выбираются в зависимости от направления шага. Отрицательные индексы считаются с конца, поэтому s[-1] — последний элемент. Отрицательный step идет справа налево, а s[::-1] возвращает перевернутую последовательность. Нужно помнить, что срез часто создает новый объект, поэтому он выразительный, но не бесплатный на больших данных.
text = "interview"
print(text[1:5])
print(text[::-1])
Python передает ссылки на объекты через присваивание; это часто называют call by sharing. Параметры функции становятся локальными именами, связанными с теми же объектами, которые передал вызывающий код. Если объект изменяемый, функция может изменить его, и вызывающий код это увидит. Если функция просто присвоит параметру другое значение, изменится только локальная привязка. Хороший ответ различает mutation in place и rebinding: items.append(...) или items.clear() меняет список у вызывающего кода, а items = [] внутри функции только переназначает локальное имя.
Значения по умолчанию вычисляются один раз при определении функции, а не при каждом вызове. Поэтому list или dict в default может разделяться между всеми вызовами и случайно хранить состояние. Безопасный паттерн: def f(items=None), затем внутри создать новый список, если items is None. Иногда общее состояние нужно специально, например для маленького cache, но тогда это должно быть явно и понятно читателю.
def add_tag(tag, tags=[]):
tags.append(tag)
return tags
print(add_tag("python"))
print(add_tag("backend")) # Same list reused
def safe_add_tag(tag, tags=None):
tags = [] if tags is None else tags
tags.append(tag)
return tags
*args собирает лишние позиционные аргументы в tuple, а **kwargs собирает лишние именованные аргументы в dict. Они полезны для wrappers, decorators, гибких API и проброса вызовов дальше. При вызове *iterable распаковывает позиционные значения, а **mapping — keyword values. Компромисс в читаемости: явные параметры лучше для стабильного публичного API, а args/kwargs лучше на границах совместимости и forwarding.
Python ищет имя в Local, Enclosing, Global и Builtins. Чтение имени идет по этому порядку. Присваивание внутри функции обычно создает локальную переменную, поэтому можно получить UnboundLocalError, если прочитать имя до локального присваивания. global направляет присваивание в модульную область, nonlocal — во внешнюю функцию. В production-коде их лучше использовать редко, потому что они усложняют поток состояния.
total = 0
def add(value):
global total
total += value
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
Closure — функция, которая сохраняет доступ к переменным из внешней области после завершения внешней функции. Это полезно для decorators, callback factories и функций с конфигурацией. Late binding означает, что свободная переменная ищется при вызове внутренней функции, а не при ее создании. Поэтому lambda в цикле может увидеть последнее значение счетчика. Значение фиксируют через default argument, например lambda i=i: i, или через отдельную factory-функцию.
callbacks = []
for i in range(3):
callbacks.append(lambda: i)
print([callback() for callback in callbacks]) # [2, 2, 2]
callbacks = []
for i in range(3):
callbacks.append(lambda i=i: i)
print([callback() for callback in callbacks]) # [0, 1, 2]
11Чем переменная класса отличается от переменной экземпляра?
Переменная класса хранится на объекте класса и разделяется экземплярами, пока экземпляр не перекроет ее собственным атрибутом. Переменная экземпляра хранится на конкретном объекте и обычно задается как self.name в __init__. Неизменяемые class constants обычно безопасны, но изменяемые class attributes часто становятся багом, если их случайно используют как состояние экземпляра. В ответе полезно упомянуть порядок поиска: сначала instance, потом class и ancestors.
class Cart:
items = []
first = Cart()
second = Cart()
first.items.append("book")
print(second.items) # ["book"]
class SafeCart:
def __init__(self):
self.items = []
Python ищет атрибуты по Method Resolution Order, который можно посмотреть через ClassName.__mro__. В одиночном наследовании все просто, но при множественном наследовании Python использует C3 linearization, чтобы получить согласованный порядок. super() означает не просто 'вызвать родителя', а 'вызвать следующую реализацию в MRO'. Это позволяет cooperative multiple inheritance, если методы принимают совместимые аргументы и каждый класс корректно вызывает super().
class A:
def save(self):
print("A")
class B(A):
def save(self):
print("B")
super().save()
class C(A):
def save(self):
print("C")
super().save()
class D(B, C):
pass
D().save() # B, C, A
Iterable — объект, который возвращает iterator через iter(), например list или file object. Iterator хранит состояние обхода, реализует __next__ и вызывает StopIteration при завершении. Generator — удобный способ создать iterator через yield или generator expression. Генераторы экономят память, потому что лениво производят значения, но обычно одноразовые и не поддерживают индексацию как list.
def read_lines(path):
with open(path, encoding="utf-8") as file:
for line in file:
yield line.strip()
Context manager задает поведение входа и выхода через __enter__ и __exit__, либо создается через contextlib.contextmanager. with делает управление ресурсами явным и надежным: файлы закрываются, lock освобождается, транзакция commit/rollback выполняется даже при исключениях. __exit__ получает информацию об исключении и может подавить его, вернув true, но подавлять ошибки стоит осторожно.
from contextlib import contextmanager
with open("data.txt", encoding="utf-8") as file:
data = file.read()
@contextmanager
def temporary_setting():
print("enter")
try:
yield
finally:
print("exit")
Shallow copy создает новый внешний контейнер, но оставляет ссылки на те же вложенные объекты. Deep copy рекурсивно копирует вложенные объекты, где это возможно. Для простых плоских списков shallow copy часто достаточно. Для вложенных изменяемых структур shallow copy может разделять состояние и удивлять. Deep copy дороже и может упасть с TypeError или PicklingError для ресурсов: открытых файлов, sockets, database sessions или объектов с важной identity-семантикой.
from copy import copy, deepcopy
original = [[1], [2]]
shallow = copy(original)
deep = deepcopy(original)
original[0].append(99)
print(shallow) # [[1, 99], [2]]
print(deep) # [[1], [2]]
В стандартном CPython Global Interpreter Lock позволяет только одному потоку выполнять Python bytecode в конкретный момент. Поэтому threads обычно не ускоряют CPU-bound Python-код. Но threads полезны для I/O-bound задач, потому что GIL освобождается во время многих операций ожидания. Для CPU-heavy работы используют multiprocessing, native extensions, vectorized native libraries или отдельные сервисы. Для интервью в 2026 году важно упомянуть Python 3.13+ free-threaded CPython builds: GIL становится опциональным для совместимого кода, но зависимости и performance нужно проверять.
Threads подходят для blocking I/O и библиотек только с синхронным API. Processes выбирают для CPU-bound работы, изоляции или устойчивости к падениям, принимая overhead сериализации и памяти. asyncio подходит для большого числа concurrent I/O operations с async-compatible библиотеками: async database drivers, HTTP clients, queues. Практический выбор зависит от framework и dependencies: async endpoint с blocking calls может стать хуже, а не лучше.
18Для чего async/await полезны в Python web services?
async и await дают cooperative concurrency. Coroutine возвращает управление event loop, когда ожидает I/O, и в это время могут выполняться другие задачи. Это полезно для API, которые ждут database, HTTP calls, queues или sockets. Это не ускоряет CPU-bound код: тяжелые вычисления блокируют event loop, если не вынести их в worker, process или native code. В FastAPI обычный def endpoint выполняется в thread pool, а blocking calls внутри async def могут заблокировать event loop; async помогает только когда call path использует non-blocking async I/O.
19Как правильно обрабатывать exceptions в production Python?
Перехватывайте самый конкретный exception, который можете обработать, и делайте это на слое, который может восстановиться, добавить контекст или перевести ошибку в domain error. Избегайте bare except и молчаливого swallowing: это скрывает потерю данных и operational problems. Используйте raise для повторного возбуждения и raise NewError(...) from exc для сохранения причины. На границах системы нужны полезные logs, корректные HTTP errors и сообщения без утечки secrets.
try:
result = parse_payload(payload)
except ValueError as exc:
raise InvalidRequest("Invalid payload") from exc
20Как структурировать и тестировать production Python service?
Отделяйте domain logic от framework и infrastructure code, чтобы основное поведение тестировалось без сервера и базы. Используйте configuration из environment или typed settings, logging вместо print, dependency injection на границах и явные timeouts для network calls. В тестах сочетайте unit tests для чистой логики, integration tests для database/external boundaries и несколько end-to-end checks для критических flows. Mock используйте на понятных границах, а проверяйте observable behavior, не private implementation details.