Что выведет код: функция мутирует список и кортеж
Классика телефонного скрининга: тебе дают крохотную функцию и просят предсказать вывод, не запуская код. Проверяют не знание синтаксиса, а понимание, как Python передаёт аргументы и что вообще можно, а что нельзя менять на месте.
Задача
Есть функция, которая добавляет элемент в переданную коллекцию:
def add_element(collection):
collection.append(0)
Её вызывают сначала для списка, потом для кортежа:
some_list = [1, 2, 3]
add_element(some_list)
print(some_list)
some_tuple = (1, 2, 3)
add_element(some_tuple)
print(some_tuple)
Что выведет каждый print? Собеседующий ждёт не только ответа, но и объяснения — почему так.
Решение
def add_element(collection):
collection.append(0)
some_list = [1, 2, 3]
add_element(some_list)
print(f"list [1, 2, 3] после add_element -> {some_list}")
some_tuple = (1, 2, 3)
try:
add_element(some_tuple)
print(f"tuple (1, 2, 3) после add_element -> {some_tuple}")
except AttributeError as e:
print(f"tuple (1, 2, 3) -> {type(e).__name__}: {e}")
Кортеж роняет программу исключением, поэтому в решении я ловлю его через try/except и печатаю тип — иначе скрипт оборвался бы на середине, а нам нужно увидеть оба случая.
Разбор
Здесь две разные идеи, которые новички путают: как передаётся аргумент и можно ли объект менять.
Про передачу. Python не копирует объект при вызове функции — внутрь уходит ссылка на тот же самый объект в памяти. Поэтому когда add_element делает collection.append(0), он меняет ровно тот список, что снаружи. Никакого «локального» списка нет — some_list и collection указывают на одну и ту же коробку. Отсюда и результат: снаружи список стал [1, 2, 3, 0]. Это и есть та самая «передача по ссылке», на которой горят: думаешь, что функция работает с копией, а она правит твои данные.
Про мутабельность. Список — мутабельный тип, у него есть метод append, который меняет объект на месте. Кортеж — иммутабельный: после создания его содержимое зафиксировано, и метода append у него попросту нет. Поэтому вызов some_tuple.append(0) падает не с логической ошибкой, а с AttributeError: 'tuple' object has no attribute 'append' — Python не находит такого метода у типа. Важная тонкость: дело не в том, что «кортеж защищён от изменения» магией, а в том, что у него нет методов-мутаторов.
Практический смысл. Если функция принимает список (или словарь, множество) и что-то в него дописывает — она молча меняет данные вызывающего кода. Это источник неприятных багов: передал конфиг в функцию, а он вернулся изменённым. Когда нужна гарантия неизменности — используют кортеж или передают копию (some_list.copy() или list(some_list)). Тема тесно связана с тем, чем генератор отличается от списка: и там, и там речь про то, как Python на самом деле хранит и отдаёт объекты, а не про то, как код выглядит.
Ожидаемый результат
list [1, 2, 3] после add_element -> [1, 2, 3, 0]
tuple (1, 2, 3) -> AttributeError: 'tuple' object has no attribute 'append'
Хочешь так же уверенно решать на собеседовании?
Читать разбор полезно, а настоящая прокачка — самому решать такие задачи и получать обратную связь. Потренируйся в симуляторе аналитика, лёгкий режим — бесплатно.
Прорешать в бесплатной песочнице