用默认值解开和参数问题

时间:2019-03-27 15:43:57

标签: python serialization pickle

让我说一堂课

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”,一切都应该正常工作。

1 个答案:

答案 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)