Как передаются аргументы в функцию в Python, по ссылке или по значению, особенно для неизменяемых объектов
Что проверяют
Понимаете ли вы модель «имена ссылаются на объекты» и не путаете ли её с передачей по ссылке из C++. Следом обычно дают код с += внутри функции или с изменяемым аргументом по умолчанию и просят предсказать вывод.
Как отвечать
Правильный ответ: ни по значению, ни по ссылке. В документации Python это называется передачей по присваиванию, в литературе встречается термин call by sharing. Параметр становится ещё одним именем для того же объекта, что и аргумент, id() у них совпадает. Копирования нет.
Отсюда два правила:
- изменение объекта видно снаружи:
lst.append(4)илиd["key"] = 1меняют тот же список или словарь, что у вызывающего; - перепривязка имени снаружи не видна:
lst = [0]внутри функции связывает локальное имя с новым объектом, у вызывающего всё по-старому.
У неизменяемых объектов (int, str, tuple, frozenset, bytes) остаётся только перепривязка. s += " world" создаёт новую строку и связывает с ней локальное s, исходная не меняется. Прямая попытка изменить объект, например t[0] = 1 для кортежа, даёт TypeError.
Сильный ответ добавит тонкость: += у списка работает иначе. lst += [4] вызывает __iadd__, который меняет список на месте, и вызывающий это увидит, а lst = lst + [4] создаёт новый список. Если нужна независимая копия, используйте lst.copy() или copy.deepcopy для вложенных структур.
Где спотыкаются
Пишут изменяемый аргумент по умолчанию: def add(item, bucket=[]). Значение вычисляется один раз при определении функции, и список копится между вызовами. Правильно bucket=None и создание списка внутри.
Считают кортеж неизменяемым целиком. Заменить его элемент нельзя, но список внутри кортежа меняется свободно, и такой кортеж даже не хешируется.
Сравнивают числа через is. Для небольших целых a is b иногда даёт True из-за кеша CPython для значений от -5 до 256, и код ломается на больших числах. Для значений нужен ==.