用戶: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 的部分轉換的對象;第二個元素為傳入字符串中,其餘部分起始位置的索引。

自定義編碼器 / 解碼器

編輯

讀者可以通過創建新的編碼器 / 解碼器類型,來自定義編碼器或解碼器,只要它實現了上面的所有方法和參數。

參考文獻

編輯