Faster JSON - orjson | Python 主题月

7,783 阅读4分钟

本文正在参加「Python主题月」,详情查看 活动链接

微信公众号搜索【程序媛小庄】,Rest cannot be enjoyed by lazy people~

前言

JSON使用的越多就越可能遇到JSON编码或者解码的瓶颈,Python的内置json库虽然也很好用但是也有很多其他更快的JSON库可以使用,但是具体选择哪一个或者哪一种需要根据具体情况,没有一种标准的规则衡量哪一种JSON最好或者最快,因为不同的项目有不同的需求,有的注重安全有的注重速度更快,本文介绍更快的json-orjson

orjson简介

orjson是一个更加快速的Python JSON库,通过基准测试发现比标准json库和rapidjson的速度更快,可以序列化dataclass datetime numpy UUIDjson或者rapidjson相比orjson序列化得到的结果是bytes类型而不是json格式的str类型,序列化时不会将unicode转换成ASCII,需要注意的是,orjson不提供dump/load方法实现对file-like对象进行序列化和反序列化。更多关于orjson的使用请参考官方文档

json rapidson和orjson速度对比

下面是json rapidjson orjson进行的基准测试代码以及结果。

import json
import orjson
import rapidjson
import time

m = {
    "timestamp": 1556283673.1523004,
    "task_uuid": "0ed1a1c3-050c-4fb9-9426-a7e72d0acfc7",
    "task_level": [1, 2, 1],
    "action_status": "started",
    "action_type": "main",
    "key": "value",
    "another_key": 123,
    "and_another": ["a", "b"],
}


def benchmark(module_name, dumps):
    start = time.time()
    for i in range(100000):
        dumps(m)
    print(module_name, time.time() - start)


benchmark('json', json.dumps)
benchmark('rapidjson', rapidjson.dumps)
benchmark("orjson", lambda s: str(orjson.dumps(s), "utf-8"))  # orjson只能输出bytes

并且结果如下,orjson即使需要额外的Unicode解码也是最快的,但是并不代表博主一定推荐您使用orjson,需要根据实际情况进行选择哦~:

json 1.1019978523254395
rapidjson 0.25800156593322754
orjson 0.0859987735748291

orjson基本使用

orjson安装

安装命令非常简单:pip install orjson

需要注意的是,在Linux环境中使用pip命令安装时pip版本需要大于19.3,因此在安装orjson时可以先更新pip版本。

orjson基本使用

序列化-dumps

和Python标准JSON库json相比最大的不同在于orjson.dumps返回的结果的是bytesjson.dumps返回的结果是strsort_keys参数被option=orjson.OPT_SORT_KEYS代替,indent参数被option=orjson.OPT_INDENT_2代替并且不支持其他indent等级。

def dumps(
    __obj: Any,
    default: Optional[Callable[[Any], Any]] = ...,
    option: Optional[int] = ...,
) -> bytes: ...
    
# 序列化
import orjson

m = {
    "timestamp": 1556283673.1523004,
    "task_uuid": "0ed1a1c3-050c-4fb9-9426-a7e72d0acfc7",
    "task_level": [1, 2, 1],
    "action_status": "started",
    "action_type": "哈哈",
    "key": "value",
    "another_key": 123,
    "and_another": ["a", "b"],
}


res = orjson.dumps(m)
print(res)  
# b'{"timestamp":1556283673.1523004,"task_uuid":"0ed1a1c3-050c-4fb9-9426-a7e72d0acfc7","task_level":[1,2,1],"action_status":"started","action_type":"\xe5\x93\x88\xe5\x93\x88","key":"value","another_key":123,"and_another":["a","b"]}'

反序列化-loads

loads方法将bytes类型的JSON格式数据反序列化称为Python示例对象。

print(orjson.loads(res))

float int str类型的序列化和反序列化

float

orjson在序列化和反序列化双精度浮点数时不会损失精度,在json rapidjson中同样不会损失精度。orjson.dumps对序列化Nan,Infinity,-Infinity不兼容,会得到null的结果,但是json和rapidjson支持。

>>> import orjson, ujson, rapidjson, json
>>> orjson.dumps([float("NaN"), float("Infinity"), float("-Infinity")])
b'[null,null,null]'
>>> rapidjson.dumps([float("NaN"), float("Infinity"), float("-Infinity")])
'[NaN,Infinity,-Infinity]'
>>> json.dumps([float("NaN"), float("Infinity"), float("-Infinity")])
'[NaN, Infinity, -Infinity]'

int

orjson默认可以序列化和反序列化64-bits的证书,支持的范围是带符号的最小值(-9223372036854775807)到无符号的最大值(18446744073709551615),但是在某些场景下只支持53-bits的证书,比如web浏览器,对于不兼容的部分,dumps方法会抛出JSONEncodeError异常。

>>> import orjson
>>> orjson.dumps(9007199254740992)
b'9007199254740992'
>>> orjson.dumps(9007199254740992, option=orjson.OPT_STRICT_INTEGER)
JSONEncodeError: Integer exceeds 53-bit range
>>> orjson.dumps(-9007199254740992, option=orjson.OPT_STRICT_INTEGER)
JSONEncodeError: Integer exceeds 53-bit range

str

orjson和UTF8具有严格的一致性,比Python的标准库json更为严格,json在序列化和反序列化时使用UTF-16进行代理,但是这是不可用的UTF8。如果orjson.dumps的参数传了一个不是UTF8的字符那么会抛出orjson.JSONEncodeError异常,如果orjson.loads()的参数收到了不可用的UTF8字符也会抛出同样的异常。

orjson和rapidjson和Python标准库json相比来说,对于不符合规则的输入始终都会抛出相应的异常。

为了程序的健壮性,可以在反序列化时先将bytes类型编码成成UTF8格式。

>>> import orjson
>>> orjson.loads(b'"\xed\xa0\x80"')
JSONDecodeError: str is not valid UTF-8: surrogates not allowed
>>> orjson.loads(b'"\xed\xa0\x80"'.decode("utf-8", "replace"))

结语

文章首发于微信公众号程序媛小庄,同步于掘金

码字不易,转载请说明出处,走过路过的小伙伴们伸出可爱的小指头点个赞再走吧(╹▽╹)

image.png