Какое поведение у изменяемых типов данных при передачи в функцию?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Передача изменяемых типов данных в функции
Это одна из ключевых особенностей Python, которая часто вызывает неожиданное поведение. Давайте разберемся по порядку.
Суть проблемы
В Python все передается по ссылке на объект (это не совсем pass-by-reference и не pass-by-value классических языков). Когда ты передаёшь изменяемый тип (list, dict, set) в функцию, функция получает ссылку на ТОТ ЖЕ ОБЪЕКТ, а не его копию. Это означает, что изменения внутри функции отразятся на исходном объекте.
def modify_list(lst):
lst.append(999) # Модифицируем список в функции
numbers = [1, 2, 3]
print(f"До: {numbers}") # До: [1, 2, 3]
modify_list(numbers)
print(f"После: {numbers}") # После: [1, 2, 3, 999]
Видишь? Исходный список изменился! Это потому, что lst и numbers указывают на один и тот же объект в памяти.
Сравнение с неизменяемыми типами
Со строками и числами (immutable) ситуация другая:
def modify_number(n):
n = n + 1 # Внутри функции создаём НОВЫЙ объект
print(f"Внутри функции: {n}")
x = 5
print(f"До: {x}") # До: 5
modify_number(x)
print(f"После: {x}") # После: 5 — не изменился!
Если внутри функции переприсваиваем переменную (n = n + 1), то n указывает на новый объект, а x остаётся указывать на старый.
Опасные операции с mutable типами
Прямое изменение (опасно):
# ПЛОХО — изменяется исходный объект
def dangerous(lst):
lst[0] = "changed"
lst.append("new")
return lst
my_list = [1, 2, 3]
result = dangerous(my_list)
print(my_list) # ["changed", 2, 3, "new"] — оригинал повреждён!
Переприсваивание (безопасно):
# ХОРОШО — исходный объект не меняется
def safe(lst):
lst = lst + [999] # Создаём НОВЫЙ список
return lst
my_list = [1, 2, 3]
result = safe(my_list)
print(my_list) # [1, 2, 3] — оригинал целехонький
print(result) # [1, 2, 3, 999]
Как защитить себя
1. Копирование данных (shallow copy):
def safe_modify(lst):
lst = lst.copy() # или lst[:]
lst.append(999)
return lst
numbers = [1, 2, 3]
modify_list(numbers)
print(numbers) # [1, 2, 3] — не изменился
2. Deep copy для вложенных структур:
import copy
nested = [[1, 2], [3, 4]]
def modify_nested_bad(data):
data[0].append(999) # Меняет внутренний список!
data = copy.deepcopy(nested)
modify_nested_bad(data)
print(nested) # [[1, 2], [3, 4]] — защищено
def modify_nested_good(data):
data = copy.deepcopy(data) # Полная копия
data[0].append(999)
return data
result = modify_nested_good(nested)
print(nested) # Не изменился
print(result) # [[1, 2, 999], [3, 4]]
Важные нюансы
Изменение vs Переприсваивание:
def func(lst):
lst.append(1) # ИЗМЕНЯЕТ исходный объект
lst = [99] # ПЕРЕПРИСВАИВАЕТ, исходный не меняется
lst.append(2) # ИЗМЕНЯЕТ локальный lst
original = [1]
func(original)
print(original) # [1] — только append(1) сработал!
С dict и set всё то же самое:
def modify_dict(d):
d["new_key"] = "new_value"
my_dict = {"a": 1}
modify_dict(my_dict)
print(my_dict) # {"a": 1, "new_key": "new_value"} — изменился!
Практические рекомендации
- Явность — лучше скрытности: если функция модифицирует аргумент, это должно быть очевидно из имени и документации
- Предпочитай возвращаемые значения: вместо изменения аргумента верни новый объект
- Копируй, если сомневаешься: лучше пересторахуйтесь, чем создадите баги
- Помни о shallow vs deep copy: для вложенных структур используй
copy.deepcopy()
Понимание этого поведения критично для написания надёжного кода и отладки неожиданных побочных эффектов.