用户:Xyy23330121/Python/json


在学习文本流(file object)之后,我们可以简单地把运行时的数据存储到文本文件中。在存储时,选择合适的格式可以让存储事半功倍。有多种格式可以用于存储,比如:

  1. 内置的 marshal 模块[1]
  2. 内置的 pickle 模块[2]

上面的模块及其支持的格式都是 Python 独有的格式,且有时不够安全。JSON 是一个起源自 JavaScript 的、通用的数据交换格式,且有内置的模块为之提供支持。它易于阅读和修改,相比上面的两种格式而言,更适合作为我们存储数据的方式。

备注:如果存储的数据较为简单,使用 csv 格式 有时更为方便。

基本使用

编辑

json 支持的格式

编辑
json 类型转换表
Python 类型 JSON 类型 Python 类型
dict object dict
list array list
tuple
str string str
int number int
float number float
True true True
False false False
None null None

json 支持转换的基本类型包括字符串、整数、浮点数、布尔值以及None。并支持以这些基本类型构成的字典、列表以及字典和/或列表的嵌套。

具体来讲,在默认情况下,它会作出如右表所示的转换。

读者也可以通过设置转换函数。在转换 JSON 时,将不支持的类型转换为支持的类型、并进行输出。

备注:特别的,它还支持将“由 int 或 float 派生的枚举”[3][4][5]转换为[6]对应的“numbers”。由于此教程作者看不出“枚举”有什么作用(字典是完全的上位替代),本教程不会包含枚举的内容。

转换为JSON格式

编辑

json.dump(obj, fp)

编辑

json.dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

对于给出的、支持写入的文本流 fp,将 obj 的内容转换为 json 字符串,并用 fp.write() 写入到文件中。

建议使用刚打开的、模式为 'w' 的文本流,以防文件已有内容或先前已经写入过内容,导致写入后的文件不符合 JSON 标准。对同一个文本流连续使用 json.dump 也会导致写入后的文件不符合 JSON 标准。

json.dumps(obj)

编辑

json.dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

类似 json.dump ,但是转换后的内容会被作为字符串返回。

参数说明

编辑

skipkeys 默认为 False。如果 skipkeys 为 True,则在转换字典时,跳过不支持的键。如果为 False,则在遇到不支持的键时,会引发 TypeError。

ensure_ascii 默认为 True。如果 ensure_ascii 为 True,则会对所有非 ascii 字符进行转义。否则,会原样输出字符。

check_circular 默认为 True。如果为 True 它会检查转换时对容器类型的循环引用,如果有循环引用,则会报错 ValueError: Circular reference detected。如果为 False,则不会进行检查,并会由于循环引用引发 RecursionError (或者导致更糟的情况)。关于循环引用,参见下面的章节。

allow_nan 默认为 True。如果为 True,它会将严格的 JSON 规格外的浮点数 NaN 和无穷大转换为其 JavaScript 等价物。如果为 False,它会报错 ValueError。

indent 用于设置输出的缩进格式。

separators 用于设置输出的分隔符。它是两类分隔符的元组,默认为(', ', ': ')。在使用时,比如,如果想要输出最紧凑的版本,应当输入删去空格的(',', ':');而如果想要输出不那么紧凑的版本,则可以输入形如(',   ', ':   ')的。

default 应当输入一个函数。这个函数应当能输入一个不支持的对象(比如自定义类和自定义实例),并输出一个支持转换为JSON的对象(比如字典)。在转换时,如果遇到某个对象无法被直接转换为 JSON 格式,就会调用 default 来先转换成支持的对象,再转换成 JSON。

因此,default 函数应当输入一个不被 JSON 支持的对象,并输出一个可以被转换为 JSON 格式的对象,或者报错 TypeError。

sort_keys 默认为 False。如果为 True,它会把字典中键的顺序排序后、再按排序进行输出。

cls 参数指定编码时使用的 JSON 编码器对象的类型。默认为 json.JSONEncoder。

indent 与输出格式
编辑

我们可以看以下示例:

import json
obj = {"one":1,"two":2,"all":["one","two"]}
print(json.dumps(obj, indent=None))
print(json.dumps(obj, indent=4))

输出为:

{"one": 1, "two": 2, "all": ["one", "two"]}
{
    "one": 1,
    "two": 2,
    "all": [
        "one",
        "two"
    ]
}

此处缩进设置为 4,即用 4 个空格进行缩进。显然,设置了 indent 的,输出的嵌套层次更清楚。而未设置 indent 的输出则更紧凑、占用更小的空间。

循环引用
编辑

循环引用指的是比如以下的情况:

s = [0, 1]
s[1] = s
print(s[1][1][1][1][1][1][0]) #输出:0
print(s[1][1][1][1][1][1][1]) #输出:[0, [...]]

由于列表的实现方式,制造出这种循环是简单而且可行的。Python 对于循环引用的处理很精妙——当检测到循环时,就输出省略号。

但是,json 格式不支持省略号。在转化时,它会无尽地生成:

[0,[0,[0,[0,[0,[0,[0,...]]]]]]]

并导致错误。所以,在转换成 json 时,一定要注意不要进行循环引用。

从 JSON 格式转换

编辑

json.load(fp)

编辑

json.load(fp, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)

对于给出的、支持读取的文本流 fp,使用 fp.read() 读取其内容,并返回对应的 Python 对象。

建议使用刚打开的、模式为 'r' 的文本流。

json.loads(obj)

编辑

json.loads(s, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)

类似 json.load ,但是输入的并非文本流,而是 JSON 格式的字符串。

参数说明

编辑
object_hook 与 object_pairs_hook
编辑

object_hook 应传入一个函数。该函数输入一个字典,返回一个任意对象。如果传入此项,解码时获得的字典都会被传入到该函数中,并被转换为该函数返回的对象。

object_pairs_hook 与 object_hook 类似,但字典会先被转换为列表,再传入到该函数中。

如果这两项都没有指定,默认为返回解码时获得的原字典。

具体表现可以看以下示例:

import json
def p(obj):
	print(obj, type(obj))
	return "string"

j = json.loads(
     json.dumps({"one":1,"two":2}),
     object_hook = p)
print(j)
j = json.loads(
     json.dumps({"one":1,"two":2}),
     object_pairs_hook = p)
print(j)

输出为:

{'one': 1, 'two': 2} <class 'dict'>
string
[('one', 1), ('two', 2)] <class 'list'>
string
parse_float、parse_int 与 parse_constant
编辑

parse_float 应指定为一个函数。该函数输入 JSON 中表示浮点数的字符串,返回一个对象。我们可以简单传入 decimal.Decimal 使所有表示浮点数的 numbers 被转换为 Decimal。

parse_int 与 parse_float 类似,但是输入的是表示整数的字符串。

parse_constant 与上述两者类似,但是输入的是字符串 '-Infinity'、'Infinity' 或 'NaN'。

cls 参数指定解码时使用的 JSON 解码器对象的类型。默认为 json.JSONDecoder。

编码器和解码器

编辑

创建编码器 / 解码器

编辑

class json.JSONEncoder(*, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None)

class json.JSONDecoder(*, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, strict=True, object_pairs_hook=None)

创建编码器和解码器时,使用的参数与上面“基本使用”章节中,函数的参数是一致的。上面的函数先读取参数 cls 的内容,确定要使用的编码器 / 解码器的类型,然后再用对应的类型构造器生成编码器或解码器的实例,并用实例的方法来进行转换操作。

编码器的方法

编辑

default(o) 输入一个不被支持的对象,并引发 TypeError。如果在创建实例时,使用了 default 参数,则会用参数的内容覆盖该方法。

encode(o) 输入一个 Python 对象,输出其对应的 json 字符串。

iterencode(o) 输入一个 Python 对象,输出一个由 encode(o) 的切片形成的迭代器,使得 "".join([chunk for chunk in json.JSONEncoder().iterencode(o)]) == json.JSONEncoder().encode(o)。该方法用于将过长的字符串分多次写到文件中,以节省内存开支。

解码器的方法

编辑

decode(s) 传入 json 字符串,返回对应的 Python 对象。

raw_decode(s) 传入由 json 字符串开头的字符串,返回一个元组。元组的内容如下所示:

j = json.dumps({1:2,3:4})
print(j, len(j))            #输出:{"1": 2, "3": 4} 16
dec = json.JSONDecoder()
r,i = dec.raw_decode(j+"s")
print(r,type(r),i,type(i))  #输出:{'1': 2, '3': 4} <class 'dict'> 16 <class 'int'>

返回的元组,第一个元素为传入字符串中,json 的部分转换的对象;第二个元素为传入字符串中,其余部分起始位置的索引。

自定义编码器 / 解码器

编辑

读者可以通过创建新的编码器 / 解码器类型,来自定义编码器或解码器,只要它实现了上面的所有方法和参数。

参考文献

编辑