我需要创建支持dict的类t
的实例T
都使用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)
打印:
[('a', 1), ('b', 2)]
cast: {'a': 1, 'b': 2}
json: {"a": 1, "b": 2}
但是我希望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)
这给出了:
[('a', 1), ('b', 2), ('default', 'DEFAULT')]
cast: {'a': 1, 'b': 2}
json: {"a": 1, "b": 2, "default": "DEFAULT"}
,并且由于没有键“默认”,因此强制转换无法正常进行, 而且我不知道提供哪种“魔术”功能来进行铸造 工作。
当我基于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)
给出:
[('a', 1), ('b', 2), ('default', 'DEFAULT')]
cast: {'a': 1, 'b': 2, 'default': 'DEFAULT'}
ERROR: Object of type 'T' is not JSON serializable
JSON转储失败,因为该转储程序无法处理
MutableMapping
子类,它使用PyDict_Check
在C级别上进行显式测试。
当我尝试将T
和dict
和
MutableMapping
,我确实得到了与仅使用时相同的结果
dict
子类。
我当然可以认为它是json
自卸车没有的错误
已更新为假定(的具体子类)
collections.abc.Mapping
是可转储的。但是即使被承认
作为一个错误,并在将来的某些Python版本中得到修复,我不认为
这样的修复将应用于旧版本的Python。
第1季度:我如何制作T
实施,它是的子类
dict
,才能正常投放?
第二季度:如果第一季度没有答案,会回答吗?
如果我使C级类返回正确的值,则可以工作
PyDict_Check
,但不执行任何实际实现(并且
然后将T
和MutableMapping
都设为该子类(我不
认为添加这样一个不完整的C级字典会起作用,但是我没有
尝试过),而这个傻子json.dumps()
会成为傻瓜吗?
第3季度,这是否会使第一种方法像第一个示例一样正常工作?
实际代码,即
更复杂的是我的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))
,告诉他们使用与默认(往返)加载程序不同的加载程序。例如:
yaml = YAML(typ='safe')
data = yaml.load(stream)
,在类.to_json()
上实现一些T
方法,并使用户
的ruamel.yaml
知道这一点
,或者返回子类dict
并告诉人们这样做
dict([(k, v) for k, v in t.items()])
没有一个是真正友好的,这表明不可能 制作类似dict的类,这是不平凡的,并且与标准很好地协作 库。
答案 0 :(得分:2)
由于这里的真正问题实际上是json.dumps
的默认编码器无法将MutableMapping
(或您实际示例中的ruamel.yaml.comments.CommentedMap
)视为命令,而不是告诉人们如前所述,将default
的{{1}}参数设置为json.dumps
函数,您可以使用json_default
将functools.partial
设为{{1} } json_default
的参数,这样人们在使用您的软件包时就不必做任何其他事情:
default
或者,如果您需要允许人们指定自己的json.dumps
参数甚至自己的from functools import partial
json.dumps = partial(json.dumps, default=json_default)
子类,则可以在default
周围使用包装器,以便将{{1由json.JSONEncoder
参数指定的}}函数和由json.dumps
参数指定的自定义编码器的default
方法指定的,无论哪个:
default
,以使以下测试代码具有自定义default
函数和可处理cls
对象的自定义编码器,以及没有自定义import inspect
class override_json_default:
# keep track of the default methods that have already been wrapped
# so we don't wrap them again
_wrapped_defaults = set()
def __call__(self, func):
def override_default(default_func):
def default_wrapper(o):
o = default_func(o)
if isinstance(o, MutableMapping):
o = dict(o)
return o
return default_wrapper
def override_default_method(default_func):
def default_wrapper(self, o):
try:
return default_func(self, o)
except TypeError:
if isinstance(o, MutableMapping):
return dict(o)
raise
return default_wrapper
def wrapper(*args, **kwargs):
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
default = bound.arguments.get('default')
if default:
bound.arguments['default'] = override_default(default)
encoder = bound.arguments.get('cls')
if not default and not encoder:
bound.arguments['cls'] = encoder = json.JSONEncoder
if encoder:
default = getattr(encoder, 'default')
if default not in self._wrapped_defaults:
default = override_default_method(default)
self._wrapped_defaults.add(default)
setattr(encoder, 'default', default)
return func(*bound.args, **bound.kwargs)
sig = inspect.signature(func)
return wrapper
json.dumps=override_json_default()(json.dumps)
或编码器的代码:
default
都将提供正确的输出:
datetime
如评论中所指出的,以上代码使用default
,它在Python 3.3之前不可用,即使那样,from datetime import datetime
def datetime_encoder(o):
if isinstance(o, datetime):
return o.isoformat()
return o
class DateTimeEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, datetime):
return o.isoformat()
return super(DateTimeEncoder, self).default(o)
def dump(data):
print(list(data.items()))
try:
print('cast:', dict(**data))
except Exception as e:
print('ERROR:', e)
try:
print('json with custom default:', json.dumps(data, default=datetime_encoder))
print('json wtih custom encoder:', json.dumps(data, cls=DateTimeEncoder))
del data['c']
print('json without datetime:', json.dumps(data))
except Exception as e:
print('ERROR:', e)
t = T(a=1, b=2, c=datetime.now())
dump(t)
在Python 3.5和{{3}之前不可用}包,是Python 3.3的[('a', 1), ('b', 2), ('c', datetime.datetime(2018, 9, 15, 23, 59, 25, 575642)), ('default', 'DEFAULT')]
cast: {'a': 1, 'b': 2, 'c': datetime.datetime(2018, 9, 15, 23, 59, 25, 575642), 'default': 'DEFAULT'}
json with custom default: {"a": 1, "b": 2, "c": "2018-09-15T23:59:25.575642", "default": "DEFAULT"}
json wtih custom encoder: {"a": 1, "b": 2, "c": "2018-09-15T23:59:25.575642", "default": "DEFAULT"}
json without datetime: {"a": 1, "b": 2, "default": "DEFAULT"}
的反向端口,也没有inspect.signature
方法。为了使代码尽可能地向后兼容,您可以简单地将Python 3.5+的funcsigs
的代码复制并粘贴到您的模块中,并在导入{{1 }}:
inspect.BoundArguments.apply_defaults
答案 1 :(得分:1)
对Q1和Q2的回答是:“您不能”。 “不”
简而言之:您无法在Python中即时添加密钥并获得JSON输出
以及(不修补json.dumps或向其提供default
)。
这样做的原因是,要使JSON完全起作用,您需要
您的类是dict
的子类(或其他在上实现的对象
C级),以便其调用PyDict_Check()
返回非零
(这意味着objectheader中的tp_flags字段具有
Py_TPFLAGS_DICT_SUBCLASS位置1)。
强制转换(dict(**data))
)首先在C级别执行此检查,如下所示:
好(在dictobject.c:dict_merge
中)。但是在方式上有所不同
事情从那里开始。转储JSON时,代码实际上
使用子类提供的例程迭代键/值
如果可用的话。
相反,如果有任何子类,则转换看起来不
继续并复制C级实现中的值(
dict
,ruamel.ordereddict
等)。
在投射不是dict
子类的内容时,
调用普通的Python类级别接口(__iter__
来获取
键/值对。这就是为什么子类MutableMapping使强制转换
可以,但是不幸的是,它中断了JSON转储。
创建精简的C级类并返回非零值是不够的
PyDict_Check()
,因为强制转换会在C级上对该类的键和值进行迭代。
透明地实现此目标的唯一方法是通过实现C级字典(如class)来完成
动态插入密钥default
及其值。它必须通过伪造一个
长度比实际条目数大一倍,并且
以某种方式在ma_keys
和ma_values
的C级实现索引
多余的物品。如果可能的话,这将非常困难,因为dict_merge
假设
修复了有关源对象内部很多知识的知识。
修复json.dumps
的另一种方法是修复dict_merge
,但后者会影响
很多代码的速度都受到负面影响,因此不太可能发生(而且不会
可以在旧版本的Python上追溯执行。