Мне нужно создать экземпляр t
класса, похожего на dict 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"}
и приведение не работает должным образом, потому что нет ключа «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
подклассы, он явно тестирует на уровне C, используя PyDict_Check
.
Когда я попытался сделать T
подклассом dict
и MutableMapping
, я получилтот же результат, что и при использовании только подкласса dict
.
Конечно, я могу считать ошибкой, что самосвал json
не был обновлен, чтобы предположить, что (конкретные подклассы) collections.abc.Mapping
являются дампами,Но даже если это будет признано ошибкой и будет исправлено в какой-то будущей версии Python, я не думаю, что такое исправление будет применено к более старым версиям Python.
Q1 : Как я могу сделать реализацию T
, которая является подклассом dict
, для правильного приведения?
Q2 : Если Q1 неу меня нет ответа, сработает ли это, если я создам класс уровня C, который возвращает правильное значение для PyDict_Check
, но не выполняет какую-либо реальную реализацию (а затем делает T
подклассом этого, а также MutableMapping
(я не думаю, что добавление такого неполного dict уровня C будет работать, но я не пробовал), и будет ли этот дурак 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))
, попросить их использоватьзагрузчик, отличный от загрузчика по умолчанию (туда и обратно), например:
yaml = YAML(typ='safe')
data = yaml.load(stream)
, реализует некоторые .to_json()
mв классе T
и сообщите пользователям ruamel.yaml
об этом
, или вернитесь к подклассу dict
и попросите людей сделать
dict([(k, v) for k, v in t.items()])
ничего из этогодействительно дружелюбен и может указывать на то, что невозможно создать диктовочный класс, который не является тривиальным и хорошо взаимодействует со стандартной библиотекой.