Будут ли проблемы, если по умолчанию указать строку вместо списка в Python?
Что проверяют
Вопрос почти всегда идёт после классической ловушки def f(items=[]). Интервьюер проверяет, понимаете ли вы, что опасен не список как тип, а изменяемый объект, созданный один раз. Следом просят исправить функцию или сказать, когда вычисляется значение по умолчанию.
Как отвечать
Начните с механики: значения по умолчанию вычисляются один раз, при выполнении def, и хранятся в f.__defaults__. Все вызовы без аргумента получают один и тот же объект.
- список по умолчанию: в
def add(x, items=[])сitems.append(x)второй вызовadd(2)вернёт[1, 2], список общий и копит состояние; - строка по умолчанию: строка неизменяема.
items += "a"создаёт новый объект и привязывает его к локальному имени, а объект в__defaults__не меняется. Протечки между вызовами нет, так же безопасныNone, числа,tuple,frozenset; - но тип другой: если тело рассчитано на список, строка ломает логику.
"abc".appendдастAttributeError, аfor x in "abc"пройдёт по символам, и функция молча обработает"a","b","c"вместо одного элемента. Это хуже исключения, ошибку не видно; - идиоматичное решение:
items: list[str] | None = None, внутриif items is None: items = []. Если контейнер нужен только для чтения, подойдёт кортеж().
Упомяните инструменты: ruff ловит изменяемые значения по умолчанию правилом B006 из набора flake8-bugbear, а аннотация типа плюс mypy покажут, что строку передают туда, где ждут список.
Где спотыкаются
Говорят, что строка по умолчанию опасна так же, как список. Общий объект действительно один, но изменить его нельзя, поэтому состояние не протекает.
Исправляют через items = items or []. Пустой список, который вызывающий код передал намеренно, подменяется новым, и изменения до вызывающего не доходят.
Не видят проблему итерации по строке. Функция, которая ждёт list[str], примет "tag" и разложит его на три тега без единой ошибки в логе.