Задачи по Python с собеседований
Python·junior

Что выведет код: функция мутирует список и кортеж

Классика телефонного скрининга: тебе дают крохотную функцию и просят предсказать вывод, не запуская код. Проверяют не знание синтаксиса, а понимание, как 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'

Хочешь так же уверенно решать на собеседовании?

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

Прорешать в бесплатной песочнице

Смежные задачи

Вопросы по теме

Из блога