Як я можу створити копію об'єкта в Python?


200

Я хотів би створити копію об’єкта. Я хочу, щоб новий об'єкт мав усі властивості старого об'єкта (значення полів). Але я хочу мати незалежні об’єкти. Отже, якщо я змінюю значення полів нового об’єкта, на старий об'єкт це не повинно впливати.

Відповіді:


180

Для отримання повністю незалежної копії об'єкта можна скористатися copy.deepcopy()функцією.

Для отримання більш детальної інформації про дрібне та глибоке копіювання зверніться до інших відповідей на це питання та приємного пояснення у цій відповіді на відповідне запитання .


2
Цю відповідь було позначено як "Не відповідь", видалено та відмінено
Aaron Hall

@AaronHall Дякуємо, що повідомили мене! Це, звичайно, не найбільша відповідь, яку я написав, але я згодна з рішенням, що воно не повинно бути насильно видалено. Я трохи розберу його, але оскільки вже є відповіді з усіма подробицями (особливо вашими), я буду тримати її коротко.
Свен Марнах

70

Як я можу створити копію об'єкта в 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]

11

Неглибока копія с 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.


-2

Я вважаю, що з багатьма добре сприйнятими класифікованими в Python системами слід працювати:

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

(Звичайно, я не говорю тут про "глибокі копії", це вже інша історія, і яка може бути не дуже чіткою концепцією - наскільки глибока досить глибока?)

Згідно з моїми тестами з Python 3, для незмінних об'єктів, таких як кортежі або рядки, він повертає той самий об’єкт (тому що не потрібно робити дрібну копію незмінного об'єкта), але для списків або словників створюється незалежна неглибока копія .

Звичайно, цей метод працює лише для класів, конструктори яких ведуть себе відповідно. Можливі випадки використання: виготовлення дрібної копії стандартного класу контейнерів Python.


Це акуратно і все, але не відповідає на питання, оскільки ваша функція копіювання не відповідає для користувацьких класів, а питання стосується об'єктів .
Джаред Сміт,

@JaredSmith, не було заявлено, що питання стосується всіх об'єктів. Навіть не було зрозуміло, чи йдеться про глибоку чи неглибоку копію (я б припустив звичайну дрібну, але прийнята відповідь - про глибоку). Щодо користувацьких класів, якщо вони є вашими, ви можете просто поважати подібну умову в їх __init__методі. Отже, я вважав, що цей метод може бути досить хорошим для певних цілей. У будь-якому випадку, мені будуть цікаві інформативні коментарі до цієї пропозиції.
Олексій

Розглянемо class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argбазові, як це виходить. Якщо я маю на foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>увазі, що ваша copyфункція порушена навіть для самих основних класів. Знову ж таки, це акуратний трюк (отже, не DV), але не відповідь.
Джаред Сміт,

@JaredSmith, я бачив, що існує copy.copyметод створення неглибоких копій, але, можливо, наївно, мені здається, що клас повинен відповідати за створення "дрібної конструктора копій". У такому випадку чому б не надати йому такий же інтерфейс, як dictі listробити? Отже, якщо ваш клас хоче взяти на себе відповідальність за копіювання його об'єктів, чому б не додати if isinstance(arg, type(self))пункт до нього __init__?
Олексій

1
Тому що ти не завжди маєш контроль над класами, ти використовуєш те, як ти робиш ті, які ти визначаєш. Вони, як один із прикладів, можуть бути програмами C, які мають прив'язки Python (наприклад, GTK, openalpr, частини core). Не кажучи вже про те, що навіть якщо ви взяли сторонній бібліотеку і додали методи копіювання до кожного класу, як ви збираєтеся вбудувати це у своє управління залежністю?
Джаред Сміт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.