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

Зачем нужно глубокое копирование (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 когда имеешь дело со сложными изменяемыми структурами, чтобы избежать неожиданных побочных эффектов.

Зачем нужно глубокое копирование (deep copy)? | PrepBro