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

Как можно скопировать объект по значению?

2.2 Middle🔥 181 комментариев
#Python Core

Комментарии (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, чтобы не получить неожиданные побочные эффекты.

Как можно скопировать объект по значению? | PrepBro