Python如何基于已有类实例创建对应类的新实例
你报错的核心原因是混淆了类对象和实例对象:类方法里的cls参数指代的是类本身(也就是Foo),类的__dict__是只读的映射代理对象,本身就不允许直接赋值,而且就算能改,你修改类属性得到的也不是新的实例,逻辑完全走偏了。
下面是几种可行的实现方案,按实用性排序:
方案1:直接读取反序列化结果(最简便)
shelve本身基于pickle做序列化,你存进去的self本身就是Foo实例,读出来的时候已经是完整的实例对象了,完全可以把之前的外部加载逻辑直接搬到类方法里,替代外部函数:
import shelve class Foo: def __init__(self, a): self.a = a def save(self, fname): with shelve.open(fname, flag='c') as shlv: shlv['data'] = self @classmethod def load(cls, fname): with shelve.open(fname) as shlv: return shlv['data']
这个方案和你之前的外部函数逻辑完全一致,写起来最省事,但缺点是兼容性差:如果后续你修改了Foo类的定义、调整了模块路径,旧文件里存的整实例反序列化很容易报错。
方案2:存储属性字典手动构造实例(推荐生产环境用)
存的时候不要存整个实例对象,只存实例的属性字典;加载的时候先创建不执行__init__的空实例,再把属性赋值上去,兼容性好很多,也能避免__init__里的副作用(比如初始化连接、打印日志之类的逻辑在加载旧实例时重复执行)。
import shelve class Foo: def __init__(self, a): self.a = a def save(self, fname): with shelve.open(fname, flag='c') as shlv: # 仅持久化实例属性,不存整个实例 shlv['instance_attrs'] = self.__dict__ @classmethod def load(cls, fname): with shelve.open(fname) as shlv: saved_attrs = shlv['instance_attrs'] # 创建空实例,跳过__init__执行 new_instance = object.__new__(cls) # 把保存的属性挂载到新实例上 new_instance.__dict__.update(saved_attrs) return new_instance
如果你的__init__参数和存储的属性名完全对应,也可以直接传参构造,不需要跳过__init__:
@classmethod def load(cls, fname): with shelve.open(fname) as shlv: saved_attrs = shlv['instance_attrs'] return cls(**saved_attrs)
这个写法要求存储的属性字典key和__init__的形参名完全一致,灵活性稍差,适合类结构简单、__init__逻辑轻量的场景。
方案3:适配你最初的from_saved_data接口设计
如果你就是要保留from_saved_data的入参设计(传入已经从shelve读出来的data对象,基于它创建新实例),只要把操作对象从类改成新建的实例就行:
@classmethod def from_saved_data(cls, data): new_instance = object.__new__(cls) new_instance.__dict__.update(data.__dict__) return new_instance
这个写法完全不会触发你之前遇到的__dict__不可写报错,因为你操作的是新生成实例的属性字典,不是类本身的只读属性字典。
注意:永远不要通过修改类本身的属性来"创建实例",类是生成实例的模板,你需要的是基于模板生成新的实例对象,再调整实例的属性,而不是直接修改模板。
内容的提问来源于stack exchange,提问作者NotAName

