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

2021-11-17  本文已影响0人  北国的秋天

一个快速正确的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]]}

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

序列化

参数说明

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

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

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

上一篇下一篇

猜你喜欢

热点阅读