一个更快、支持对更多数据类型进行序列化、反序列化的Python库

638 阅读7分钟

一个快速正确的Python JSON库,它本身就支持数据类、日期、时间、numpy 数据类型。

最近,在开发中遇到需要对大量复杂JSON进行序列化和反序列化操作场景,但发现内置的json库速度非常慢,想使用第三方库替代它,于是发现了上面提到的 orjson 库。

下面,让我们看一下orjson 与其他Python json库相比的优缺点。

  • 序列化数据类(dataclasses.dataclas)实例的速度是其他库的40-50倍。

  • 将datetime、date和time实例序列化为RFC 3339格式,例如,“1970-01-01T00:00:00+00:00”。

  • 序列化 numpy.ndarray的实例化速度是其他库的0.3倍。

  • 序列化为字节而不是字符串,也就是说,不是临时替换。

  • 序列化字符串而不将unicode转义为ASCII,例如,"好"而不是"\u597d"。

  • 序列化浮点型数据的速度是其他库的10倍,反序列化的速度是其他库的两倍。

  • 具有严格的UTF-8和JSON格式一致性,比标准库更正确。

  • 不额外提供用于读取/写入类文件对象的load()或dump()函数。

安装

使用pip 直接安装,如下:

pip install orjson

简单示例

下面是一个含序列化、反序列化的示例:

data = {"emoji": "", "integer": 9527, "float": 9.527, "boolean": False,
         "list": ["", 9527, 9.527, False], 
         "dict": {"key1": "value1", "key2": "value2"},
         "chinese": "您好", "japanese": "こんにちは", 
         "created_at": datetime.datetime(1970, 1, 1),
         "status": "🆗", "payload": numpy.array([[1, 2], [3, 4]])}
 # 序列化 data
 data_dumps = orjson.dumps(data, option=orjson.OPT_NAIVE_UTC | orjson.OPT_SERIALIZE_NUMPY)
 print(data_dumps)
 
 # 反序列化 data_dumps
 data_loads = orjson.loads(data_dumps)
 print(data_loads)
 
 # 执行上述代码,输出结果为:
 b'{"emoji":"\xf0\x9f\x98\x82","integer":9527,"float":9.527,"boolean":false,"list":["\xf0\x9f\x98\x82",9527,9.527,false],"dict":{"key1":"value1","key2":"value2"},"chinese":"\xe6\x82\xa8\xe5\xa5\xbd","japanese":"\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81\xa1\xe3\x81\xaf","created_at":"1970-01-01T00:00:00+00:00","status":"\xc3\xb0\xc5\xb8\xe2\x80\xa0\xe2\x80\x94","payload":[[1,2],[3,4]]}'
 
 {'emoji': '', 'integer': 9527, 'float': 9.527, 'boolean': False, 'list': ['', 9527, 9.527, False], 'dict': {'key1': 'value1', 'key2': 'value2'}, 'chinese': '您好', 'japanese': 'こんにちは', 'created_at': '1970-01-01T00:00:00+00:00', 'status': '🆗', 'payload': [[1, 2], [3, 4]]}

因头条显示问题,部分数据显示不出,特附上述代码截图,如下:

点击免费领取软件测试资料 100+ 名企测试内推资源倾情分享

序列化

参数说明

对于序列化,可以指定以下两个输入参数:

  • *default:要序列化子类或任意类型,可指定default作为返回受支持类型的可调用对象。 此外,您可以通过引发诸如TypeError之类的异常来强制执行规则以处理不受支持的日期类型。

  • option:若要修改数据序列化的方式,请指定选项。在orjson 中,每个选项都是一个整数常量。要指定多个选项,将它们一起屏蔽,例如: option=orjson.OPT_STRICT_INTEGER | orjson.OPT_NAIVE_UTC 。

序列化 default 参数

当输入包含不支持的Decimal数据类型时,则会引发错误。

import decimal 
 orjson.dumps(decimal.Decimal("3.141592653"))

运行上述代码,将得到以下输出:

TypeError: Type is not JSON serializable: decimal.Decimal

为了使得 orjson 序列化支持Decimal数据类型,我们可以创建一个 callable 函数或lambda 表达式并将其作为default参数传递,如下。

def default(obj):
     if isinstance(obj, decimal.Decimal):
         return str(obj)
     raise TypeError
     
 data = orjson.dumps(decimal.Decimal("0.0842389659712649442845"), default=default)
 
 # 执行上述代码,输出结果为:
 b'"0.0842389659712649442845"'

序列化 option 参数

  • OPT_APPEND_NEWLINE :在输出中附加\n 。

  • OPT_INDENT_2 :缩进两个空格的打印输出。

  • OPT_NAIVE_UTC :将没有tzinfo的 datetime.datetime对象序列化为UTC。 这对设置了tzinfo datetime.datetime对象没有影响。

  • OPT_NON_STR_KEYS :序列化字符串以外类型的字典键。它允许dict的键为字符串、整型、浮点型、布尔型、None、时间(datetime.time、datetime.datetime)、日期(datetime.date)、枚举(enum.Enum)、uuid.UUID。

  • OPT_OMIT_MICROSECONDS :不要序列化datetime.datetime或datetime.time实例上的微秒级数据。

  • OPT_PASSTHROUGH_DATACLASS: 支持序列化数据类(dataclasses.dataclas)实例时,通过default参数 定制化输出内容。

  • OPT_PASSTHROUGH_DATETIME: 序列化datetime.datetime, datetime.date, and datetime.time实例时,通过default参数自定义格式。

  • OPT_SERIALIZE_NUMPY :序列化numpy.ndarray实例。

  • OPT_SORT_KEYS :按排序顺序对字典键进行序列化。 默认值是未指定顺序进行序列化。

  • OPT_STRICT_INTEGER :对整数(而不是标准的64位)强制执行53位限制。

代码示例,如下:

import orjson, datetime, uuid
 # 序列化 dict键为uuid.UUID的数据
 orjson.dumps(
         {uuid.UUID("9527d115-6ff8-9aj1-n3b1-128sj384392135reiop"): [1, 2]},
         option=orjson.OPT_NON_STR_KEYS,
     )
 # 序列化 dict键为datetime.datetime的数据
 orjson.dumps(
         {datetime.datetime(2021, 1, 1, 0, 0, 0): [1, 2]},
         option=orjson.OPT_NON_STR_KEYS | orjson.OPT_NAIVE_UTC,
     )
 # 不序列化微妙字段
 orjson.dumps(
         datetime.datetime(2021, 1, 1, 0, 0, 0, 1),
         option=orjson.OPT_OMIT_MICROSECONDS,
     )
 # 序列化 dataclasses 数据时,通过default参数定制化输出内容
 import dataclasses 数据
 
 @dataclasses.dataclass
 class User:
     id: str
     name: str
     password: str
 
 def default(obj):
     if isinstance(obj, User):
         return { "name": obj.name,"password":obj.password}
     raise TypeError
 
 orjson.dumps(
         User("123", "Tom", "123456"),
         option=orjson.OPT_PASSTHROUGH_DATACLASS,
         default=default,
     )
 
 #  序列化datetime.datetime实例时,通过default参数自定义格式。
 def default(obj):
     if isinstance(obj, datetime.datetime):
         return obj.strftime("%a, %d %b %Y %H:%M:%S GMT")
     raise TypeError
     
  orjson.dumps(
         {"created_at": datetime.datetime(2021, 1, 1)},
         option=orjson.OPT_PASSTHROUGH_DATETIME,
         default=default,
     )

反序列化

loads() 接受bytes、bytearray、memoryview、STR输入。它反序列化为dict、list、int、float、str、bool和None对象。如果输入作为memoryview、bytearray或bytes对象存在,建议直接传递这些,而不是创建一个不必要的str对象。这样可以降低内存使用量和延迟,输入必须是有效的UTF-8。

文件读/写

通常,我们可以通过write() 函数将序列化后返回的字节数据保存到文件中。 但是,需要在mode参数中包含b模式 。

data = {"emoji": "", "integer": 9527, "float": 9.527, "boolean": False,
         "list": ["", 9527, 9.527, False], 
         "dict": {"key1": "value1", "key2": "value2"},
         "chinese": "您好", "japanese": "こんにちは", 
         "created_at": datetime.datetime(1970, 1, 1),
         "status": "🆗", "payload": numpy.array([[1, 2], [3, 4]])
        }
 
 with open("example.json", "wb") as f:
     f.write(orjson.dumps(data, option=orjson.OPT_NAIVE_UTC | orjson.OPT_SERIALIZE_NUMPY))

因头条显示问题,部分数据显示不出,特附上述代码截图,如下:

生成的example.json 内容如下:

同样,从文件中读取数据也很简单,如下所示:

with open("example.json", "rb") as f:
     json_data = orjson.loads(f.read())
 
 print(json_data)
 
 # 执行上述代码,输出结果为
 {'emoji': '', 'integer': 9527, 'float': 9.527, 'boolean': False, 'list': ['', 9527, 9.527, False], 'dict': {'key1': 'value1', 'key2': 'value2'}, 'chinese': '您好', 'japanese': 'こんにちは', 'created_at': '1970-01-01T00:00:00+00:00', 'status': '🆗', 'payload': [[1, 2], [3, 4]]}

最后,性能测试

我们通过简单的测试,来比较一下json、ujson、orjson三者的序列化性能,其中json库为Pthon内置库,ujson 库是用 C实现的,orjson 库是用Rust 实现的。

# -*- coding: utf-8 -*-
 import json
 import random
 import ujson
 import orjson
 import time
 
 
 def cost_time(func):
     def inner(*args, **kwargs):
         start_time = time.time()
         result = func(*args, **kwargs)
         stop_time = time.time()
         print("{0} 耗时:{1}".format(func.__name__, stop_time - start_time))
         return result
 
     return inner
 
 @cost_time
 def json_dumps(obj):
     return json.dumps(obj)
 
 @cost_time
 def ujson_dumps(obj):
     return ujson.dumps(obj)
 
 @cost_time
 def orjson_dumps(obj):
     return orjson.dumps(obj)
 
 if __name__ == '__main__':
 
     test = {}
     for i in range(1, 2000000):
         test[str(i)] = ''.join(random.sample(
             ['z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 
              'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e','d', 'c', 'b', 'a'], 10))
 
     json_dumps(test)
     ujson_dumps(test)
     orjson_dumps(test)

我们可以看到,同样是序列含200万k-v 的dict对象,使用orjson的处理性能远比其他两个库效率高的多。

json_dumps 耗时:1.1578669548034668
 ujson_dumps 耗时:0.45979905128479004
 orjson_dumps 耗时:0.09074163436889648

看了这篇内容后,坚信以下两件事,也会对你的自我提升有一定的帮助:

1、点赞,让更多人能看到,同时你的认可也会鼓励我创作更多优质内容。

2、要让自己变得更强:想想,假如你是要在测试这个行业长期做下去,你的工作经验和测试技术是绝对不够的,你需要提升,你需要丰富你的技术栈!还等什么!

点击免费领取软件测试资料 100+ 名企测试内推资源倾情分享

这一些资料,对做【软件测试】的朋友而言应该是较为完整了,这类学习资料也陪伴我走过了最艰难的路程,希望也可以帮助到你!万事要尽早,尤其是技术行业,一定要提升技术功底。