← Назад к вопросам
Зачем нужно глубокое копирование (deep copy)?
1.0 Junior🔥 181 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем нужно глубокое копирование (deep copy)?
Глубокое копирование (deep copy) в Python необходимо для создания полностью независимой копии объекта, включая все вложенные объекты. Это критично для избежания неожиданных побочных эффектов при работе со сложными структурами данных.
Проблема: Неглубокое копирование
import copy
# ❌ Проблема 1: Присваивание только создаёт ссылку
original = [1, 2, 3]
copy_ref = original
copy_ref[0] = 999
print(original) # [999, 2, 3] — оригинал изменился!
print(copy_ref) # [999, 2, 3]
print(original is copy_ref) # True — это один и тот же объект
# ❌ Проблема 2: Неглубокое копирование (shallow copy)
original_list = [[1, 2], [3, 4]]
shallow = copy.copy(original_list)
shallow[0][0] = 999 # Меняем вложенный список
print(original_list) # [[999, 2], [3, 4]] — оригинал изменился!
print(shallow) # [[999, 2], [3, 4]]
1. Различие между присваиванием, shallow copy и deep copy
import copy
original = {
'name': 'Alice',
'contacts': {
'email': 'alice@example.com',
'phone': '123-456-7890'
}
}
# ❌ Присваивание — ссылка на один объект
ref = original
ref['name'] = 'Bob'
print(original['name']) # 'Bob' — оригинал изменился!
# ✅ Неглубокое копирование — копирует только верхний уровень
shallow = copy.copy(original)
shallow['name'] = 'Charlie' # OK, верхний уровень не затронут
shallow['contacts']['email'] = 'charlie@example.com' # ❌ Вложенные данные меняются
print(original['contacts']['email']) # 'charlie@example.com' — оригинал затронут
# ✅ ГЛУБОКОЕ копирование — копирует рекурсивно все уровни
deep = copy.deepcopy(original)
deep['name'] = 'Diana'
deep['contacts']['email'] = 'diana@example.com'
print(original['contacts']['email']) # 'alice@example.com' — оригинал не затронут!
2. Проблема с изменяемыми параметрами по умолчанию
# ❌ ОПАСНО — используются общие объекты
def add_user(user, user_list=[]):
"""Добавить пользователя в список (с опасным default параметром)"""
user_list.append(user)
return user_list
result1 = add_user({'name': 'Alice'})
result2 = add_user({'name': 'Bob'})
print(result1) # [{'name': 'Alice'}, {'name': 'Bob'}] — неожиданно!
print(result2) # [{'name': 'Alice'}, {'name': 'Bob'}] — тот же список!
# ✅ Правильно — каждый вызов получает свой список
def add_user_safe(user, user_list=None):
if user_list is None:
user_list = []
user_list.append(user)
return user_list
3. Deep copy при работе с конфигурациями
from copy import deepcopy
import json
class ConfigManager:
"""Менеджер конфигурации с глубоким копированием"""
def __init__(self):
self.default_config = {
'database': {
'host': 'localhost',
'port': 5432,
'credentials': {'user': 'admin', 'password': 'secret'}
},
'cache': {
'enabled': True,
'ttl': 3600,
'backends': ['redis', 'memcached']
}
}
def get_config_for_env(self, env: str):
"""Получить конфиг для окружения (с глубокой копией)"""
config = deepcopy(self.default_config) # ВАЖНО: глубокая копия
# Модифицируем конфиг для конкретного окружения
if env == 'production':
config['database']['host'] = 'prod-db.example.com'
config['cache']['ttl'] = 7200
config['cache']['backends'].append('cloudflare')
elif env == 'testing':
config['database']['host'] = 'test-db.local'
config['cache']['enabled'] = False
return config
def get_default_config(self):
"""Получить дефолтный конфиг БЕЗ модификаций"""
# ❌ БЕЗ deepcopy, изменения затронут дефолтный конфиг!
# return self.default_config
# ✅ С deepcopy, дефолтный конфиг защищён
return deepcopy(self.default_config)
# Использование
manager = ConfigManager()
prod_config = manager.get_config_for_env('production')
test_config = manager.get_config_for_env('testing')
default_config = manager.get_default_config()
# Дефолтный конфиг остался неизменным
print(manager.default_config['database']['host']) # 'localhost'
print(prod_config['database']['host']) # 'prod-db.example.com'
print(test_config['database']['host']) # 'test-db.local'
4. Deep copy при работе со сложными объектами
from dataclasses import dataclass
from copy import deepcopy
@dataclass
class Address:
street: str
city: str
zip_code: str
@dataclass
class Person:
name: str
age: int
address: Address
# Создаём объект с вложенными данными
original_person = Person(
name='John',
age=30,
address=Address('Main St', 'New York', '10001')
)
# ❌ Неглубокая копия — address всё ещё ссылается на оригинал
shallow_person = original_person.__dict__.copy()
shallow_person['address'].city = 'Boston'
print(original_person.address.city) # 'Boston' — оригинал затронут!
# ✅ Глубокая копия — полностью независимый объект
deep_person = deepcopy(original_person)
deep_person.address.city = 'Los Angeles'
print(original_person.address.city) # 'New York' — оригинал не затронут!
5. Deep copy при работе с кэшем
from copy import deepcopy
from typing import Any, Dict
class Cache:
"""Кэш с глубоким копированием"""
def __init__(self):
self.data: Dict[str, Any] = {}
def set(self, key: str, value: Any):
"""Сохранить значение в кэш (с глубокой копией)"""
# Глубокая копия защищает кэш от внешних изменений
self.data[key] = deepcopy(value)
def get(self, key: str) -> Any:
"""Получить значение из кэша (с глубокой копией)"""
if key not in self.data:
return None
# Глубокая копия защищает кэш от модификаций клиентом
return deepcopy(self.data[key])
# Использование
cache = Cache()
original_data = {'items': [1, 2, 3], 'metadata': {'version': 1}}
cache.set('my_data', original_data)
# Получаем из кэша
cached_data = cache.get('my_data')
cached_data['items'].append(999)
cached_data['metadata']['version'] = 2
# Оригинальные данные в кэше не затронуты
print(cache.get('my_data')) # {'items': [1, 2, 3], 'metadata': {'version': 1}}
6. Deep copy для состояния игры или приложения
from copy import deepcopy
from typing import Any, List
class GameState:
"""Состояние игры с поддержкой undo/redo"""
def __init__(self):
self.state = {
'player_position': {'x': 0, 'y': 0},
'inventory': ['sword', 'shield'],
'enemies': [{'id': 1, 'health': 100}, {'id': 2, 'health': 80}]
}
self.history: List[dict] = []
def save_state(self):
"""Сохранить текущее состояние (для undo)"""
# ОБЯЗАТЕЛЬНО глубокая копия — иначе история будет ссылаться на один объект
self.history.append(deepcopy(self.state))
def move_player(self, dx: int, dy: int):
"""Переместить игрока"""
self.save_state() # Сохранили предыдущее состояние
self.state['player_position']['x'] += dx
self.state['player_position']['y'] += dy
def damage_enemy(self, enemy_id: int, damage: int):
"""Нанести урон врагу"""
self.save_state() # Сохранили предыдущее состояние
for enemy in self.state['enemies']:
if enemy['id'] == enemy_id:
enemy['health'] -= damage
break
def undo(self):
"""Отменить последнее действие"""
if self.history:
self.state = self.history.pop()
# Использование
game = GameState()
game.move_player(5, 3)
game.damage_enemy(1, 20)
print(game.state['player_position']) # {'x': 5, 'y': 3}
print(game.state['enemies'][0]['health']) # 80
game.undo()
print(game.state['player_position']) # {'x': 0, 'y': 0} — вернулись
7. Deep copy и производительность
import copy
import time
# Сравнение производительности
large_data = {
'level': 1,
'nested': {'data': list(range(10000))}
}
# ✅ Неглубокая копия — быстро
start = time.time()
for _ in range(1000):
shallow = copy.copy(large_data)
shallow_time = time.time() - start
# ❌ Глубокая копия — медленнее (но гарантирует корректность)
start = time.time()
for _ in range(1000):
deep = copy.deepcopy(large_data)
deep_time = time.time() - start
print(f"Shallow: {shallow_time:.4f}s")
print(f"Deep: {deep_time:.4f}s")
# Deep примерно в 10-100 раз медленнее, в зависимости от структуры
# СОВЕТ: Используй deepcopy только когда нужно, а не везде
8. Deep copy с пользовательскими объектами
from copy import deepcopy, copy
class CustomObject:
def __init__(self, name: str, data: list):
self.name = name
self.data = data
def __copy__(self):
"""Определить неглубокую копию"""
return CustomObject(self.name, self.data) # data всё ещё ссылка
def __deepcopy__(self, memo):
"""Определить глубокую копию"""
# memo предотвращает циклические ссылки
return CustomObject(
copy.deepcopy(self.name, memo),
copy.deepcopy(self.data, memo)
)
# Использование
original = CustomObject('test', [1, 2, 3])
deep = deepcopy(original)
deep.data.append(999)
print(original.data) # [1, 2, 3] — не затронута
print(deep.data) # [1, 2, 3, 999]
Лучшие практики
from copy import deepcopy
# ✅ Используй deepcopy когда:
# 1. Модифицируешь вложенные структуры
config = deepcopy(default_config)
config['db']['host'] = 'new-host'
# 2. Сохраняешь состояние для undo/redo
history.append(deepcopy(current_state))
# 3. Возвращаешь данные из кэша или хранилища
return deepcopy(self.cache[key])
# 4. Создаёшь копию для параллельной обработки
for worker in workers:
worker.process(deepcopy(data))
# ❌ НЕ используй deepcopy когда:
# 1. Не нужна полная независимость
temp = shallow_copy(obj)
# 2. Критична производительность
for _ in range(1000000):
copy_obj = obj # Просто ссылка, если не меняешь
Выводы
Глубокое копирование нужно для:
- Защиты оригинальных данных от неожиданных изменений
- Изоляции состояния в многопоточных приложениях
- Кэширования без побочных эффектов
- Undo/Redo функциональности (сохранение истории)
- Конфигурационных менеджеров (разные конфиги для разных окружений)
- Параллельной обработки (безопасное распределение данных)
Основное правило: используй deepcopy когда имеешь дело со сложными изменяемыми структурами, чтобы избежать неожиданных побочных эффектов.