Python 3中__getinitargs__的替代:反序列化时如何调用__init__
__init__的可行方案 你说得没错——Python 3里所有类都是新式类,__getinitargs__确实被彻底忽略了,而__getnewargs__只能给__new__传参,没办法直接触发__init__执行。针对这个问题,我整理了几个靠谱的解决办法,一个个给你说:
方案1:用__reduce__自定义反序列化逻辑
__reduce__是pickle模块处理对象序列化/反序列化的核心方法,我们可以通过它直接指定反序列化时调用类的构造函数。
修改你的代码如下:
import pickle class Car: def __init__(self, model, number): self.model = model self.number = number print("constructed with", model, number) # many other things to do def __reduce__(self): # 返回元组:(构造函数, 构造参数, 状态) # 这里构造函数就是Car类本身,参数对应__init__的入参 return (Car, (self.model, self.number), None) c = Car("toyota", 1234) d = pickle.loads(pickle.dumps(c)) print("reconstructed with", d.model, d.number)
运行后Python 3会输出:
constructed with toyota 1234 constructed with toyota 1234 reconstructed with toyota 1234
完全符合你想要的效果——反序列化时会正常调用__init__,参数也能正确传递。
方案2:结合__getnewargs_ex__和手动调用__init__
如果不想重写__reduce__,也可以用__getnewargs_ex__给__new__传参,再在__new__里手动触发__init__。这个方法适合对__reduce__逻辑不太熟悉的场景:
import pickle class Car: def __init__(self, model, number): self.model = model self.number = number print("constructed with", model, number) # many other things to do def __getnewargs_ex__(self): # 返回(位置参数元组, 关键字参数字典) return ((), {"model": self.model, "number": self.number}) def __new__(cls, *args, **kwargs): # 创建实例对象 instance = super().__new__(cls) # 手动调用__init__完成初始化 instance.__init__(*args, **kwargs) return instance c = Car("toyota", 1234) d = pickle.loads(pickle.dumps(c)) print("reconstructed with", d.model, d.number)
这个方案也能达到相同效果,不过要注意:如果你的类继承了内置不可变类型(比如str、tuple),__new__的逻辑可能需要调整,但普通自定义类完全适用。
方案3:在__setstate__里手动触发__init__
如果你的需求只是让反序列化后的对象拥有正确属性,且不排斥手动调用__init__,也可以在__setstate__里直接执行初始化逻辑:
import pickle class Car: def __init__(self, model, number): self.model = model self.number = number print("constructed with", model, number) # many other things to do def __getstate__(self): # 保存构造所需的参数 return {"model": self.model, "number": self.number} def __setstate__(self, state): # 直接调用__init__完成初始化 self.__init__(state["model"], state["number"]) c = Car("toyota", 1234) d = pickle.loads(pickle.dumps(c)) print("reconstructed with", d.model, d.number)
这个方法逻辑直观,适合简单场景,也能正常触发__init__里的所有逻辑。
补充:为什么Python 2的代码在Python 3里失效?
Python 2里老式类和新式类的pickle机制不同,__getinitargs__是专门为老式类设计的初始化参数传递方法。而Python 3统一使用新式类后,pickle的序列化逻辑改成了优先依赖__reduce__、__getnewargs__等方法,__getinitargs__就被直接忽略了,这也是你看到属性未初始化报错的原因。
内容的提问来源于stack exchange,提问作者pevogam

