Технически это не проблема для вызова __init__
из __new__
, но это избыточно, так как вызов __init__
происходит автоматически, когда __new__
возвращает экземпляр.
Теперь прибывает в почему deepcopy
терпит неудачу, мы можем посмотреть его внутренности немного.
Когда __deepcopy__
не определен в классе, он попадает в это условие:
reductor = getattr(x, "__reduce_ex__", None)
rv = reductor(4)
Теперь здесь reductor(4)
возвращает функцию , которая будет использоваться для воссоздания объекта , тип объекта (Test
), передаваемые аргументы и его состояние (в данном случае элементы в словаре экземпляра test.__dict__
):
>>> !rv
(
<function __newobj__ at 0x7f491938f1e0>, # func
(<class '__main__.Test'>,), # type + args in a single tuple
{'num1': 1, 'extra': []}, None, None) # state
Теперь он вызывает _reconstruct
с этими данными:
def _reconstruct(x, memo, func, args,
state=None, listiter=None, dictiter=None,
deepcopy=deepcopy):
deep = memo is not None
if deep and args:
args = (deepcopy(arg, memo) for arg in args)
y = func(*args)
...
Здесь этот вызов закончится Вызов:
def __newobj__(cls, *args):
return cls.__new__(cls, *args)
Но поскольку args
пусто, а cls <class '__main__.Test'>
, вы получите ошибку.
Теперь, как Python определяет эти аргументы для вашего объект, как кажется, это проблема?
Для этого нам нужно рассмотреть: reductor(4)
, где редуктор равен __reduce_ex__
, а 4
, переданный здесь, является pickle версия протокола.
Теперь этот __reduce_ex__
внутренне вызывает reduce_newobj
, чтобы получить функцию создания объекта, аргументы, состояние и т. д. c для создаваемой новой копии.
Аргументы сами по себе определяются с помощью _PyObject_GetNewArguments
.
Теперь эта функция ищет в классе __getnewargs_ex__
или __getnewargs__
, поскольку наш класс не имеет это, мы ничего не получаем за аргументы.
Теперь давайте добавим этот метод и попробуем еще раз:
import copy
class Test:
def __init__(self, num1):
self.num1 = num1
def __getnewargs__(self):
return ('Eggs',)
def __new__(cls, *args, **kwargs):
print(args)
new_inst = object.__new__(cls)
new_inst.__init__(*args, **kwargs)
new_inst.extra = []
return new_inst
test = Test([])
xx = copy.deepcopy(test)
print(xx.num1, test.num1, id(xx.num1), id(test.num1))
# ([],)
# ('Eggs',)
# [] [] 139725263987016 139725265534088
Удивительно, но для глубокой копии xx
не хранится Eggs
в num1
хотя мы возвращаем его с __getnewargs__
. Это связано с тем, что функция _reconstruct
повторно добавляет глубинную копию состояния, которое оно первоначально получило, к экземпляру после его создания, что отменяет эти изменения.
def _reconstruct(x, memo, func, args,
state=None, listiter=None, dictiter=None,
deepcopy=deepcopy):
deep = memo is not None
if deep and args:
args = (deepcopy(arg, memo) for arg in args)
y = func(*args)
if deep:
memo[id(x)] = y
if state is not None:
...
if state is not None:
y.__dict__.update(state) <---
...
Есть ли другие способы сделать это? это?
Обратите внимание на приведенное выше объяснение, и рабочая функция предназначена только для объяснения проблемы. Я бы на самом деле не назвал это лучшим или худшим способом сделать это.
Да, вы могли бы определить свой собственный __deepcopy__
крючок класса для дальнейшего управления поведением. Я бы оставил это упражнение пользователю.