扩展的类似dict的子类,支持转换和JSON转储,无需额外的功能

Ant*_*hon 9 python json dictionary casting

我需要创建一个t类似于dict的类的实例,T它支持"转换"到真正的dict dict(**t),而不需要回复 dict([(k, v) for k, v in t.items()]).同时支持使用标准json库转储为JSON ,而不扩展普通的JSON编码器(即没有为default参数提供功能).

随着t作为一个正常的dict,这两个工作:

import json

def dump(data):
    print(list(data.items()))
    try:
        print('cast:', dict(**data))
    except Exception as e:
        print('ERROR:', e)
    try:
        print('json:', json.dumps(data))
    except Exception as e:
        print('ERROR:', e)

t = dict(a=1, b=2)
dump(t)
Run Code Online (Sandbox Code Playgroud)

打印:

[('a', 1), ('b', 2)]
cast: {'a': 1, 'b': 2}
json: {"a": 1, "b": 2}
Run Code Online (Sandbox Code Playgroud)

但是我希望t成为类的一个实例,T例如将一个default "动态"键添加到其项目中,因此不需要预先插入(实际上我希望来自一个或多个T实例的合并键出现,这个是对那个真实的,更复杂的类的简化.

class T(dict):
    def __getitem__(self, key):
        if key == 'default':
           return 'DEFAULT'
        return dict.__getitem__(self, key)

    def items(self):
        for k in dict.keys(self):
            yield k, self[k]
        yield 'default', self['default']

    def keys(self):
        for k in dict.keys(self):
            yield k 
        yield 'default'

t = T(a=1, b=2)
dump(t)
Run Code Online (Sandbox Code Playgroud)

这给了:

[('a', 1), ('b', 2), ('default', 'DEFAULT')]
cast: {'a': 1, 'b': 2}
json: {"a": 1, "b": 2, "default": "DEFAULT"}
Run Code Online (Sandbox Code Playgroud)

由于没有关键的"默认",演员阵容无法正常工作,我不知道提供哪种"魔术"功能来进行演员表演.

当我构建实现T的功能collections.abc,并在子类中提供所需的抽象方法时,转换工作:

from collections.abc import MutableMapping

class TIter:
    def __init__(self, t):
        self.keys = list(t.d.keys()) + ['default']
        self.index = 0

    def __next__(self):
        if self.index == len(self.keys):
            raise StopIteration
        res = self.keys[self.index]
        self.index += 1
        return res

class T(MutableMapping):
    def __init__(self, **kw):
        self.d = dict(**kw)

    def __delitem__(self, key):
        if key != 'default':
            del self.d[key]

    def __len__(self):
        return len(self.d) + 1

    def __setitem__(self, key, v):
        if key != 'default':
            self.d[key] = v

    def __getitem__(self, key):
        if key == 'default':
           return 'DEFAULT'
        # return None
        return self.d[key]

    def __iter__(self):
        return TIter(self)

t = T(a=1, b=2)
dump(t)
Run Code Online (Sandbox Code Playgroud)

这使:

[('a', 1), ('b', 2), ('default', 'DEFAULT')]
cast: {'a': 1, 'b': 2, 'default': 'DEFAULT'}
ERROR: Object of type 'T' is not JSON serializable
Run Code Online (Sandbox Code Playgroud)

JSON转储失败,因为该转储器无法处理 MutableMapping子类,它使用C语言显式测试PyDict_Check.

当我试图让T两者的子类dict和 MutableMapping,我没有得到相同的结果只用时为dict子类.

我当然可以认为这是一个错误,json转储器尚未更新以假设(具体的子类) collections.abc.Mapping是可转储的.但即使它被认为是一个bug并在未来的Python版本中得到修复,我也不认为这样的修复将适用于旧版本的Python.

问题1:我如何才能使T实现成为dict正确的子类 ?
Q2:如果Q1没有答案,如果我创建一个返回正确值的C级别 PyDict_Check但是没有执行任何实际实现(然后创建它T的子类以及MutableMapping(我)不要认为添加这样一个不完整的C级别的dict会起作用,但我还没有尝试过),这个傻瓜会不会这样json.dumps()?
Q3这是一个完全错误的方法,让两者都像第一个例子一样工作吗?


实际的代码,更复杂,是我的ruamel.yaml库的一部分,它必须在Python 2.7和Python 3.4+上工作.

只要我无法解决这个问题,我就必须告诉那些过去使用JSON转储器(没有额外参数)的人:

def json_default(obj):
    if isinstance(obj, ruamel.yaml.comments.CommentedMap):
        return obj._od
    if isinstance(obj, ruamel.yaml.comments.CommentedSeq):
        return obj._lst
    raise TypeError

print(json.dumps(d, default=json_default))
Run Code Online (Sandbox Code Playgroud)

告诉他们使用与默认(往返)加载器不同的加载器.例如:

yaml = YAML(typ='safe')
data = yaml.load(stream)
Run Code Online (Sandbox Code Playgroud)

,.to_json()在类上实现一些方法T,让用户ruamel.yaml意识到这一点

,或者回到子类化dict并告诉人们去做

 dict([(k, v) for k, v in t.items()])
Run Code Online (Sandbox Code Playgroud)

其中没有一个是非常友好的,并且表明不可能创建一个非平凡的类似dict的类,并且与标准库很好地配合.

blh*_*ing -1

您可以用完全不同的方式解决问题。您可以使用设置为所需值'default'的键来初始化字典'default',然后通过覆盖所有可能更改内容的方法来保护键的值,而不是在动态请求键时尝试生成值。'default'字典,以便键的值'default'永远不会改变:

class T(dict):
    def __init__(self, **kwargs):
        kwargs['default'] = 'DEFAULT'
        super(T, self).__init__(**kwargs)

    def __setitem__(self, key, value):
        if key != 'default':
            super(T, self).__setitem__(key, value)

    def __delitem__(self, key):
        if key != 'default':
            super(T, self).__delitem__(key)

    def clear(self):
        super(T, self).clear()
        self.__init__()

    def pop(self, key, **kwargs):
        if key == 'default':
            return self[key]
        return super(T, self).pop(key, **kwargs)

    def popitem(self):
        key, value = super(T, self).popitem()
        if key == 'default':
            key2, value2 = super(T, self).popitem()
            super(T, self).__setitem__(key, value)
            return key2, value2
        return key, value

    def update(self, other, **kwargs):
        if kwargs:
            if 'default' in kwargs:
                del kwargs['default']
        elif 'default' in other:
            del other['default']
        super(T, self).update(other, **kwargs)
Run Code Online (Sandbox Code Playgroud)