Copy: примеры (PYTHON)

Изучаем функцию copy в Python
Раздел: Копирование объектов, Утилиты
copy: Any

Основы функции copy

Функция copy() является методом для коллекций (таких как списки или словари) и предназначена для создания поверхностной (shallow) копии объекта. Её использование актуально, когда требуется получить новый объект с тем же содержимым, но чтобы изменения в копии не затрагивали оригинал (за исключением случаев с вложенными изменяемыми объектами).

Функция не принимает аргументов и возвращает новый объект того же типа. Для списков list.copy() эквивалентно срезу list[:]. Для словарей dict.copy() создаёт новый словарь с теми же парами ключ-значение. Важно помнить, что копия является поверхностной: создаётся новый контейнер, но элементы внутри него остаются ссылками на те же объекты, что и в оригинале.

Простые примеры использования

Копирование списка

original_list = [1, 2, 3]
new_list = original_list.copy()
print(f'Оригинал: {original_list}')
print(f'Копия: {new_list}')
new_list.append(4)
print(f'Оригинал после изменения копии: {original_list}')
print(f'Копия после изменения: {new_list}')
Оригинал: [1, 2, 3]
Копия: [1, 2, 3]
Оригинал после изменения копии: [1, 2, 3]
Копия после изменения: [1, 2, 3, 4]

Копирование словаря

original_dict = {'a': 1, 'b': 2}
new_dict = original_dict.copy()
print(f'Оригинал: {original_dict}')
print(f'Копия: {new_dict}')
new_dict['c'] = 3
print(f'Оригинал после изменения копии: {original_dict}')
print(f'Копия после изменения: {new_dict}')
Оригинал: {'a': 1, 'b': 2}
Копия: {'a': 1, 'b': 2}
Оригинал после изменения копии: {'a': 1, 'b': 2}
Копия после изменения: {'a': 1, 'b': 2, 'c': 3}

Особенность поверхностной копии

original_list = [[1, 2], [3, 4]]
new_list = original_list.copy()
new_list[0][0] = 'changed'
print(f'Оригинал: {original_list}')
print(f'Копия: {new_list}')
Оригинал: [['changed', 2], [3, 4]]
Копия: [['changed', 2], [3, 4]]

Аналоги в Python

Функция copy.deepcopy() из модуля copy создает глубокую (deep) копию, рекурсивно копируя все вложенные изменяемые объекты. Это предпочтительно для сложных структур, где необходима полная изоляция копии от оригинала.

Синтаксис среза list[:] для списков функционально эквивалентен list.copy(). Выбор между ними часто сводится к стилю программирования.

Конструктор типа list(original_list) или dict(original_dict) также создаёт поверхностную копию. Это явный и читаемый способ, особенно полезный при копировании итератора в конкретную коллекцию.

Метод dict.fromkeys() не является прямым аналогом, но может использоваться для создания нового словаря с теми же ключами и общим значением.

Функции копирования в других языках

JavaScript

Используется оператор spread ... или Object.assign() для поверхностного копирования объектов и массивов. Для глубокого копирования часто применяют JSON.parse(JSON.stringify(obj)) или библиотечные функции.

// Поверхностное копирование массива
const original = [1, 2, {a: 3}];
const copy = [...original];
copy[2].a = 4;
console.log(original[2].a); // 4
// Поверхностное копирование объекта
const obj = {x: 1};
const objCopy = {...obj};

Java

Для коллекций используются конструкторы (new ArrayList<>(oldList)) или метод clone(). Копия обычно поверхностная. Глубокое копирование требует реализации интерфейса Cloneable или использования сериализации.

PHP

Оператор присваивания создаёт ссылку на объект. Для копирования объектов используется ключевое слово clone, которое выполняет поверхностное копирование. Метод __clone() может быть определён для управления процессом.

// Поверхностное копирование объекта
class MyClass {}
$obj = new MyClass();
$objCopy = clone $obj;

Golang

Присваивание для срезов (slices) и карт (maps) копирует дескриптор, а не данные. Для поверхностного копирования среза используется функция copy(dst, src []Type). Для глубокого копирования необходима самостоятельная реализация или использование пакетов вроде github.com/jinzhu/copier.

// Копирование среза
original := []int{1, 2, 3}
newSlice := make([]int, len(original))
copy(newSlice, original)

C#

Для коллекций из пространства имён System.Collections.Generic можно использовать конструктор (new List(oldList)) для поверхностного копирования. Метод object.MemberwiseClone() создаёт поверхностную копию объекта, но требует приведения типа.

Частые ошибки

Ожидание глубокого копирования

import copy
original = [[1, 2], [3, 4]]
shallow_copy = original.copy()  # или copy.copy(original)
shallow_copy[0][0] = 99
print(original)  # Изменится и оригинал
[[99, 2], [3, 4]]

Для решения следует использовать copy.deepcopy().

Копирование объектов, содержащих не только данные

Метод copy() не копирует дополнительные атрибуты, добавленные в экземпляры пользовательских классов, если они не определены в __slots__ или __dict__ (что копируется поверхностно).

Попытка использования для объектов, не поддерживающих копирование

try:
    gen = (x for x in range(3))
    gen.copy()
except AttributeError as e:
    print(f'Ошибка: {e}')
Ошибка: 'generator' object has no attribute 'copy'

Изменения в новых версиях Python

Метод copy() для словарей был добавлен в Python 3.x как аналог методу dict.copy(), существовавшему и ранее. В версиях Python 3.3 и выше не было значительных изменений в поведении этой функции для основных встроенных коллекций. Однако, всегда важно учитывать, что для типов из модуля collections (например, deque, defaultdict, OrderedDict) метод copy() также доступен и ведёт себя аналогично, создавая поверхностную копию.

Расширенные примеры

Копирование объектов кастомных классов

Пример python
class MyClass:
    def __init__(self, value):
        self.value = value
        self.data = []

obj1 = MyClass(5)
obj1.data.append([1, 2])
# Поверхностное копирование через copy.copy
import copy
obj2 = copy.copy(obj1)
obj2.value = 10
obj2.data[0][0] = 99  # Изменение затронет obj1.data
print(f'obj1.value={obj1.value}, obj1.data={obj1.data}')
print(f'obj2.value={obj2.value}, obj2.data={obj2.data}')
obj1.value=5, obj1.data=[[99, 2]]
obj2.value=10, obj2.data=[[99, 2]]

Копирование с использованием __copy__ и __deepcopy__

Пример python
class GraphNode:
    def __init__(self, name):
        self.name = name
        self.connections = []
    def __copy__(self):
        new_node = GraphNode(self.name)
        new_node.connections = self.connections.copy()  # Поверхностная копия списка
        return new_node
    def __deepcopy__(self, memo):
        import copy
        new_node = GraphNode(copy.deepcopy(self.name, memo))
        memo[id(self)] = new_node
        new_node.connections = copy.deepcopy(self.connections, memo)
        return new_node

node_a = GraphNode('A')
node_b = GraphNode('B')
node_a.connections.append(node_b)

node_a_shallow = copy.copy(node_a)
node_a_deep = copy.deepcopy(node_a)
print(f'id оригинала node_a.connections: {id(node_a.connections)}')
print(f'id поверхностной копии connections: {id(node_a_shallow.connections)}')
print(f'id глубокой копии connections: {id(node_a_deep.connections)}')
id оригинала node_a.connections: [адрес в памяти]
id поверхностной копии connections: [другой адрес в памяти]
id глубокой копии connections: [третий адрес в памяти]

Копирование множества (set)

Пример python
original_set = {1, 2, 3}
set_copy = original_set.copy()  # или set(original_set)
original_set.add(4)
print(f'Оригинал: {original_set}')
print(f'Копия: {set_copy}')
Оригинал: {1, 2, 3, 4}
Копия: {1, 2, 3}

Сравнение скорости различных способов копирования списка

Пример python
import timeit
setup = 'original = list(range(1000))'
stmt1 = 'new = original.copy()'
stmt2 = 'new = original[:]'
stmt3 = 'new = list(original)'
stmt4 = 'import copy; new = copy.copy(original)'
time1 = timeit.timeit(stmt1, setup, number=10000)
time2 = timeit.timeit(stmt2, setup, number=10000)
time3 = timeit.timeit(stmt3, setup, number=10000)
time4 = timeit.timeit(stmt4, setup, number=10000)
print(f'Метод copy(): {time1:.4f} сек')
print(f'Срез [:]: {time2:.4f} сек')
print(f'Конструктор list(): {time3:.4f} сек')
print(f'copy.copy(): {time4:.4f} сек')
Метод copy(): 0.0012 сек
Срез [:]: 0.0013 сек
Конструктор list(): 0.0019 сек
copy.copy(): 0.0043 сек

питон copy function comments

En
Copy Return shallow copy