Как создать копию объекта в Python?

Я хотел бы создать копию объекта. Я хочу, чтобы новый объект обладал всеми свойствами старого объекта (значения полей). Но я хочу иметь независимые объекты. Поэтому, если я изменяю значения полей нового объекта, этот объект не должен влиять на старый объект.

Ответ 1

Чтобы получить полностью независимую копию объекта, вы можете использовать copy.deepcopy().

Для получения более подробной информации о мелком и глубоком копировании, пожалуйста, обратитесь к другим ответам на этот вопрос и хорошему объяснению в этом ответе на связанный вопрос.

Ответ 2

Как я могу создать копию объекта в Python?

Таким образом, если я изменю значения полей нового объекта, на старый объект это не должно повлиять.

Вы имеете в виду изменчивый объект тогда.

В Python 3 списки получают метод copy (в 2 вы бы использовали фрагмент для создания копии):

>>> a_list = list('abc')
>>> a_copy_of_a_list = a_list.copy()
>>> a_copy_of_a_list is a_list
False
>>> a_copy_of_a_list == a_list
True

Мелкие копии

Мелкие копии - это всего лишь копии самого внешнего контейнера.

list.copy - это мелкая копия:

>>> list_of_dict_of_set = [{'foo': set('abc')}
... ]
>>> list_of_dict_of_set = [{'foo': set('abc')}]
>>> lodos_copy = list_of_dict_of_set.copy()
>>> lodos_copy[0]['foo'].pop()
'c'
>>> lodos_copy
[{'foo': {'b', 'a'}}]
>>> list_of_dict_of_set
[{'foo': {'b', 'a'}}]

Вы не получаете копию предметов интерьера. Это один и тот же объект - поэтому, когда они видоизменены, изменение обнаруживается в обоих контейнерах.

Глубокие копии

Глубокие копии - это рекурсивные копии каждого предмета интерьера.

>>> lodos_deep_copy = copy.deepcopy(list_of_dict_of_set)
>>> lodos_deep_copy[0]['foo'].add('c')
>>> lodos_deep_copy
[{'foo': {'c', 'b', 'a'}}]
>>> list_of_dict_of_set
[{'foo': {'b', 'a'}}]

Изменения не отражаются в оригинале, только в копии.

Неизменные предметы

Неизменяемые объекты обычно не нужно копировать. На самом деле, если вы попытаетесь, Python просто выдаст вам оригинальный объект:

>>> a_tuple = tuple('abc')
>>> tuple_copy_attempt = a_tuple.copy()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'copy'

У кортежей даже нет метода копирования, поэтому давайте попробуем его с фрагментом:

>>> tuple_copy_attempt = a_tuple[:]

Но мы видим тот же объект:

>>> tuple_copy_attempt is a_tuple
True

Аналогично для строк:

>>> s = 'abc'
>>> s0 = s[:]
>>> s == s0
True
>>> s is s0
True

и для Frozensets, хотя у них есть метод copy:

>>> a_frozenset = frozenset('abc')
>>> frozenset_copy_attempt = a_frozenset.copy()
>>> frozenset_copy_attempt is a_frozenset
True

Когда копировать неизменяемые объекты

Неизменяемые объекты должны быть скопированы, если вам нужно скопировать изменяемый внутренний объект.

>>> tuple_of_list = [],
>>> copy_of_tuple_of_list = tuple_of_list[:]
>>> copy_of_tuple_of_list[0].append('a')
>>> copy_of_tuple_of_list
(['a'],)
>>> tuple_of_list
(['a'],)
>>> deepcopy_of_tuple_of_list = copy.deepcopy(tuple_of_list)
>>> deepcopy_of_tuple_of_list[0].append('b')
>>> deepcopy_of_tuple_of_list
(['a', 'b'],)
>>> tuple_of_list
(['a'],)

Как мы видим, когда внутренний объект копии видоизменен, оригинал не изменяется.

Пользовательские объекты

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

Чтобы создать копируемый объект, определите __copy__ (для мелких копий) и/или __deepcopy__ (для глубоких копий).

from copy import copy, deepcopy

class Copyable:
    __slots__ = 'a', '__dict__'
    def __init__(self, a, b):
        self.a, self.b = a, b
    def __copy__(self):
        return type(self)(self.a, self.b)
    def __deepcopy__(self, memo): # memo is a dict of id to copies
        id_self = id(self)        # memoization avoids unnecesary recursion
        _copy = memo.get(id_self)
        if _copy is None:
            _copy = type(self)(
                deepcopy(self.a, memo), 
                deepcopy(self.b, memo))
            memo[id_self] = _copy 
        return _copy

Обратите внимание, что deepcopy хранит словарь для deepcopy id(original) (или идентификационные номера) для копирования. Чтобы получить хорошее поведение с рекурсивными структурами данных, убедитесь, что вы еще не сделали копию, и, если она у вас есть, верните ее.

Итак, давайте сделаем объект:

>>> c1 = Copyable(1, [2])

И copy делает мелкую копию:

>>> c2 = copy(c1)
>>> c1 is c2
False
>>> c2.b.append(3)
>>> c1.b
[2, 3]

И deepcopy теперь делает глубокую копию:

>>> c3 = deepcopy(c1)
>>> c3.b.append(4)
>>> c1.b
[2, 3]

Ответ 3

Мелкая копия с copy.copy()

#!/usr/bin/env python3

import copy

class C():
    def __init__(self):
        self.x = [1]
        self.y = [2]

# It copies.
c = C()
d = copy.copy(c)
d.x = [3]
assert c.x == [1]
assert d.x == [3]

# It shallow.
c = C()
d = copy.copy(c)
d.x[0] = 3
assert c.x == [3]
assert d.x == [3]

Глубокая копия с copy.deepcopy()

#!/usr/bin/env python3
import copy
class C():
    def __init__(self):
        self.x = [1]
        self.y = [2]
c = C()
d = copy.deepcopy(c)
d.x[0] = 3
assert c.x == [1]
assert d.x == [3]

Документация: https://docs.python.org/3/library/copy.html

Проверено на Python 3.6.5.

Ответ 4

Я считаю, что следующее должно работать со многими классами в Python:

def copy(obj):
    return type(obj)(obj)

(Конечно, я не говорю здесь о "глубоких копиях", это отдельная история, и которая может быть не очень ясным понятием - насколько глубоко достаточно глубоко?)

Согласно моим тестам с Python 3, для неизменяемых объектов, таких как кортежи или строки, он возвращает один и тот же объект (поскольку нет необходимости делать поверхностную копию неизменяемого объекта), но для списков или словарей он создает независимую поверхностную копию.,

Конечно, этот метод работает только для классов, конструкторы которых ведут себя соответственно. Возможные варианты использования: создание мелкой копии стандартного контейнерного класса Python.