Chr*_*per 2 python copy new-operator python-internals
我遇到了(在我看来)一个有点奇怪的问题。我定义了一个同时定义了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
Run Code Online (Sandbox Code Playgroud)
如果正常使用,效果很好:
test = Test(1)
assert test.extra == 2
Run Code Online (Sandbox Code Playgroud)
但是,它不会复制。deepcopy:
import copy
copy.deepcopy(test)
Run Code Online (Sandbox Code Playgroud)
给出
TypeError: __init__() missing 1 required positional argument: 'num1'
Run Code Online (Sandbox Code Playgroud)
这可能与使用类包装器和 __new__ 装饰类有关- 我无法确切地看到如何,但我在这里尝试类似的事情 - 我需要new将类包装器应用到我创建的测试实例。
任何帮助感激不尽!
__init__从技术上讲,从调用不是问题__new__,但它是多余的,因为一旦返回实例,__init__ 就会自动发生调用。__new__
deepcopy研究一下它的内部结构。当__deepcopy__类上没有定义时,它会遇到这种情况:
reductor = getattr(x, "__reduce_ex__", None)
rv = reductor(4)
Run Code Online (Sandbox Code Playgroud)
现在,这里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
Run Code Online (Sandbox Code Playgroud)
现在它_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)
...
Run Code Online (Sandbox Code Playgroud)
这里这个调用最终会调用:
def __newobj__(cls, *args):
return cls.__new__(cls, *args)
Run Code Online (Sandbox Code Playgroud)
但由于args是 空且 cls 是<class '__main__.Test'>,因此您会收到错误。
为此,我们需要查看:reductor(4),其中reductor 是__reduce_ex__,4这里传递的是pickle 协议版本。
现在,它__reduce_ex__在内部调用reduce_newobj以获取要制作的新副本的对象创建函数、参数、状态等。
参数本身是使用 找到的_PyObject_GetNewArguments。
现在这个函数在类上查找__getnewargs_ex__or __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
Run Code Online (Sandbox Code Playgroud)
令人惊讶的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) <---
...
Run Code Online (Sandbox Code Playgroud)
注意上面的解释和工作函数只是为了解释问题。我真的不会称其为最好或更差的方法。
是的,您可以在类上定义自己的__deepcopy__挂钩来进一步控制行为。我将这个练习留给用户。