Unpickling 和带有默认值的 args 的问题
Problem with Unpickling and args with default values
假设我有一个 class:
class Character():
def __init__(self):
self.race = "Ork"
我创建了一个实例并将其 pickle。
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
属性。为了以正确的方式进行,按照 "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)
现在 unpickling 应该可以正常工作了:
with open(r'C:\tmp\state.bin', 'rb') as f:
c = pickle.load(f)
这段代码工作正常,但是,我仍然没有在 c
对象中看到我们新的 health
属性。
问题很简单,为什么会这样?根据 "Effective Python".
一切都应该正常工作
unpickling 的标准行为直接分配属性 - 它不使用 __init__
或 __new__
。因此,您的默认参数不适用。
When a class instance is unpickled, its __init__()
method is usually not invoked. 1
调用 __init__
可能会产生副作用,并且可能需要比属性更多、更少或其他参数。这使它成为不安全的默认值。实际上,pickle 使用 object.__new__(cls)
创建实例,然后更新它的 __dict__
.
如果需要,您必须明确告诉 pickle
使用 __init__
。
使用 copyreg
时,必须将 constructor
parameter 传递给它。请注意,这确实与您的 unpickle_character
.
具有不同的签名
否则,您的 pickling 函数 (pickle_character
) 静态定义了用于 unpickle 的函数。由于没有为 Character
class 注册构造函数并且旧 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)
在 class 上定义 __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__
是安全且向后兼容的,您也可以使用它从 pickled 状态进行初始化。
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)
假设我有一个 class:
class Character():
def __init__(self):
self.race = "Ork"
我创建了一个实例并将其 pickle。
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
属性。为了以正确的方式进行,按照 "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)
现在 unpickling 应该可以正常工作了:
with open(r'C:\tmp\state.bin', 'rb') as f:
c = pickle.load(f)
这段代码工作正常,但是,我仍然没有在 c
对象中看到我们新的 health
属性。
问题很简单,为什么会这样?根据 "Effective Python".
一切都应该正常工作unpickling 的标准行为直接分配属性 - 它不使用 __init__
或 __new__
。因此,您的默认参数不适用。
When a class instance is unpickled, its
__init__()
method is usually not invoked. 1
调用 __init__
可能会产生副作用,并且可能需要比属性更多、更少或其他参数。这使它成为不安全的默认值。实际上,pickle 使用 object.__new__(cls)
创建实例,然后更新它的 __dict__
.
如果需要,您必须明确告诉 pickle
使用 __init__
。
使用 copyreg
时,必须将 constructor
parameter 传递给它。请注意,这确实与您的 unpickle_character
.
否则,您的 pickling 函数 (pickle_character
) 静态定义了用于 unpickle 的函数。由于没有为 Character
class 注册构造函数并且旧 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)
在 class 上定义 __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__
是安全且向后兼容的,您也可以使用它从 pickled 状态进行初始化。
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)