腌制没有__reduce__方法的字典子类不会加载成员属性

ela*_*ard 7 python inheritance dictionary pickle python-3.x

我需要确保a dict只能接受某种类型的对象作为值。它也必须是可挑选的。这是我的第一次尝试:

import pickle

class TypedDict(dict):
    _dict_type = None

    def __init__(self, dict_type, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._dict_type = dict_type

    def __setitem__(self, key, value):
        if not isinstance(value, self._dict_type):
            raise TypeError('Wrong type')
        super().__setitem__(key, value)
Run Code Online (Sandbox Code Playgroud)

如果我使用以下代码进行测试(python 3.5)

my_dict = TypedDict(int)
my_dict['foo'] = 98

with open('out.pkl', 'wb') as fin:
    pickle.dump(my_dict, fin)

with open('out.pkl', 'rb') as fin:
    out = pickle.load(fin)
Run Code Online (Sandbox Code Playgroud)

我收到错误:TypeError: isinstance() arg 2 must be a type or tuple of types
似乎它没有为加载正确的值,_dict_type而是使用default None
另外,它似乎取决于协议,好像它与protocol=0

但是,如果我重写该__reduce__方法并仅调用super,那么一切都会神奇地起作用。

def __reduce__(self):
    return super().__reduce__()
Run Code Online (Sandbox Code Playgroud)

怎么可能?这两个类(w / o __reduce__)不应该等效吗?我想念什么?

MSe*_*ert 5

怎么可能?这两个类(w / o __reduce__)不应该等效吗?我想念什么?

您错过了关键的一步:如果没有__reduce__方法(或者方法失败!),它将使用其他方法来使您的类腌制。因此,具有的类的__reduce__行为就不会与没有的类的行为类似__reduce__(有几种特殊的方法具有这种行为)!

在第一种情况下,它将默认为基本dict转储和加载,然后处理子类逻辑。因此它将使用多个__setitem__调用来创建字典,然后设置实例属性。但是您__setitem__需要instance属性 _dict_type。如果没有,它将默认为class属性 None,该属性将失败

TypeError: isinstance() arg 2 must be a type or tuple of types
Run Code Online (Sandbox Code Playgroud)

这就是为什么它不包含任何键值对的TypedDict情况下可以腌制的原因__reduce__。因为它不会调用__setitem__,然后再设置instance属性:

my_dict = TypedDict(int)

with open('out.pkl', 'wb') as fin:
    pickle.dump(my_dict, fin)

with open('out.pkl', 'rb') as fin:
    out = pickle.load(fin)

print(out._dict_type)   # int
Run Code Online (Sandbox Code Playgroud)

另一方面,如果您实现您的__reduce__方法,__reduce__它就可以工作,因为与失败的普通命令不同-它确实适用于子类(但是如果您不实现,则不会尝试__reduce__):

>>> d = {1: 1}
>>> dict.__reduce__(d)
TypeError: "can't pickle dict objects"

>>> d = TypedDict(int)
>>> dict.__reduce__(d)
(<function copyreg._reconstructor>,
 (__main__.TypedDict, dict, {}),
 {'_dict_type': int})
Run Code Online (Sandbox Code Playgroud)