为何set未像其他内置容器使用__newobj__反序列化?继承类拷贝问题解析
继承set的自定义容器类使用copy/deepcopy时的异常问题
最近编写了一批继承自内置list、dict和set类型的自定义容器类,意外发现继承set的类实例使用copy模块的copy和deepcopy函数拷贝时会触发异常,而其他自定义类可正常工作。
最小可复现示例(MWE)
import copy class SetBase(set): def __init__(self, iterable, name, master): super().__init__(iterable) self.name = name self.master = master set_1 = SetBase([1, 2], name='set', master='master') set_2 = copy.deepcopy(set_1)
报错回溯信息
Traceback (most recent call last): File "/Users/test.py", line 10, in <module> set_2 = copy.deepcopy(set_1) ^^^^^^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/copy.py", line 162, in deepcopy y = _reconstruct(x, memo, *rv) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/copy.py", line 253, in _reconstruct y = func(*args) ^^^^^^^^^^^ TypeError: SetBase.__init__() missing 2 required positional arguments: 'name' and 'master'
SetBase是继承自内置set的类,其__init__方法额外接收name和master两个参数。经排查,报错根源在于set类的__reduce_ex__方法。
进一步探索
测试各内置类型的__reduce_ex__方法,得到如下结果:
>>> "".__reduce_ex__(5) (<function __newobj__ at 0x104d41800>, (<class 'str'>, ''), None, None, None) >>> [1,2].__reduce_ex__(5) (<function __newobj__ at 0x104d41800>, (<class 'list'>,), None, <list_iterator object at 0x104c43df0>, None) >>> {1:2}.__reduce_ex__(5) (<function __newobj__ at 0x104d41800>, (<class 'dict'>,), None, None, <dict_itemiterator object at 0x104c5f830>) >>> (1,2).__reduce_ex__(5) (<function __newobj__ at 0x104d41800>, (<class 'tuple'>, (1, 2)), None, None, None) >>> {1,2}.__reduce_ex__(5) (<class 'set'>, ([1, 2],), None)
可以看到,list、dict、tuple使用__newobj__函数进行反序列化创建拷贝实例,而set直接使用自身类。继承set的类遵循该模式,创建拷贝实例后会显式调用__init__方法,但因参数不全触发异常。
疑问
- 是什么导致
set的__reduce_ex__方法如此特殊? - 为何不采用看似更健壮的
__newobj__方式? - 是否有底层因素阻碍
set实现该机制?
可能的修复方案
针对上述示例,可通过重写__reduce__方法解决问题:
class SetBase(set): def __init__(self, iterable, name, master): super().__init__(iterable) self.name = name self.master = master def __reduce__(self): return (self.__class__, (list(self), self.name, self.master))
在实际场景中,若自定义容器有更复杂的属性和状态,可为iterable之外的额外参数设置默认值作为临时解决方案。
内容的提问来源于stack exchange,提问作者ZHANG Wuji
相关产品推荐
相关产品推荐

