Основы языка — Примеры

Пример 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, а список создаётся внутри, каждый вызов получает собственный. Дефект невидим в определении и проявляется только как состояние, растущее между вызовами, поэтому значение по умолчанию должно быть неизменяемым везде, где оно может меняться. Явная передача списка по-прежнему работает точно так же.