我遇到了(在我看来)一个有点奇怪的问题。我定义了一个同时定义了 init 和 new 的类,如下:
class Test:
def __init__(self, num1):
self.num1 = num1
def __new__(cls, *args, **kwargs):
new_inst = object.__new__(cls)
new_inst.__init__(*args, **kwargs)
new_inst.extra = 2
return new_inst
如果正常使用,效果很好:
test = Test(1)
assert test.extra == 2
但是,它不会复制。deepcopy:
import copy
copy.deepcopy(test)
给出
TypeError: __init__() missing 1 required positional argument: 'num1'
这可能与 Decorating class with class wrapper and __new__ 有关- 我看不出具体如何,但我在这里尝试类似的事情 - 我需要 new 将类包装器应用到我创建的测试实例。
非常感谢您的帮助!
最佳答案
从技术上讲,调用 __init__
不是问题。来自__new__
,但作为对 __init__
的调用是多余的自动发生一次__new__
返回实例。
现在来看看为什么 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)
,其中 reducer 是 __reduce_ex__
和 4
这里传递的是pickle协议(protocol)版本。
现在这个__reduce_ex__
内部调用 reduce_newobj
获取要制作的新副本的对象创建函数、参数、状态等。
参数本身可以使用 _PyObject_GetNewArguments
找到.
现在这个函数寻找__getnewargs_ex__
或__getnewargs__
在类里面,由于我们的类(class)没有它,所以我们没有任何争论。
现在让我们添加此方法并重试:
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__
Hook 类以进一步控制行为。我将这个练习留给用户。
关于python - 无法深度复制同时定义了 __init__ 和 __new__ 的类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59762417/