【发布时间】:2021-05-01 19:13:59
【问题描述】:
我想创建一个自定义 Python 类,该类可以像字典一样进行 JSON 序列化。以Python 的duck-typing 命名,我想我可以创建一个看起来和嘎嘎声完全像字典的类。但是,对于json.dumps,下面显示的类显然不够像dict——下面的代码会产生错误TypeError: Object of type TotallyADict is not JSON serializable。我可以对 TotallyADict 进行哪些更改,以便 json.dumps 的默认编码器将输出 {"a": 1, "b": 2, "c": 3}?
我知道这个直接的问题可以通过创建自定义编码器来解决,但在这个特定问题已经从中提炼出来的更大问题中,这不是一个可接受的解决方案。
另一个尝试的解决方案是让 TotallyADict 继承自 dict 而不是 MutableMapping。这不会引发任何异常,但在这种情况下 json.dumps(x) 会产生 {};显然,json.dumps 的默认编码器用于 dicts 的数据源不是以下任何被覆盖的方法。
我在这里想要的是能够使用属性语义 (x.c = x.a + x.b) 但仍然序列化为 JSON 对象。因此,一个似乎不起作用的可能建议是 TypedDict(必须是 x['c'] = x['a'] + x['b'])。通过__setattr__ 和__getattribute__ 拦截属性分配和检索并重定向到从dict 继承的条目self 似乎工作得很好,所以这是我的默认解决方案。但令我惊讶的是,有一次我真的想使用鸭式打字而不是严格(ish)打字,它似乎不起作用。
from collections.abc import MutableMapping
import json
class TotallyADict(MutableMapping):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
self._fields = {'a', 'b', 'c'}
def __getitem__(self, key):
if key in self._fields:
return getattr(self, key)
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def __setitem__(self, key, value):
if key in self._fields:
setattr(self, key, value)
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def __delitem__(self, key):
raise RuntimeError('Cannot delete fields from {}'.format(type(self).__name__))
def __iter__(self):
return iter(self._fields)
def __len__(self):
return len(self._fields)
def __contains__(self, k):
return k in self._fields
def copy(self):
return type(self)(**{k: getattr(self, k) for k in self._fields})
def __repr__(self):
return '{' + ', '.join('"{}": {}'.format(k, repr(getattr(self, k))) for k in self._fields) + '}'
def get(self, key, default=None):
if key in self._fields:
value = getattr(self, key)
if value is None:
value = default
return value
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def setdefault(self, key, default=None):
if key in self._fields:
value = getattr(self, key)
if value is None:
value = default
setattr(self, key, value)
return value
else:
raise KeyError('"{}" is not a field in {}'.format(key, type(self).__name__))
def pop(self, key, value=None):
raise RuntimeError('Cannot delete fields from {}'.format(type(self).__name__))
def keys(self):
return self._fields
def items(self):
return [(k, getattr(self, k)) for k in self._fields]
def values(self):
return [getattr(self, k) for k in self._fields]
def __eq__(self, other):
if type(self) is type(other):
for k in self._fields:
if getattr(self, k) != getattr(other, k):
return False
return True
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
x = TotallyADict(1, 2, 3)
print(json.dumps(x))
【问题讨论】:
-
在阅读了 JSON 的文档后,您可能缺少
default方法 docs.python.org/3/library/json.html#json.JSONEncoder -
是的,调用
json.dumps(x, default=lambda d: {k: d[k] for k in d._fields})或类似的方法确实会产生预期的结果。但是,这并不能回答为什么TotallyADict不会在json.dumps中将dict转换为json.dumps,或者如何将其更改为dict。
标签: python json dictionary duck-typing