Проблема - В определении функции значение по умолчанию вычисляется один раз при загрузке модуля, поэтому мутабельный объект используется повторно для всех вызовов. В вашем фрагменте это lst=[]\text{lst}=[]lst=[]. - Следствие: данные накапливаются между вызовами, то есть вызовы не изолированы друг от друга. Краткий пример поведения - Первый вызов: add(1)→[1]\text{add(1)}\to [1]add(1)→[1] - Второй вызов: add(2)→[1,2]\text{add(2)}\to [1,2]add(2)→[1,2] — вместо ожидаемого [2][2][2]. Последствия на долгоживущих серверах - Накопление памяти (памятный «утечка»), если список постоянно растёт. - Пересечение данных между разными запросами/сессиями — риск утечки данных и некорректного поведения. - В многопоточной среде — состояние разделяется между потоками, возможны гонки и неконсистентность без явной синхронизации. - Потенциальные проблемы безопасности и сложность отладки из-за неявного состояния. Безопасные альтернативы 1) Использовать sentinel `None` и создавать новый список внутри: def add(item, lst=None): if lst is None: lst = [] lst.append(item) return lst 2) Возвращать новый список (функция без сайд-эффектов): def add(item, lst=None): base = lst if lst is not None else [] return base + [item] 3) Если функция должна принимать уже существующий список, копировать вход (чтобы не менять внешнюю структуру): def add(item, lst=None): if lst is None: lst = [] else: lst = list(lst) # shallow copy lst.append(item) return lst 4) Если нужен общий (разделяемый) стейт — делать это явно: хранить в объекте/атрибуте, в базе данных или в кэше и защищать доступ (locks, очереди, процессы, атомарные операции), не в дефолтном аргументе. Рекомендация - Для большинства случаев — вариант (1) с lst=None\text{lst}=\text{None}lst=None и явным созданием списка внутри функции — самый простой и безопасный.
- В определении функции значение по умолчанию вычисляется один раз при загрузке модуля, поэтому мутабельный объект используется повторно для всех вызовов. В вашем фрагменте это lst=[]\text{lst}=[]lst=[].
- Следствие: данные накапливаются между вызовами, то есть вызовы не изолированы друг от друга.
Краткий пример поведения
- Первый вызов: add(1)→[1]\text{add(1)}\to [1]add(1)→[1]
- Второй вызов: add(2)→[1,2]\text{add(2)}\to [1,2]add(2)→[1,2] — вместо ожидаемого [2][2][2].
Последствия на долгоживущих серверах
- Накопление памяти (памятный «утечка»), если список постоянно растёт.
- Пересечение данных между разными запросами/сессиями — риск утечки данных и некорректного поведения.
- В многопоточной среде — состояние разделяется между потоками, возможны гонки и неконсистентность без явной синхронизации.
- Потенциальные проблемы безопасности и сложность отладки из-за неявного состояния.
Безопасные альтернативы
1) Использовать sentinel `None` и создавать новый список внутри:
def add(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
2) Возвращать новый список (функция без сайд-эффектов):
def add(item, lst=None):
base = lst if lst is not None else []
return base + [item]
3) Если функция должна принимать уже существующий список, копировать вход (чтобы не менять внешнюю структуру):
def add(item, lst=None):
if lst is None:
lst = []
else:
lst = list(lst) # shallow copy
lst.append(item)
return lst
4) Если нужен общий (разделяемый) стейт — делать это явно: хранить в объекте/атрибуте, в базе данных или в кэше и защищать доступ (locks, очереди, процессы, атомарные операции), не в дефолтном аргументе.
Рекомендация
- Для большинства случаев — вариант (1) с lst=None\text{lst}=\text{None}lst=None и явным созданием списка внутри функции — самый простой и безопасный.