Основы языка — Примеры
Пример 1 — Список, которым владеют два имени
Помощник добавляет метку к списку меток и возвращает его, а тот, кто вызывает, передаёт ему свой кэшированный список — и после этого в кэше оказывается новая метка, хотя менять его никто не просил. Объясните модель и сделайте помощник безопасным для вызова.
def add_tag(tags, tag):
tags.append(tag)
return tags
cached = ["draft"]
add_tag(cached, "public")
print(cached) # ['draft', 'public'] — список самого вызывающего
Решение
def add_tag(tags, tag):
return tags + [tag]
cached = ["draft"]
labels = add_tag(cached, "public")
print(cached) # ['draft']
print(labels) # ['draft', 'public']
Присваивание привязывает имя к объекту, а не копирует его, поэтому первая версия добавляет в список того, кто вызвал, тогда как построение нового значения оставляет его данные нетронутыми. Однако кортеж, переданный в исходный помощник, вызвал бы ошибку на добавлении, — и потому для всего, что пересекает границу, безопаснее неизменяемое значение по умолчанию. Обычная ошибка — думать, что tags внутри функции это копия cached.
Пример 2 — Значение по умолчанию, которое накапливается
Помощник логирования хранит последние события: на первом вызове он печатает одно событие, а к третьему — уже все, хотя вызывается он из несвязанных частей программы. Исправьте значение по умолчанию.
def record(event, history=[]):
history.append(event)
return history
print(record("start")) # ['start']
print(record("stop")) # ['start', 'stop']
Решение
def record(event, history=None):
if history is None:
history = []
history.append(event)
return history
print(record("start")) # ['start']
print(record("stop")) # ['stop']
Значение по умолчанию вычисляется один раз, при определении функции, поэтому изменяемое разделяется всеми вызовами, которые его опускают; если же по умолчанию стоит None, а список создаётся внутри, каждый вызов получает собственный. Дефект невидим в определении и проявляется только как состояние, растущее между вызовами, поэтому значение по умолчанию должно быть неизменяемым везде, где оно может меняться. Явная передача списка по-прежнему работает точно так же.