← Назад к вопросам
Что будет с манипуляцией с изменяемым объектом?
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() или работай с неизменяемыми структурами данных