Это немного сложный случай. Это имеет смысл, когда вы хорошо понимаете, как Python обрабатывает имена и объекты . Вы должны стремиться развивать это понимание как можно скорее, если вы изучаете Python, потому что оно имеет решающее значение для абсолютно всего, что вы делаете в Python.
Имена в Python - это такие вещи, как a
, f1
, b
. Они существуют только в определенных областях (т. Е. Вы не можете использовать b
вне функции, которая его использует). Во время выполнения имя ссылается на значение , но в любой момент может быть восстановлено до нового значения с помощью операторов присваивания, таких как:
a = 5
b = a
a = 7
Значения создаются в некоторой точке вашей программы, и на них можно ссылаться по именам, но также по слотам в списках или других структурах данных. В приведенном выше примере имя a
привязано к значению 5, а затем - к значению 7. Это не влияет на значение 5, которое всегда равно значению 5, независимо от количества имен. в настоящее время связаны с этим.
С другой стороны, присвоение b
делает привязку имени b
к значению , на которое ссылается a
в тот момент времени. Повторная привязка имени a
впоследствии не влияет на значение 5 и, следовательно, не влияет на имя b
, которое также связано со значением 5.
Назначение всегда работает в Python таким образом. Это никогда никак не влияет на значения. (За исключением того, что некоторые объекты содержат «имена»; повторное связывание этих имен, очевидно, влияет на объект, содержащий имя, но не влияет на значения, имя которых упоминалось до или после изменения)
Всякий раз, когда вы видите имя в левой части оператора присваивания, вы (пере) связываете имя. Всякий раз, когда вы видите имя в любом другом контексте, вы извлекаете (текущее) значение, на которое ссылается это имя.
Теперь мы можем видеть, что происходит в вашем примере.
Когда Python выполняет функцию Definition , он оценивает выражения, используемые для аргументов по умолчанию, и запоминает их где-то незаметно в стороне. После этого:
def f1(a, l=[]):
l.append(a)
return(l)
l
- это не что-нибудь, потому что l
- это только имя в рамках функции f1
, и мы не внутри этой функции. Однако значение []
хранится где-то в другом месте.
Когда выполнение Python переходит в вызов в f1
, он связывает все имена аргументов (a
и l
) с соответствующими значениями - либо значения, переданные вызывающей стороной, либо значения по умолчанию, созданные при определении функции. Поэтому, когда существа Python выполняют вызов f3(5)
, имя a
будет связано со значением 5, а имя l
будет связано с нашим списком по умолчанию.
Когда Python выполняет l.append(a)
, назначения не видно, поэтому мы ссылаемся на текущие значения l
и a
. Таким образом, если это как-то повлияет на l
, оно может сделать это только путем изменения значения, к которому относится l
, и это действительно так. Метод append
списка изменяет список, добавляя элемент в конец. Таким образом, после этого нашего значения списка, , которое по-прежнему является тем же значением, сохраненным в качестве аргумента по умолчанию f1
, теперь к нему добавлено 5 (текущее значение a
), и оно выглядит как [5]
.
Затем мы возвращаем l
. Но мы изменили список по умолчанию, так что это повлияет на любые будущие вызовы. Но мы также вернули список по умолчанию, поэтому любые другие изменения возвращенного нами значения будут влиять на любые будущие вызовы!
Теперь рассмотрим f2
:
def f2(a, b=1):
b = b + 1
return(a+b)
Здесь, как и прежде, значение 1 спрятано где-то, чтобы служить значением по умолчанию для b
, и когда мы начнем выполнять f2(5)
вызов, имя a
станет связанным с аргументом 5, и имя b
станет привязанным к значению по умолчанию 1
.
Но затем мы выполняем оператор присваивания. b
появляется в левой части оператора присваивания, поэтому мы связываем имя b
. Сначала Python вырабатывает b + 1
, то есть 6, затем связывает b
с этим значением. Теперь b
привязано к значению 6. Но значение по умолчанию для функции не было затронуто : 1 по-прежнему равно 1!
Надеюсь, это прояснилось. Вы действительно должны уметь мыслить в терминах имен, которые относятся к значениям и могут быть отсканированы, чтобы указывать на разные значения, чтобы понять Python.
Вероятно, также стоит указать на хитрый случай. Правило, которое я дал выше (о присваивании, всегда связывающем имена без влияния на значение, поэтому, если что-то еще влияет на имя, оно должно сделать это путем изменения значения), верно для стандартного присваивания, но не всегда для «расширенных» операторов присваивания как +=
, -=
и *=
.
Что они делают, к сожалению, зависит от того, на чем вы их используете. В:
x += y
это обычно ведет себя как:
x = x + y
т.е. он вычисляет новое значение с помощью и привязывает x
к этому значению, не влияя на старое значение. Но если x
является списком, то он фактически изменяет значение, на которое ссылается x
! Так что будьте осторожны с этим делом.