Рассмотрите следующий фрагмент на Python:
def append_unique(item, lst=[]):
if item not in lst:
lst.append(item)
return lst
Объясните, почему поведение этой функции может быть неожиданным при многократных вызовах, какие проблемы это вызывает, и предложите корректные альтернативы для разного уровня требований (простое исправление, идиоматическое решение, безопасный вариант для многопоточной среды)

2 Окт в 17:17
19 +1
0
Ответы
1

Код, который вы привели,

def append_unique(item, lst=[]):
if item not in lst:
lst.append(item)
return lst

работает не так, как многие ожидают, потому что значение по умолчанию lst=[] создаётся один раз — при определении функции — и потом переиспользуется при всех последующих вызовах, где аргумент lst не передан. В результате список «накапливает» значения между вызовами:

append_unique(1)
[1]
append_unique(2)
[1, 2]
append_unique(1)
[1, 2] # 1 уже есть
append_unique(3)
[1, 2, 3]

Ожидаемое поведение (часто) — получить новый пустой список при каждом вызове без аргумента. Непредсказуемое совместное состояние приводит к багам, трудноотлавливаемым ошибкам и уязвимостям (особенно если список используется для накопления данных).

Какие проблемы вызывает

«Паразитное» состояние между вызовами (side effects) — функция не чистая.Непредсказуемость и трудности в отладке.Если функция документирована как создающая новый список при отсутствии аргумента, это ломает API.В многопоточной среде совместный список может стать точкой гонки; нарушение инвариантов и исключения.

Правильные альтернативы (несколько вариантов для разных требований)

1) Простое исправление (общепринятое)
Использовать None как значение по умолчанию и создавать новый список внутри:

def append_unique(item, lst=None):
if lst is None:
lst = []
if item not in lst:
lst.append(item)
return lst

Это устраняет повторное использование одного и того же объекта по умолчанию. Это самый простой и идиоматичный способ избавиться от «mutable default» бага.

2) Идиоматическое/без мутации входного аргумента
Если вы хотите, чтобы функция не изменила переданный список (т.е. всегда возвращала новый список), скопируйте вход:

def append_unique(item, lst=None):
base = list(lst) if lst is not None else []
if item not in base:
base.append(item)
return base

Это полезно, когда ожидается неизменяемость внешнего состояния (функции без сайд-эффектов).

3) Эффективно для больших данных (быстрая проверка вхождения)
Проверка item in lst — O(n). Если производительность важна и у вас много элементов, используйте set для теста уникальности (и, при необходимости, отдельный список для порядка):

class OrderedUnique:
def init(self):
self.lst = []
self.seen = set()
def add(self, item):
if item not in self.seen:
self.seen.add(item)
self.lst.append(item)
def as_list(self):
return list(self.lst)

4) Потокобезопасный вариант
Если список должен разделяться между потоками, обеспечьте синхронизацию (Lock) или избегайте общей мутации (copy-on-write):

import threading

class ThreadSafeUnique:
def init(self):
self.lst = []
self.lock = threading.Lock()
def append_unique(self, item):
with self.lock:
if item not in self.lst:
self.lst.append(item)

возвращаем копию, чтобы внешнему коду не давать прямой доступ return list(self.lst)

Альтернатива без общей мутации: возвращать новый список при каждом вызове (copy-on-write) — тогда mutex не нужен, но затраты на копирование выше.

5) Если вы хотите намеренно иметь «сохранённое» состояние между вызовами
Тогда делайте это явным: храните список в глобальной переменной или в атрибуте функции/классе и документируйте поведение. Такой код должен быть очевиден для читателя и тестов.

Рекомендация

Самый частый и правильный выбор: использовать lst=None и внутри создавать список (вариант 1). Это решает проблему и понятен любому Python-разработчику.Если функция не должна мутировать аргумент — копируйте вход (вариант 2).Для многопоточности — использовать блокировку или специально разработанную потокобезопасную структуру (вариант 4).

Если нужно, могу привести конкретный пример для вашего случая (например, если важен порядок элементов или нужно многопоточное добавление с сохранением порядка).

2 Окт в 18:22
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир