用户:Xyy23330121/Python/json
在学习文本流(file object)之后,我们可以简单地把运行时的数据存储到文本文件中。在存储时,选择合适的格式可以让存储事半功倍。有多种格式可以用于存储,比如:
上面的模块及其支持的格式都是 Python 独有的格式,且有时不够安全。JSON 是一个起源自 JavaScript 的、通用的数据交换格式,且有内置的模块为之提供支持。它易于阅读和修改,相比上面的两种格式而言,更适合作为我们存储数据的方式。
备注:如果存储的数据较为简单,使用 csv 格式 有时更为方便。
基本使用
编辑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
编辑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 的部分转换的对象;第二个元素为传入字符串中,其余部分起始位置的索引。
自定义编码器 / 解码器
编辑读者可以通过创建新的编码器 / 解码器类型,来自定义编码器或解码器,只要它实现了上面的所有方法和参数。
参考文献
编辑- ↑ https://docs.python.org/zh-cn/3.13/library/marshal.html#module-marshal
- ↑ https://docs.python.org/zh-cn/3.13/library/pickle.html#module-pickle
- ↑ 枚举 - Enum https://docs.python.org/zh-cn/3.12/library/enum.html
- ↑ int派生的枚举 - IntEnum https://docs.python.org/zh-cn/3.12/library/enum.html#enum.IntEnum
- ↑ int派生的枚举 - IntFlag https://docs.python.org/zh-cn/3.12/library/enum.html#enum.IntFlag
- ↑ https://docs.python.org/zh-cn/3.12/library/json.html#json.JSONEncoder