Изменяемые значения параметров по умолчанию в Python

Обновлено:

Мы уже знаем, что функции могут иметь параметры со значениями по умолчанию:

def calculate_price(price, tax=0.2):
    return price + price * tax

Но с изменяемыми объектами, например со списками, появляется важная особенность.


1. Список как значение по умолчанию

На первый взгляд такой код выглядит нормально:

def add_product(product, products=[]):
    products.append(product)

    return products

Вызовем функцию несколько раз:

print(
    add_product('Laptop')
)

print(
    add_product('Mouse')
)

print(
    add_product('Keyboard')
)

Результат:

['Laptop']
['Laptop', 'Mouse']
['Laptop', 'Mouse', 'Keyboard']

При каждом новом вызове мы могли ожидать новый пустой список.

Но этого не произошло.


Почему так происходит

2. Значение по умолчанию создаётся один раз

Когда Python выполняет определение функции:

def add_product(product, products=[]):
    ...

выражение:

[]

вычисляется один раз.

Создаётся один объект списка, который затем сохраняется как значение параметра по умолчанию.

Упрощённо:

создание функции
↓
создаётся []
↓
этот список сохраняется как значение по умолчанию
↓
первый вызов использует этот список
↓
второй вызов использует тот же список
↓
третий вызов использует тот же список

Поэтому:

products.append(product)

каждый раз изменяет один и тот же объект.


3. Это можно увидеть по id()

def add_number(number, numbers=[]):
    print(id(numbers))

    numbers.append(number)

    return numbers

Вызовем:

add_number(10)
add_number(20)
add_number(30)

При обычной работе программы во всех трёх вызовах параметр numbers будет ссылаться на тот же объект списка.

Именно поэтому его содержимое накапливается.


Почему с int такой проблемы нет

4. Неизменяемые объекты

Например:

def increase(number=0):
    number += 1

    return number

Вызовем:

print(increase())
print(increase())
print(increase())

Результат:

1
1
1

int является неизменяемым объектом.

Операция:

number += 1

не изменяет существующий объект 0, а связывает локальное имя number с новым объектом.

Поэтому проблема особенно важна для изменяемых объектов:

list
dict
set
экземпляры некоторых собственных классов

С некоторыми из этих типов познакомимся позже.


Правильный вариант

5. Используем None

Если при каждом вызове функции нужен новый список, стандартный подход:

def add_product(
    product,
    products=None,
):
    if products is None:
        products = []

    products.append(product)

    return products

Теперь:

print(
    add_product('Laptop')
)

print(
    add_product('Mouse')
)

print(
    add_product('Keyboard')
)

Результат:

['Laptop']
['Mouse']
['Keyboard']

При каждом вызове без второго аргумента выполняется:

products = []

поэтому создаётся новый список.


6. Почему именно None

None — неизменяемый singleton-объект, который удобно использовать как специальное значение:

Аргумент не был передан — нужно создать объект внутри функции.

def add_product(
    product,
    products=None,
):
    if products is None:
        products = []

Проверка выполняется через:

is None

Переданный список продолжает работать нормально

7. Пользователь функции может передать собственный список

def add_product(
    product,
    products=None,
):
    if products is None:
        products = []

    products.append(product)

    return products

Создадим список:

cart = [
    'Laptop',
]

Передадим его:

add_product(
    'Mouse',
    cart,
)

print(cart)

Результат:

['Laptop', 'Mouse']

Здесь это ожидаемое поведение.

Мы сами передали изменяемый объект функции, поэтому функция работает именно с ним.

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


Плохой и хороший вариант

8. Не рекомендуется

def create_order(
    product,
    products=[],
):
    products.append(product)

    return products

9. Рекомендуемый вариант

def create_order(
    product,
    products=None,
):
    if products is None:
        products = []

    products.append(product)

    return products

Официальная документация Python отдельно предупреждает:

значение параметра по умолчанию вычисляется только один раз.

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

Python FAQ рекомендует использовать:

None

а нужный изменяемый объект создавать уже внутри функции.


Главное по теме

Значения параметров по умолчанию создаются не при каждом вызове функции, а при выполнении её определения.

Поэтому:

def function(values=[]):
    ...

создаёт один список, который будет повторно использоваться следующими вызовами функции без этого аргумента.

Если функция изменяет этот список:

values.append(...)

изменения сохранятся и будут видны при следующих вызовах.

Поэтому вместо:

def function(values=[]):
    ...

обычно пишем:

def function(values=None):
    if values is None:
        values = []

Так новый список создаётся отдельно для каждого вызова, которому собственный список не был передан.

Официальное объяснение есть в Python Tutorial и Programming FAQ. Python Tutorial — Default Argument Values Python FAQ — Why are default values shared between objects?