Eng*_*ock 1 python serialization pickle
假设我有一堂课:
class Character():
def __init__(self):
self.race = "Ork"
Run Code Online (Sandbox Code Playgroud)
我创建了一个实例并对其进行了腌制。
c = Character()
import pickle
with open(r'C:\tmp\state.bin', 'w+b') as f:
pickle.dump(c, f)
Run Code Online (Sandbox Code Playgroud)
当我尝试解开它时,一切正常。但是如果我想为 Character 添加另一个属性怎么办?我去这个:
class Character():
def __init__(self):
self.race = "Ork"
self.health = 100
Run Code Online (Sandbox Code Playgroud)
假设我想取消我们没有health属性的旧版本。如果我只是从文件中解压数据,则对象将不具有该health属性。为了以正确的方式实现它,按照“Effective 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)
Run Code Online (Sandbox Code Playgroud)
现在 unpickling 应该可以正常工作:
with open(r'C:\tmp\state.bin', 'rb') as f:
c = pickle.load(f)
Run Code Online (Sandbox Code Playgroud)
这段代码工作正常,但是,我仍然没有在c对象中看到我们的新health属性。
问题很简单,为什么会发生?根据“Effective Python”,一切都应该正常工作。
unpickling 的标准行为直接分配属性 - 它不使用__init__or __new__。因此,您的默认参数不适用。
当一个类实例被 unpickle 时,它的
__init__()方法通常不会被调用。1
调用__init__可能会产生副作用,并且可能会采用比属性更多、更少或其他参数。这使其成为不安全的默认值。实际上,pickle 用于object.__new__(cls)创建实例,然后更新其__dict__.
如果您愿意,您必须明确告诉pickle使用__init__。
使用时copyreg,必须给它传递 constructor 参数。请注意,这确实具有与您的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)
Run Code Online (Sandbox Code Playgroud)
__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)
Run Code Online (Sandbox Code Playgroud)
如果您知道这__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)
Run Code Online (Sandbox Code Playgroud)