gpt4 book ai didi

python - 了解 JSONEncoder 的子类化

转载 作者:太空狗 更新时间:2023-10-29 17:51:41 27 4
gpt4 key购买 nike

我正在尝试子类化 json.JSONEncoder 这样命名的元组(使用新的 Python 3.6+ 语法定义,但它可能仍然适用于 collections.namedtuple 的输出>) 被序列化为 JSON 对象,其中元组字段对应于对象键。

例如:

from typing import NamedTuple

class MyModel(NamedTuple):
foo:int
bar:str = "Hello, World!"

a = MyModel(123) # Expected JSON: {"foo": 123, "bar": "Hello, World!"}
b = MyModel(456, "xyzzy") # Expected JSON: {"foo": 456, "bar": "xyzzy"}

我的理解是我将 json.JSONEncoder 子类化并重写它的 default 方法来为新类型提供序列化。然后类(class)的其他人将在递归等方面做正确的事情。因此我想出了以下内容:

class MyJSONEncoder(json.JSONEncoder):
def default(self, o):
to_encode = None

if isinstance(o, tuple) and hasattr(o, "_asdict"):
# Dictionary representation of a named tuple
to_encode = o._asdict()

if isinstance(o, datetime):
# String representation of a datetime
to_encode = o.strftime("%Y-%m-%dT%H:%M:%S")

# Why not super().default(to_encode or o)??
return to_encode or o

这在它尝试序列化(即,作为 json.dumpscls 参数)datetime 值时起作用——到至少部分地证明了我的假设——但从未命中命名元组的检查,并且它默认将其序列化为元组(即,JSON 数组)。奇怪的是,我假设我应该在转换后的对象上调用父类(super class)的 default 方法,但是当它尝试序列化 datetime 时会引发异常:“TypeError: “str”类型的对象不是 JSON 可序列化的”,坦率地说,这毫无意义!

如果我使命名的元组类型检查更具体(例如,isinstance(o, MyModel)),我会得到相同的行为。但是,我确实发现,如果我还重写 encode 方法,我可以几乎获得我正在寻找的行为,方法是将命名的元组检查移动到那里:

class AlmostWorkingJSONEncoder(json.JSONEncoder):
def default(self, o):
to_encode = None

if isinstance(o, datetime):
# String representation of a datetime
to_encode = o.strftime("%Y-%m-%dT%H:%M:%S")

return to_encode or o

def encode(self, o):
to_encode = None

if isinstance(o, tuple) and hasattr(o, "_asdict"):
# Dictionary representation of a named tuple
to_encode = o._asdict()

# Here we *do* need to call the superclass' encode method??
return super().encode(to_encode or o)

这有效,但不是递归的:它成功地将顶级命名元组序列化为 JSON 对象,根据我的要求,但是该命名元组中存在的任何命名元组都将使用默认行为(JSON 数组)序列化。如果我将命名元组类型检查放在 default encode 方法中,这也是行为。

文档暗示只有 default 方法应该在子类中被改变。例如,我假设在 AlmostWorkingJSONEncoder 中覆盖 encode 会导致它在进行分 block 编码时中断。然而,到目前为止,没有多少 hackery 产生了我想要的东西(或者预期会发生,因为文档很少)。

我的误解在哪里?


EDIT 阅读 json.JSONEncoder 的代码解释了为什么 default 方法在向它传递字符串时会引发类型错误:它不是从文档中清楚(至少对我而言),但是 default 方法旨在将某些不受支持的类型的值转换为可序列化的类型,然后返回该类型;如果不受支持的类型未在您的覆盖方法中转换为任何内容,那么您应该在末尾调用 super().default(o) 以引发类型错误。所以像这样:

class SubJSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Foo):
return SerialisableFoo(o)

if isinstance(o, Bar):
return SerialisableBar(o)

# etc., etc.

# No more serialisation options available, so raise a type error
super().default(o)

我认为我遇到的问题是 default 方法仅在无法匹配任何支持的类型时由编码器调用。命名元组仍然是一个元组——支持的——所以它在委托(delegate)给我重写的 default 方法之前首先匹配它。在 Python 2.7 中,执行此匹配的函数是 JSONEncoder 对象的一部分,但在 Python 3 中,它们似乎已移到模块命名空间之外(因此,用户空间无法访问) .因此,我认为如果不对您自己的实现进行大量重写和硬耦合,就不可能将 JSONEncoder 子类化以以通用方式序列化命名元组:(

编辑 2 我将其作为 bug 提交。

最佳答案

坏消息

嗯,我刚刚查看了 the source,似乎没有公共(public) Hook 来控制列表或元组实例的序列化方式。

更坏的消息

一种不安全的方法是猴子修补 _make_iterencode() 私有(private)函数。

好消息

另一种方法是预处理输入,将命名元组转换为字典:

from json import JSONEncoder
from typing import NamedTuple
from datetime import datetime

def preprocess(tree):
if isinstance(tree, dict):
return {k: preprocess(v) for k, v in tree.items()}
if isinstance(tree, tuple) and hasattr(tree, '_asdict'):
return preprocess(tree._asdict())
if isinstance(tree, (list, tuple)):
return list(map(preprocess, tree))
return tree

class MD(JSONEncoder):

def default(self, o):
if isinstance(o, datetime):
return o.strftime("%Y-%m-%dT%H:%M:%S")
return super().default(o)

应用于这些模型:

class MyModel(NamedTuple):
foo: int
bar: str = "Hello, World!"

class LayeredModel(NamedTuple):
baz: MyModel
fob: list

a = MyModel(123)
b = MyModel(456, "xyzzy")
c = LayeredModel(a, [a, b])
outer = dict(a=a, b=b, c=c, d=datetime.now(), e=10)
print(MD().encode(preprocess(outer)))

给出这个输出:

{"a": {"foo": 123, "bar": "Hello, World!"},
"b": {"foo": 456, "bar": "xyzzy"},
"c": {"baz": {"foo": 123, "bar": "Hello, World!"},
"fob": [{"foo": 123, "bar": "Hello, World!"},
{"foo": 456, "bar": "xyzzy"}]},
"d": "2019-11-03T10:46:17",
"e": 10}

关于python - 了解 JSONEncoder 的子类化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43913256/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com