В чем проблема использования изменяемых типов в качестве значений по умолчанию для аргументов функции в Python

Ответ

Проблема в том, что объект, используемый в качестве значения по умолчанию, создаётся только один раз — в момент определения функции, а не при каждом её вызове.

Если этот объект является изменяемым (например, list или dict), то все вызовы функции, которые не передают свой собственный аргумент, будут работать с одним и тем же экземпляром объекта, что приводит к неожиданным побочным эффектам.

Неправильный подход (демонстрация проблемы):

def add_item(item, items_list=[]):
    items_list.append(item)
    return items_list

# Первый вызов, всё выглядит нормально
list1 = add_item("apple")
print(list1)  # Вывод: ['apple']

# Второй вызов изменяет тот же самый список
list2 = add_item("banana")
print(list2)  # Неожиданный вывод: ['apple', 'banana']

Правильный подход (идиома с None):

Для безопасной работы следует использовать None в качестве значения по умолчанию и создавать новый изменяемый объект внутри функции.

def add_item(item, items_list=None):
    # Если список не передан, создаём новый
    if items_list is None:
        items_list = []
    items_list.append(item)
    return items_list

# Первый вызов
list1 = add_item("apple")
print(list1)  # Вывод: ['apple']

# Второй вызов работает с новым, независимым списком
list2 = add_item("banana")
print(list2)  # Ожидаемый вывод: ['banana']

Ответ 18+ 🔞

А, слушай, вот это классика, блядь! Прямо как Герасим с Муму, только в коде. Сейчас объясню, а то сам от себя охуеешь, когда поймёшь.

Смотри, ты пишешь функцию, типа, "добавь мне, сука, элемент в список". И думаешь: "А, пусть по умолчанию пустой список будет, удобно же!". И пишешь вот такую пиздопроебибну:

def add_item(item, items_list=[]):
    items_list.append(item)
    return items_list

И вроде бы всё логично, да? Ага, щас! Вот ты вызываешь её первый раз:

list1 = add_item("apple")
print(list1)  # Вывод: ['apple']

Всё, сука, окей! Работает! Ты довольный, как слон, думаешь: "Я гений, ёпта!". А потом вызываешь второй раз:

list2 = add_item("banana")
print(list2)  # Неожиданный вывод: ['apple', 'banana']

И тут тебя накрывает, блядь, медным тазом! Ты такой: "Чё за хуйня? Я же новый список хотел!". А он тебе: "Получай, мудила, старый, с яблоком!".

А вся соль в том, что этот самый [] — он создаётся один раз и навсегда, в момент, когда Питон читает твою функцию. Не при каждом вызове, нет! Он как тот самый Герасимов мешок — один на всех. И все, кто не принёс свой список, лезут в этот общий, и там всё перемешивается, блядь! Овердохуища неожиданностей!

Так что делать-то, спросишь? А делать надо по-взрослому, с подвохом! Используй None, эту хитрожопую заглушку.

Правильный подход, чтобы не облажаться:

def add_item(item, items_list=None):
    # Если список не передан, создаём новый
    if items_list is None:
        items_list = []
    items_list.append(item)
    return items_list

Вот теперь работает как часы, ебать мои старые костыли! Каждый раз, когда ты не передаёшь список, функция внутри создаёт свежий, новенький, девственный список. И никаких общих яблок с бананами!

# Первый вызов
list1 = add_item("apple")
print(list1)  # Вывод: ['apple']

# Второй вызов работает с новым, независимым списком
list2 = add_item("banana")
print(list2)  # Ожидаемый вывод: ['banana']

Вот и вся магия, Колян. Запомни: изменяемые объекты в аргументах по умолчанию — это путь в никуда, в рот меня чих-пых! Всегда используй None и создавай внутри. И будет тебе счастье, а не сюрприз в виде общего мудацкого списка на всех.