Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Копирование объектов по значению в Python: Полное руководство
Копирование объектов — это частая задача в разработке, но её легко сделать неправильно. Я научился различать разные типы копирования и понимать, когда использовать каждый из них.
Проблема: Shallow vs Deep Copy
Сначала давайте разберемся, почему простое присваивание не работает:
# НЕПРАВИЛЬНО: Это создаёт только ссылку
original = {'name': 'John', 'addresses': [{'city': 'NYC'}, {'city': 'LA'}]}
copy_wrong = original # Это не копирование!
copy_wrong['name'] = 'Jane'
print(original['name']) # "Jane" — оригинал изменился!
# Вложенный список тоже изменяется
copy_wrong['addresses'][0]['city'] = 'Boston'
print(original['addresses'][0]['city']) # "Boston" — вложенный объект тоже изменился!
Метод 1: Shallow Copy (Поверхностное копирование)
Копирует только верхний уровень объекта. Вложенные объекты остаются ссылками.
import copy
# Способ 1: copy.copy()
original = {'name': 'John', 'hobbies': ['reading', 'coding']}
shallow = copy.copy(original)
shallow['name'] = 'Jane' # OK, оригинал не изменится
print(original['name']) # "John"
shallow['hobbies'].append('gaming') # ПРОБЛЕМА!
print(original['hobbies']) # ['reading', 'coding', 'gaming'] — изменился!
# Способ 2: Для списков и словарей
original_list = [1, 2, 3]
shallow_list = original_list.copy() # или list(original_list)
shallow_list[0] = 999
print(original_list) # [1, 2, 3] — OK
original_dict = {'a': 1, 'b': 2}
shallow_dict = original_dict.copy() # или dict(original_dict)
shallow_dict['a'] = 999
print(original_dict) # {'a': 1, 'b': 2} — OK
# Способ 3: Для словарей через распаковку
original_dict = {'name': 'John', 'age': 30}
shallow_dict = {**original_dict} # Python 3.5+
Метод 2: Deep Copy (Глубокое копирование) — Правильный выбор
Копирует объект полностью, включая все вложенные объекты рекурсивно.
import copy
original = {
'name': 'John',
'addresses': [
{'city': 'NYC', 'zip': '10001'},
{'city': 'LA', 'zip': '90001'}
],
'hobbies': ['reading', 'coding']
}
# Глубокое копирование
deep = copy.deepcopy(original)
# Все изменения НЕ влияют на оригинал
deep['name'] = 'Jane'
deep['addresses'][0]['city'] = 'Boston'
deep['hobbies'].append('gaming')
print(original['name']) # "John" — неизменен
print(original['addresses'][0]['city']) # "NYC" — неизменен
print(original['hobbies']) # ['reading', 'coding'] — неизменен
Метод 3: JSON для простых типов данных
Для объектов, состоящих только из примитивов и коллекций:
import json
original = {
'name': 'John',
'age': 30,
'scores': [95, 87, 92],
'metadata': {'level': 'pro', 'verified': True}
}
# JSON способ (работает для примитивов)
deep_copy = json.loads(json.dumps(original))
deep_copy['name'] = 'Jane'
deep_copy['metadata']['level'] = 'expert'
print(original) # Не изменился
print(deep_copy) # Изменилась только копия
# ВАЖНО: datetime и кастомные объекты не работают
try:
data_with_date = {'created': datetime.now()}
copy_data = json.loads(json.dumps(data_with_date))
except TypeError as e:
print(f"Error: {e}") # TypeError: Object of type datetime is not JSON serializable
Метод 4: Для кастомных классов
from dataclasses import dataclass
from typing import List
import copy
@dataclass
class Address:
city: str
country: str
@dataclass
class Person:
name: str
age: int
addresses: List[Address]
def __copy__(self):
"""Кастомное поверхностное копирование"""
return Person(
name=self.name,
age=self.age,
addresses=self.addresses # Ссылка на тот же список
)
def __deepcopy__(self, memo):
"""Кастомное глубокое копирование"""
return Person(
name=self.name, # Строки неизменяемы
age=self.age,
addresses=copy.deepcopy(self.addresses, memo)
)
# Использование
original = Person(
name='John',
age=30,
addresses=[Address('NYC', 'USA'), Address('LA', 'USA')]
)
deep_copy = copy.deepcopy(original)
deep_copy.addresses[0].city = 'Boston'
print(original.addresses[0].city) # "NYC"
print(deep_copy.addresses[0].city) # "Boston"
Метод 5: Для DataClasses (Modern Python)
from dataclasses import dataclass, replace, field
from typing import List
import copy
@dataclass
class Tag:
name: str
color: str
@dataclass
class Post:
title: str
tags: List[Tag] = field(default_factory=list)
# Способ 1: dataclasses.replace (только shallow)
original = Post(title='Python Tips', tags=[Tag('python', 'blue')])
modified = replace(original, title='Advanced Python')
# Осторожно: tags по-прежнему ссылается на исходный список
# Способ 2: Deep copy для dataclass
original = Post(title='Python Tips', tags=[Tag('python', 'blue')])
deep = copy.deepcopy(original)
deep.tags[0].color = 'red'
print(original.tags[0].color) # "blue"
print(deep.tags[0].color) # "red"
Метод 6: Для Pydantic моделей (FastAPI, etc)
from pydantic import BaseModel
from typing import List
import copy
class Address(BaseModel):
city: str
country: str
class User(BaseModel):
name: str
addresses: List[Address]
# Способ 1: Встроенный метод copy() в Pydantic v2
original = User(
name='John',
addresses=[Address(city='NYC', country='USA')]
)
copied = original.model_copy() # Shallow copy
deep_copied = original.model_copy(deep=True) # Deep copy
deep_copied.addresses[0].city = 'Boston'
print(original.addresses[0].city) # "NYC"
# Способ 2: Через dict
original_dict = original.model_dump()
modified = User(**original_dict)
# Способ 3: Через JSON (для сериализации)
import json
json_str = original.model_dump_json()
copied = User.model_validate_json(json_str)
Практическое сравнение: Когда использовать что
import copy
import json
from timeit import timeit
data = {
'users': [
{'id': i, 'name': f'User {i}', 'tags': list(range(10))}
for i in range(100)
]
}
# Тест производительности
def test_copy():
copy.copy(data)
def test_deepcopy():
copy.deepcopy(data)
def test_json():
json.loads(json.dumps(data))
print("copy.copy():", timeit(test_copy, number=1000))
print("copy.deepcopy():", timeit(test_deepcopy, number=1000))
print("JSON:", timeit(test_json, number=1000))
# Результаты на моей машине:
# copy.copy(): 0.05 сек
# copy.deepcopy(): 2.5 сек
# JSON: 1.2 сек
Типичные ошибки
# ОШИБКА 1: Забыли про вложенные объекты
original = {'list': [1, 2, 3]}
copied = original.copy() # Shallow copy!
copied['list'].append(4)
print(original['list']) # [1, 2, 3, 4] — неправильно!
# ПРАВИЛЬНО
copied = copy.deepcopy(original)
# ОШИБКА 2: Бесконечная рекурсия при циклических ссылках
node1 = {'value': 1}
node2 = {'value': 2}
node1['next'] = node2
node2['next'] = node1 # Циклическая ссылка
# copy.copy() работает
shallow = copy.copy(node1) # OK
# copy.deepcopy() обрабатывает циклы правильно через memo
deep = copy.deepcopy(node1) # OK, не зависает
# ОШИБКА 3: Использовал JSON с datetime
from datetime import datetime
data = {'created': datetime.now()}
copy_data = json.loads(json.dumps(data)) # TypeError!
# Правильно: используйте copy.deepcopy()
Итоговая рекомендация
| Ситуация | Решение |
|---|---|
| Простые типы (int, str, tuple) | Присваивание (они неизменяемы) |
| Список/словарь примитивов | .copy() или shallow copy |
| Вложенные структуры | copy.deepcopy() |
| JSON-serializable данные | JSON способ или deepcopy |
| Pydantic модели | .model_copy(deep=True) |
| Кастомные классы | Реализовать __deepcopy__() |
| Production код | Всегда используй copy.deepcopy() если сомневаешься |
Важный принцип: При работе с изменяемыми объектами, если планируешь их менять, всегда делай deep copy, чтобы не получить неожиданные побочные эффекты.