让我说一堂课
class Character():
def __init__(self):
self.race = "Ork"
我创建一个实例并将其腌制。
c = Character()
import pickle
with open(r'C:\tmp\state.bin', 'w+b') as f:
pickle.dump(c, f)
当我尝试释放它时,一切正常。 但是,如果我想向Character添加另一个属性怎么办? 我去了:
class Character():
def __init__(self):
self.race = "Ork"
self.health = 100
比方说,我想释放不具有health
属性的旧版本。如果仅从文件中释放数据,则该对象将没有health
属性。为了以正确的方式实现它,请按照“有效的Python”一书中的内容进行操作,我需要引入具有默认值的参数,并使copyreg
发挥作用。
所以,我这样做:
class Character
def __init__(self, race = "Ork", health = 100):
self.race = race
self.health = health
import copyreg
def pickle_character(state):
kwargs = state.__dict__
return unpickle_character, (kwargs, )
def unpickle_character(kwargs):
return Character(**kwargs)
copyreg.pickle(Character, pickle_character)
现在解脱应该可以正常工作
with open(r'C:\tmp\state.bin', 'rb') as f:
c = pickle.load(f)
此代码可以正常工作,但是,我仍然没有在c
对象中看到我们新的health
属性。
问题很简单,为什么会发生?根据“有效的Python”,一切都应该正常工作。
答案 0 :(得分:3)
用于解开的标准行为直接分配属性-它不使用__init__
或__new__
。因此,您的默认参数不适用。
取消实例化类实例时,通常不会调用其
__init__()
方法。 1
调用__init__
可能会有副作用,并且可能会使用比属性更多,更少或其他的参数。这使其成为不安全的默认值。实际上,pickle使用object.__new__(cls)
创建实例,然后更新其__dict__
。
如果需要,您必须明确告知pickle
使用__init__
。
使用copyreg
时,必须将constructor
parameter传递给它。请注意,它的签名与unpickle_character
的签名不同。
否则,您的酸洗功能(pickle_character
)会静态定义用于解酸的功能。由于没有为Character
类注册构造函数,并且旧的pickle不包含它,因此加载旧的pickle不会调用您的构造函数。
def pickle_character(state):
kwargs = state.__dict__
return unpickle_character, (kwargs, )
# ^ unpickler stored for *newly pickled instance*!
# no constuctor stored for *Character class* v
copyreg.pickle(Character, pickle_character)
在课堂上定义__setstate__
更容易。这样就可以直接接收状态,甚至可以从较老的泡菜中接收。
class Character:
def __init__(self, race, health):
self.race = race
self.health = health
# load state with defaults for missing attributes
def __setstate__(self, state):
self.race = state.get('race', 'Ork')
self. health = state.get('health', 100)
如果您知道__init__
是安全的并且向后兼容,则也可以使用它从腌制状态进行初始化。
class Character:
# defaults for every initialisation
def __init__(self, race='Ork', health=100):
self.race = race
self.health = health
def __setstate__(self, state):
# re-use __init__ for initialisation
self.__init__(**state)