← Назад к вопросам

Какое поведение у изменяемых типов данных при передачи в функцию?

1.3 Junior🔥 191 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Передача изменяемых типов данных в функции

Это одна из ключевых особенностей 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"} — изменился!

Практические рекомендации

  1. Явность — лучше скрытности: если функция модифицирует аргумент, это должно быть очевидно из имени и документации
  2. Предпочитай возвращаемые значения: вместо изменения аргумента верни новый объект
  3. Копируй, если сомневаешься: лучше пересторахуйтесь, чем создадите баги
  4. Помни о shallow vs deep copy: для вложенных структур используй copy.deepcopy()

Понимание этого поведения критично для написания надёжного кода и отладки неожиданных побочных эффектов.

Какое поведение у изменяемых типов данных при передачи в функцию? | PrepBro