Ответы:
Чтобы получить полностью независимую копию объекта, вы можете использовать copy.deepcopy()функцию.
Для получения более подробной информации о мелком и глубоком копировании, пожалуйста, обратитесь к другим ответам на этот вопрос и хорошему объяснению в этом ответе на связанный вопрос .
Как я могу создать копию объекта в 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')}]
>>> 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
и для фрозенце, хотя у них есть 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's 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хранит словарь памятки 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]
Мелкая копия с 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's 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.
Я считаю, что следующее должно работать со многими классами в Python:
def copy(obj):
return type(obj)(obj)
(Конечно, я не говорю здесь о «глубоких копиях», это отдельная история, и которая может быть не очень ясным понятием - насколько глубоко это глубоко?)
Согласно моим тестам на Python 3, для неизменяемых объектов, таких как кортежи или строки, он возвращает один и тот же объект (поскольку нет необходимости делать поверхностную копию неизменяемого объекта), но для списков или словарей он создает независимую поверхностную копию. ,
Конечно, этот метод работает только для классов, конструкторы которых ведут себя соответственно. Возможные варианты использования: создание мелкой копии стандартного контейнерного класса Python.
__init__методах. Итак, я подумал, что этот метод может быть достаточно хорош для определенных целей. В любом случае меня заинтересуют информативные комментарии по этому предложению.
class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argBasic, как он есть. Если я делаю Это foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>означает, что ваша copyфункция нарушена даже для самых базовых классов. Опять же, это аккуратный трюк (следовательно, нет DV), но не ответ.
copy.copyметод для создания мелких копий, но, возможно, наивно, мне кажется, что ответственность за обеспечение «конструктора мелкой копии» должна быть на классе. В таком случае, почему бы не предоставить такой же интерфейс к нему, как dictи listделать? Итак, если ваш класс хочет взять на себя ответственность за копирование своих объектов, почему бы не добавить в него if isinstance(arg, type(self))предложение __init__?