азбучный вопрос про def test(b=None)
Сброс значения аргумента на значение по умолчанию при многократном вызове функции в учебных материалах предлагается в виде
def test(b=None):
if b is None:
b = []
b += [1]
print(b)
Этот код с тем же результатом можно записать иначе, и тогда непонятное становится явным:
def test(b=[]):
if b == []:
b = []
b += [1]
print(b)
или так
def test(b="foo"):
if b == "foo":
b = []
Похоже, что переменная в аргументах и в теле функции - это что-то разное. Но! Работает это так, будто и в строке, которая полностью в теле функции, if b == []: b = [] это разные b
Что я не понимаю? Наверное, вопрос уже был не раз, но я не нашел.
UPD
Я понимаю, что в следующий раз список будет не пустым. Это действительно во всех материалах указано. Но это не объясняет, почему алгоритм заходит внутрь if b == []: если список не пустой? Как работает, я понимаю. Я не понимаю, почему.
Ответы (2 шт):
В целом очень интересный пример, запустил его и тоже сначала не понял, а потом понял.
Смотрите, когда вы выполняете фунцию первый раз без аргумента, условие b == [] срабатывает по очевидной причине. После этого выполняется код b = [] и это записывает в переменную b уже другой пустой массив, который никак не связан с тем пустым масивом, который в заголовке функции.
Поэтому когда дальше вы делаете b += [1] это меняет тот другой массив. А пустой массив из заголовка функции так и остаётся пустым.
Поэтому при следующем запуске функции с аргументов по умолчанию, в b опять записывается пустой массив. И всё тоже самое повторяется по новой.
Такой пример (надеюсь, наглядный):
def show(text, x):
# Выводим текст, значение объекта, и последние 4 цифры адреса объекта в 16-ричном виде
print(text, x, hex(id(x))[-4:], sep="\t")
def test(b=[]):
show("1", b)
if b == []:
print("Зашло")
b = []
else:
print("Не зашло")
show("2", b)
b += [1]
show("3", b)
print()
print("Вызов 1")
show("default", test.__defaults__[0])
test()
print()
print("Вызов 2")
show("default", test.__defaults__[0])
test([])
print()
print("Вызов 3")
show("default", test.__defaults__[0])
test([1])
Пример вывода (части адреса будут при каждом запуске программы разными):
Вызов 1
default [] 5640
1 [] 5640
Зашло
2 [] bc40
3 [1] bc40
Вызов 2
default [] 5640
1 [] bc40
Зашло
2 [] d080
3 [1] d080
Вызов 3
default [] 5640
1 [1] bc40
Не зашло
2 [1] bc40
3 [1, 1] bc40
Тут default - это тот самый объект-список в параметре b, являющийся значением по умолчанию (один и тот же объект на все время работы программы). 1 - что лежит в b переменной до if, 2 - что после if, 3 - после добавления элемента
- В первом случае ничего не передаем. Сначала (на 1) в
bзначение по умолчанию (адрес...5640), происходит вход вif(т.к. это пустой список), значение меняется на другой объект-список, тоже пустой, но с другим адресом (...bc40). Тут все по идее должно быть понятно. - Во втором случае передаем пустой список. На 1 видим, что это не объект из значения по умолчанию (
5640 != bc40), но все равно входим вif, т.к. это пустой список, на 2 видим, что переданный список заменился на другой (bc40 != d080) - В третьем случае передаем не пустой список. На 1 очевидно что это разные объекты со списком из default, дальше не входит в
if, т.к. список не пустой, соответственно на 2 этапе видим тот же самый адрес, что и на 1 этапе.
Вот кстати во втором варианте вызова (с пустым списком) будет разница с "каноническим" вариантом (b=None):
def test1(b=None):
if b is None:
b = []
b += [1]
print(b)
def test2(b=[]):
if b == []:
b = []
b += [1]
print(b)
x = []
test1(x) # Вывод: [1]
print(x) # Вывод: [1]
x = []
test2(x) # Вывод: [1]
print(x) # Вывод: []
Т.е. с вашим вариантом переданный снаружи список не изменится, а будет создан новый пустой, никак не связанный с "внешней" переменной x.