Объясните поведение кода

Почему следующий код выводит ['A'] и ['A', 'A'], а не ['A'] и ['A']?

def add(a=[]):
    a.append('A')
    return a

print(add())
print(add())

Если значение ключевого параметра a сохраняется между вызовами функции, то как я могу к нему обратиться вне функции? Я нашёл, что кортеж дефолтных значений функции можно получить add.__defaults__, но как получить значение a по ключу?


Ответы (1 шт):

Автор решения: MarianD
def add(a=[]):

создает пустой список a. Этот список — сначала пустой — используется в случае, когда функция add() будет вызываться без параметров.

Но он изменяемый и кроме того он повторно инициализироваться не будет.


Объяснение:

Выражение типа ключ=значение в списке параметров определения функции (командой def) значит:

  • создай объект (соответствующего типа) со значением значение,
  • этот объект примени для таких вызовов функции, в которых значение параметра с ключом ключ не определено (т.е. отсутствует).

Точка.


Что это значит?

  1. Значение этого объекта вычисляется только один раз, именно во время создании функции (командой def).

  2. Во время вызова функции оно уже повторно не вычисляется.


Какие последствия из этого вытекают:

  • Есть разница

    • между тем, что программа действительно делает (как я выше описал),

    • и тем, что программист может ошибочно думать
      (что вычисление его стандартного значения выполняется во время каждого вызова функции, как например в Ruby).

    Пример:

    >>> t = 2
    >>> def f(x=t):           # стандартное значение будет 2, раз и навсегда
    ...     print(x)
    ...
    >>> f()
    2
    >>> t = 7                 # не получится изменить стандартное значение параметрa x
    >>> f()
    2
    

 

  • Для изменяемых объектов (например списков или словарей) следствия такой разницы могут быть еще более чудесными, как вы показали на вашем примере.

    Объект для отсутствующего параметра уже создан, первоначальное значение ему уже выло присвоено, и повторно присваиваться не будет.

    Всякое изменение этого объекта в следствии вызова функции будет сохранено.


Как возможно добиться того, чего вы ожидали?

И так:

def add(a=None):           # создается объект типа NoneType со значением None
    if a is None:          # а будет этим объектом, когда был вызов без параметра
        a = []             # создается новая, локальная переменная
    a.append('A')
    return a

Примечание:

То, что объект неизменяемый, еще не значит, что невозможно изменить значение переменной, к которой объект присвоен:

number = 5000             # Переменной number присвоен целочисленный объект

number = number + 1       # Переменной number присвоен ДРУГОЙ целочисленный объект

Вы можете проверить, что в переменной number будут 2 различных объекта применением стандартной функции id():

>>> number = 5000
>>> id(number)
38069040

>>> number = number + 1
>>> id(number)
31767920

Как вы можете видеть, функция id() возвратила разные значения. Для сравнения теперь изменим изменяемый объект (словарь), и получим то же самое id:

>>> dic = {}
>>> id(dic)
37650560

>>> dic[7] = 100
>>> id(dic)
37650560
→ Ссылка