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

Что будет с манипуляцией с изменяемым объектом?

2.3 Middle🔥 131 комментариев
#REST API и HTTP

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

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

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

Манипуляция с изменяемыми объектами

Что происходит при манипуляции с изменяемым объектом (list, dict, set)? Объект изменяется на месте (in-place), и все переменные, указывающие на этот объект, видят изменения.

Ключевое отличие от неизменяемых

Неизменяемые (int, str, tuple)

  • Создается НОВЫЙ объект
  • Переменная указывает на новый объект
  • Оригинальный объект остается нетронутым

Изменяемые (list, dict, set)

  • Объект модифицируется НА МЕСТЕ
  • ID объекта не меняется
  • ВСЕ переменные видят изменение

Пример с изменением списка

# Создаём список
my_list = [1, 2, 3]
original_id = id(my_list)
print(f"ID: {original_id}")
print(f"List: {my_list}")

# Изменяем элемент
my_list[0] = 99
print(f"ID после изменения: {id(my_list)}")
print(f"List: {my_list}")

# ID ТОТ ЖЕ!
print(f"Same object? {original_id == id(my_list)}")  # True

Проблема: несколько переменных указывают на один объект

# Создаём список
original = [1, 2, 3]
print(f"original ID: {id(original)}")

# Создаём КОПИЮ ссылки (не копию объекта!)
copy_ref = original  # copy_ref указывает на ТОТ ЖЕ список
print(f"copy_ref ID: {id(copy_ref)}")

print(f"Same object? {id(original) == id(copy_ref)}")  # True

# Изменяем через copy_ref
copy_ref[0] = 999

# OOPS! Изменился и original
print(f"original: {original}")  # [999, 2, 3]
print(f"copy_ref: {copy_ref}")  # [999, 2, 3]

Это частая ошибка! copy_ref = original создает не копию, а ссылку на тот же объект.

Как это влияет на функции

Функция может изменить входной аргумент!

def modify_list(lst):
    lst[0] = "MODIFIED"
    # Изменяем оригинальный объект

my_data = ["a", "b", "c"]
print(f"До: {my_data}")
modify_list(my_data)
print(f"После: {my_data}")
# После: ['MODIFIED', 'b', 'c']
# Оригинальный список изменился!

Это может привести к трудноуловимым багам:

def process_order(items):
    items.pop()  # Удаляем последний элемент
    return sum([x["price"] for x in items])

order_items = [{"name": "item1", "price": 100}, {"name": "item2", "price": 50}]
total = process_order(order_items)
print(f"Total: {total}")  # 100 (верно)
print(f"Items: {order_items}")  # [{'name': 'item1', 'price': 100}]
# Ой! Оригинальный список изменился!

Решение 1: Явная копия

Shallow copy (поверхностная копия)

original = [1, 2, 3]
copy = original.copy()  # Или: original[:]

print(f"Same object? {original is copy}")  # False
print(f"Same content? {original == copy}")  # True

copy[0] = 999
print(f"original: {original}")  # [1, 2, 3] (не изменился)
print(f"copy: {copy}")          # [999, 2, 3]

Deep copy (глубокая копия)

import copy

original = [[1, 2], [3, 4]]
shallow = original.copy()  # Shallow copy
deep = copy.deepcopy(original)  # Deep copy

# Изменяем вложенный список
shallow[0][0] = 999
deep[0][0] = 999

print(f"original: {original}")  # [[999, 2], [3, 4]] (изменился!)
print(f"shallow: {shallow}")    # [[999, 2], [3, 4]]
print(f"deep: {deep}")          # [[999, 2], [3, 4]]
# У shallow и original общий вложенный список [1, 2]

Решение 2: Функция не должна менять входные данные

# ПЛОХО: функция изменяет аргумент
def sort_in_place(lst):
    lst.sort()  # Изменяет оригинальный список

data = [3, 1, 2]
sort_in_place(data)
print(data)  # [1, 2, 3] (изменился)

# ХОРОШО: функция возвращает новый результат
def sorted_copy(lst):
    return sorted(lst)  # Возвращает новый список

data = [3, 1, 2]
sorted_data = sorted_copy(data)
print(data)          # [3, 1, 2] (не изменился)
print(sorted_data)   # [1, 2, 3]

Манипуляция со словарями

original_dict = {"a": 1, "b": 2}
dict_ref = original_dict

# Изменяем через dict_ref
dict_ref["a"] = 999
dict_ref["c"] = 3

# original_dict тоже изменился!
print(original_dict)  # {'a': 999, 'b': 2, 'c': 3}

# Как скопировать:
dict_copy = original_dict.copy()
dict_copy["a"] = 111
print(original_dict)  # {'a': 999, 'b': 2, 'c': 3} (не изменился)
print(dict_copy)      # {'a': 111, 'b': 2, 'c': 3}

Манипуляция с множествами

original_set = {1, 2, 3}
set_ref = original_set

# Изменяем
set_ref.add(4)
set_ref.remove(1)

# original_set изменился
print(original_set)  # {2, 3, 4}

# Как скопировать:
set_copy = original_set.copy()
set_copy.add(5)
print(original_set)  # {2, 3, 4} (не изменился)
print(set_copy)      # {2, 3, 4, 5}

Практический пример: функция для работы со списком

# НЕПРАВИЛЬНО: функция меняет оригинальный список
def remove_outliers_bad(numbers):
    numbers.sort()
    numbers = numbers[1:-1]
    return numbers

data = [1, 100, 2, 3, 200]
result = remove_outliers_bad(data)
print(data)    # [1, 100, 2, 3, 200] (отсортирован, но не "удалены")
print(result)  # [100, 2, 3] (новый список)

# ПРАВИЛЬНО: функция работает с копией
def remove_outliers_good(numbers):
    sorted_nums = sorted(numbers)  # Новый список
    return sorted_nums[1:-1]       # Новый список

data = [1, 100, 2, 3, 200]
result = remove_outliers_good(data)
print(data)    # [1, 100, 2, 3, 200] (не изменился)
print(result)  # [2, 3, 100]

Граница чистой функции

# Идея: "чистые" функции не должны иметь побочных эффектов
from typing import List

def process_items(items: List[dict]) -> int:
    """Возвращает сумму, НЕ меняет items"""
    # Если нужно отфильтровать:
    valid_items = [item for item in items if item["price"] > 0]
    return sum(item["price"] for item in valid_items)

order = [{"name": "a", "price": 100}, {"name": "b", "price": -50}]
result = process_items(order)
print(order)   # Не изменился
print(result)  # 100

Заключение

При манипуляции с изменяемыми объектами (list, dict, set):

  • Объект изменяется НА МЕСТЕ
  • ID объекта остаётся тем же
  • ВСЕ переменные, указывающие на этот объект, видят изменения
  • Это может привести к трудноуловимым багам в функциях
  • Решение: используй .copy() или работай с неизменяемыми структурами данных