Это довольно сложно, поскольку namedtuple()
это фабрика, возвращающая новый тип, производный от tuple
. Один из подходов заключается в том, чтобы ваш класс также унаследовал от UserDict.DictMixin
, но tuple.__getitem__
уже определен и ожидает целое число, обозначающее позицию элемента, а не имя его атрибута:
>>> f = foobar('a', 1)
>>> f[0]
'a'
По сути, namedtuple не подходит для JSON, поскольку на самом деле это настраиваемый тип, имена ключей которого фиксируются как часть определения типа , в отличие от словаря, в котором имена ключей хранятся внутри экземпляра. Это предохраняет вас от «кругового обхода» именованного кортежа, например, вы не можете декодировать словарь обратно в именованный кортеж без какой-либо другой части информации, такой как маркер типа для конкретного приложения в dict {'a': 1, '#_type': 'foobar'}
, что немного взломано.
Это не идеально, но если вам нужно только кодировать именованные кортежи в словари, другой подход состоит в том, чтобы расширить или изменить кодировщик JSON для особых случаев этих типов. Вот пример подкласса Python json.JSONEncoder
. Это решает проблему обеспечения правильного преобразования вложенных именованных кортежей в словари:
from collections import namedtuple
from json import JSONEncoder
class MyEncoder(JSONEncoder):
def _iterencode(self, obj, markers=None):
if isinstance(obj, tuple) and hasattr(obj, '_asdict'):
gen = self._iterencode_dict(obj._asdict(), markers)
else:
gen = JSONEncoder._iterencode(self, obj, markers)
for chunk in gen:
yield chunk
class foobar(namedtuple('f', 'foo, bar')):
pass
enc = MyEncoder()
for obj in (foobar('a', 1), ('a', 1), {'outer': foobar('x', 'y')}):
print enc.encode(obj)
{"foo": "a", "bar": 1}
["a", 1]
{"outer": {"foo": "x", "bar": "y"}}