Изменяемые и неизменяемые типы данных в Python традиционно являются причиной головной боли у начинающих разработчиков. Как следует из названия изменяемые объекты можно модифицировать, неизменяемые - постоянны. Заставим голову кружиться? Смотрим пример:
foo = ['hi']print(foo)# Вывод: ['hi']bar = foobar += ['bye']print(foo)# Вывод: ['hi', 'bye']
Что произошло? Мы этого не ожидали! Логично было бы увидеть:
foo = ['hi']print(foo)# Вывод: ['hi']bar = foobar += ['bye']print(foo)# Ожидаемый вывод: ['hi']# Вывод: ['hi', 'bye']print(bar)# Вывод: ['hi', 'bye']
Это не баг, а изменяемые типы данных в действии. Каждый раз, когда вы присваиваете значение переменной изменяемого типа другой переменной, все изменения с этим значением будут отражаться на обоих переменных. Новая переменная становится ссылкой на старую. Так происходит только с изменяемыми типами данных. Вот пример с использованием функций и изменяемых типов данных:
def add_to(num, target=[]):target.append(num)return targetadd_to(1)# Вывод: [1]add_to(2)# Вывод: [1, 2]add_to(3)# Вывод: [1, 2, 3]
Вы могли ожидать другого поведения. Например, что функция add_to
будет возвращать новый список при каждом вызове:
def add_to(num, target=[]):target.append(num)return targetadd_to(1)# Вывод: [1]add_to(2)# Вывод: [2]add_to(3)# Вывод: [3]
Причиной, опять же, является изменяемость списков, которая и вызывает основную боль. В Python аргументы функции обрабатываются при определении функции, а не при её вызове. По этой причине вы никогда не должны присваивать аргументам по умолчанию значения изменяемых типов, если абсолютно точно не уверены в своих действиях конечно. Правильным подходом будет такой код:
def add_to(element, target=None):if target is None:target = []target.append(element)return target
Каждый раз при вызове функции без аргумента target
будет создан новый список. К примеру:
add_to(42)# Вывод: [42]add_to(42)# Вывод: [42]add_to(42)# Вывод: [42]