从技术上讲,从__new__ 调用__init__ 不是问题,但这是多余的,因为一旦__new__ 返回实例,就会自动调用__init__。
现在了解deepcopy 失败的原因,我们可以稍微研究一下its internals。
当__deepcopy__ 没有在类上定义时,它属于这种情况:
reductor = getattr(x, "__reduce_ex__", None)
rv = reductor(4)
现在,reductor(4) 在这里返回 function to be used to re-create the object、对象的类型 (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),其中reductor 是__reduce_ex__,这里传递的4 是pickle 协议版本。
现在这个__reduce_ex__ 在内部调用reduce_newobj 来获取要创建的新副本的对象创建函数、参数、状态等。
使用_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
令人惊讶的是,即使我们是从 __getnewargs__ 中返回的,xx 的深层副本 Eggs 也没有存储在 num1 中。这是因为函数_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__ 挂钩以进一步控制行为。我将把这个练习留给用户。