Как скопировать экземпляр класса в Python?

Я хотел бы сделать копию экземпляра класса на Python. Я попробовал copy.deepcopy, но получаю сообщение об ошибке:

RuntimeError: только переменные, явно созданные пользователем (листья графика), в настоящий момент поддерживают протокол deepcopy.

Итак, предположим, у меня есть что-то вроде:

class C(object):
    def __init__(self,a,b, **kwargs):
        self.a=a
        self.b=b
        for x, v in kwargs.items():
            setattr(self, x, v)

c = C(4,5,'r'=2)
c.a = 11
del c.b

А теперь я хочу сделать идентичную глубокую копию c, есть ли простой способ?


person patapouf_ai    schedule 19.01.2018    source источник
comment
да. Определенно. Отмените __copy__ dunder. Или __deepcopy__, в зависимости от того, что вам нужно.   -  person cs95    schedule 19.01.2018
comment
Да, вы можете использовать copy.deepcopy. так что просто c2 = copy.deepcopy(c), затем vars(c2) == {'a': 11, 'r': 2} и vars(c) == {'a': 11, 'r': 2}, но трассировка, о которой вы сообщаете, не будет производиться указанным вами определением класса ...   -  person juanpa.arrivillaga    schedule 19.01.2018
comment
@ cᴏʟᴅsᴘᴇᴇᴅ обратите внимание, что в данном случае это не нужно. Модуль copy будет обрабатывать типы, которые не определяют __copy__ или __deepcopy__. Я не думаю, что это связано с этим связанным дубликатом, сообщение об ошибке предполагает, что глубокая копия была намеренно переопределена, чтобы вызвать ошибку времени выполнения.   -  person juanpa.arrivillaga    schedule 19.01.2018
comment
@ juanpa.arrivillaga А ... ну ... тогда резюме не по теме? Я уже проголосовал.   -  person cs95    schedule 19.01.2018
comment
@ juanpa.arrivillaga да, на самом деле я пытаюсь сделать копию метакласса torch.nn.Module, но это немного сложно, поэтому я привожу здесь более простой пример. Просто констатирую, что в моем случае deepcopy не работает, поэтому мне нужно другое решение.   -  person patapouf_ai    schedule 19.01.2018


Ответы (3)


Да, вы можете сделать копию экземпляра класса с помощью deepcopy:

from copy import deepcopy

c = C(4,5,'r'=2)
d = deepcopy(c)

Это создает копию экземпляра класса «c» в «d».

person Usman    schedule 19.01.2018
comment
Особо отмечу, что у меня это не работает. Потому что в моем случае deepcopy выдает ошибку. Я специально не хочу, чтобы решение использовало deepcopy. - person patapouf_ai; 19.01.2018
comment
Меня направили сюда из Google для поиска того, как глубоко скопировать экземпляр класса, и это работает для меня. Итак, я должен проголосовать за ... - person lode; 31.01.2019
comment
deepcopy использует pickle, который нарушает некоторые объекты: TypeError: can't pickle pygame.Surface objects - это именно то, что я искал здесь для обходного пути. Так что у меня это не работает. - person Marc Maxmeister; 29.09.2019

Один из способов сделать это - реализовать __copy__ в классе C следующим образом:

class A:
    def __init__(self):
        self.var1 = 1
        self.var2 = 2
        self.var3 = 3

class C(A):
    def __init__(self, a=None, b=None, **kwargs):
        super().__init__()
        self.a = a
        self.b = b
        for x, v in kwargs.items():
            setattr(self, x, v)

    def __copy__(self):
        self.normalizeArgs()
        return C(self.a, self.b, kwargs=self.kwargs)

    # THIS IS AN ADDITIONAL GATE-KEEPING METHOD TO ENSURE 
    # THAT EVEN WHEN PROPERTIES ARE DELETED, CLONED OBJECTS
    # STILL GETS DEFAULT VALUES (NONE, IN THIS CASE)
    def normalizeArgs(self):
        if not hasattr(self, "a"):
            self.a      = None
        if not hasattr(self, "b"):
            self.b      = None
        if not hasattr(self, "kwargs"):
            self.kwargs = {}

cMain   = C(a=4, b=5, kwargs={'r':2})

del cMain.b
cClone  = cMain.__copy__()

cMain.a = 11

del  cClone.b
cClone2 = cClone.__copy__()

print(vars(cMain))
print(vars(cClone))
print(vars(cClone2))

введите описание изображения здесь

person Poiz    schedule 19.01.2018
comment
Это не работает, я получаю сообщение об ошибке `9 def __copy __ (self): ---› 10 keywordArgs = vars (self) ['kwargs'] 11 return C (self.a, self.b, kwargs = keywordArgs) 12 KeyError: 'kwargs' ' - person patapouf_ai; 19.01.2018
comment
Не то чтобы он работал даже после того, как я удалю переменную. Итак, после выполнения del c.a я смогу сделать c_prime = c.__copy__(). - person patapouf_ai; 19.01.2018
comment
И он также должен глубоко копировать аргументы. Так что если c.a == [1,2] у меня не должно быть c.a == c_prime.a. - person patapouf_ai; 19.01.2018
comment
он бежит. Но это не решает мою проблему, потому что среди других проблем, упомянутых выше. Если я добавлю в конец скрипта следующую строку: cClone = cMain.__copy__(), (т.е. после строки del), я получу следующую ошибку: AttributeError: 'C' object has no attribute 'b' - person patapouf_ai; 19.01.2018
comment
Это также не сработает, если я обменяю вашу строку cMain = C(a=4, b=5, kwargs={'r':2}) на строку cMain = C(4,5,'r'=2), как в моем вопросе. - person patapouf_ai; 19.01.2018
comment
Последнюю проблему можно решить, если взамен: `def __copy __ (self): \ n keywordArgs = vars (self) \ n return C (** keywordArgs)`. - person patapouf_ai; 19.01.2018
comment
Еще одна проблема с вашим решением заключается в том, что если у меня cMain = C([], 1, r=3), а затем cClone = cMain.__copy__(), затем cMain.a.append(1), то у меня cClone.a == [1], но должно быть []. - person patapouf_ai; 19.01.2018
comment
@patapouf_ai Вы можете реализовать метод gatekeeper для учета этих сценариев ... Фрагмент выше был обновлен, чтобы проиллюстрировать это ... Прокомментируйте, если он все еще не работает для вашего варианта использования ... Также см .: eval.in/938243 - person Poiz; 19.01.2018

Я в основном разобрался. Единственная проблема, которую я не могу преодолеть, - это знание приемлемого набора аргументов инициализации (аргументов для __init__) для всех классов. Поэтому я должен сделать следующие два предположения:

1) У меня есть набор аргументов по умолчанию для класса C, который я называю argsC. 2) Все объекты в C могут быть инициализированы пустыми аргументами.

В этом случае я могу Сначала: инициализировать новый экземпляр класса C из этого экземпляра, который я хочу скопировать c:

c_copy = c.__class__(**argsC)

Второй. Просмотрите все атрибуты c и установите атрибуты c_copy как копии атрибутов c

for att in c.__dict__:
    setattr(c_copy, att, object_copy(getattr(c,att)))

где object_copy - рекурсивное приложение создаваемой нами функции.

Последнее: удалите все атрибуты в c_copy, но не в c:

for att in c_copy.__dict__:
    if not hasattr(c, att):
        delattr(c_copy, att)

Собирая все это вместе, мы получаем:

import copy

def object_copy(instance, init_args=None):
    if init_args:
        new_obj = instance.__class__(**init_args)
    else:
        new_obj = instance.__class__()
    if hasattr(instance, '__dict__'):
        for k in instance.__dict__ :
            try:
                attr_copy = copy.deepcopy(getattr(instance, k))
            except Exception as e:
                attr_copy = object_copy(getattr(instance, k))
            setattr(new_obj, k, attr_copy)

        new_attrs = list(new_obj.__dict__.keys())
        for k in new_attrs:
            if not hasattr(instance, k):
                delattr(new_obj, k)
        return new_obj
    else:
        return instance

Итак, сложив все это вместе, мы имеем:

argsC = {'a':1, 'b':1}
c = C(4,5,r=[[1],2,3])
c.a = 11
del c.b
c_copy = object_copy(c, argsC)
c.__dict__

{'a': 11, 'r': [[1], 2, 3]}

c_copy.__dict__

{'a': 11, 'r': [[1], 2, 3]}

c.__dict__

{'a': 11, 'r': [[1, 33], 2, 3]}

c_copy.__dict__

{'a': 11, 'r': [[1], 2, 3]}

Что и есть желаемый результат. Он использует deepcopy, если может, но в тех случаях, когда он вызывает исключение, он может обойтись и без него.

person patapouf_ai    schedule 30.01.2018