python - 无法深度复制同时定义了 __init__ 和 __new__ 的类

标签 python copy new-operator python-internals

我遇到了(在我看来)一个有点奇怪的问题。我定义了一个同时定义了 initnew 的类,如下:

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/

相关文章:

python - 检查 git 上的 Python 包是否比本地更新

c++ - 检测堆栈或堆分配

python - 如何从数据框中提取特定值的索引和列?

python - 如何在文件夹内运行 docker 命令

delphi - 将文件从本地复制到 SharePoint 服务器位置

c++ - 隐式声明的 move 操作不会回退到复制?

c++ - 在类中使用自定义 new() 运算符时,谁为 shared_ptr 的控制 block 分配内存

javascript - JavaScript 中关键字 'new' 有什么副作用?

python - pandas 1.0.1 中 "datetime64[ns, UTC]"采样的 groupby 行为不正确?

c# - 将数据从数据表复制到 SQL Server