V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
mimzy
V2EX  ›  Python

Python 如何高效地将 JSON 反序列化为对象

  •  
  •   mimzy ·
    mookrs · 2021-03-22 11:17:24 +08:00 · 4434 次点击
    这是一个创建于 1371 天前的主题,其中的信息可能已经有所发展或是发生改变。

    现在通过 API 获取的 JSON,我一般先用 HTTPX.json() 方法转换为 Python 内置数据结构,然后用 Pydanticparse_obj_as() 转化为对象(因为 parse_obj_as() 可以很方便地转换 list ),便于使用 type hints 。

    目前的链路是这样的:str -> dict/list -> Pydantic object,有点冗余,而且当 JSON 体积大到一定程度的时候,第二步比第一步慢一个数量级,已经无法接受。所以想了解下,有没有其他高效地将 JSON 反序列化为对象的方法?

    28 条回复    2021-03-23 15:41:59 +08:00
    ungrown
        1
    ungrown  
       2021-03-22 11:37:23 +08:00
    可以借助 JSONPath 之类的东西做个封装对象
    ch2
        2
    ch2  
       2021-03-22 11:41:31 +08:00
    自己写一个 converter
    hahastudio
        3
    hahastudio  
       2021-03-22 11:47:49 +08:00   ❤️ 1
    knightdf
        4
    knightdf  
       2021-03-22 11:48:35 +08:00   ❤️ 1
    ujson,比内置 json 快
    so1n
        5
    so1n  
       2021-03-22 11:56:58 +08:00
    第一步可以用 ujson ojson 代替 第二步目前还是 pydantic 最快
    mimzy
        6
    mimzy  
    OP
       2021-03-22 12:05:04 +08:00
    @hahastudio #3 昨天看了 orjson,文档中 https://github.com/ijl/orjson#deserialize 提到 loads() deserializes JSON to Python objects. It deserializes to dict, list, int, float, str, bool, and None objects. 我希望能像 Pydantic 一样返回一个对象而不是内置数据结构,就感觉不太符合…
    abersheeran
        7
    abersheeran  
       2021-03-22 12:18:35 +08:00   ❤️ 1
    有一个问题,你需要进行默认值的填充和参数校验吗?如果你不需要这些。

    写一个类似于这里面的 https://github.com/abersheeran/index.py/blob/master/indexpy/utils.py#L50 类就行了。基本思路是使用三个魔术方法来自定义 obj.attr 的行为。比起其他需要校验、填充默认值的玩意,快不止一个数量级,因为这里压根就没有 COPY 的损耗。
    no1xsyzy
        8
    no1xsyzy  
       2021-03-22 12:34:05 +08:00
    那 type hint 就是继承之后再写咯……
    Kobayashi
        9
    Kobayashi  
       2021-03-22 12:35:29 +08:00 via Android
    第二步要做类型校验、转换,这能一样吗?
    berserk
        10
    berserk  
       2021-03-22 12:36:46 +08:00
    import json
    json.loads(s)不行吗
    qlhai
        11
    qlhai  
       2021-03-22 12:51:02 +08:00
    你都已经用 Python 了,还在乎这点时间吗
    Vegetable
        12
    Vegetable  
       2021-03-22 12:55:08 +08:00
    parse_obj_as 是要验证的,如果数据是可信的,可以跳过验证,根据官方文档的说法:

    construct() is generally around 30x faster than creating a model with full validation
    mimzy
        13
    mimzy  
    OP
       2021-03-22 12:57:01 +08:00
    @abersheeran #7 参数校验其实不需要,这个场景下我获取数据,格式基本可以保证。问题在于它传给我的结构嵌套可能比较多,所以希望用自定义对象的方式访问,这样每一层我能知道对象拥有的属性和类型,而不是从字典里一层一层访用 key 访问…你这个方式我学习一下,不过看起来没办法让 IDE 给我提示…
    mimzy
        14
    mimzy  
    OP
       2021-03-22 13:03:09 +08:00
    @Vegetable #12 是的,昨天也简单看了下 construct(),但是看文档 https://pydantic-docs.helpmanual.io/usage/models/#creating-models-without-validation 它接收的是一堆参数,这就导致我要将返回的 List[Dict[str, Any]] 这样的东西循环一次,然后将 **dict 作为参数,这么构造完已经 30s 了,当然可能我用得不对…

    如果 construct() 和 parse_raw() 能结合的话,我估计效率应该会提升。
    mimzy
        15
    mimzy  
    OP
       2021-03-22 13:07:03 +08:00
    @berserk #10 .json() 这一步其实就做完了这件事,我想要的是一个我能确定内部结构的对象,类型于 Go 的 structs 。不过这么说的话,突然想起来好像用 TypedDict 注解一下也行…
    Contextualist
        16
    Contextualist  
       2021-03-22 13:08:35 +08:00   ❤️ 1
    不需要参数校验的话,可以试试 attrs + cattrs 。我自己在用这个方案,但是是用来反序列化配置文件的,所以没有考虑性能。另外 pydantic 我没用过,没有发言权。据 pydantic 作者自己说估计 attrs 能更快: https://github.com/samuelcolvin/pydantic/issues/1459#issuecomment-622045131
    mimzy
        17
    mimzy  
    OP
       2021-03-22 13:26:33 +08:00
    @mimzy #15 目前的结论:在不需要参数校验的前提下,因为我只是需要掌握对象的内部结构和类型,TypedDict 基本解决了我的需求…
    abersheeran
        18
    abersheeran  
       2021-03-22 13:37:58 +08:00
    @mimzy 如果你只是需要代码提示,TypedDict 永远的神……https://github.com/abersheeran/baize/blob/master/baize/typing.py#L66
    mimzy
        19
    mimzy  
    OP
       2021-03-22 14:08:44 +08:00
    @abersheeran #18 我之前就是觉得 TypedDict 只用来做类型注解,然后要写一堆,太浪费了,所以上了 Pydantic 。不过看来有时候返璞归真也挺好…
    abersheeran
        20
    abersheeran  
       2021-03-22 14:40:24 +08:00
    @mimzy 另外一提,如果你没有用 pydantic 提供的 Cython 编译后的版本,其实它的速度和 attrs 之流差不多甚至慢一些。有时候觉得 pydantic 、fastapi 这些家伙的宣传挺可耻的……虚假宣传
    abersheeran
        21
    abersheeran  
       2021-03-22 14:40:38 +08:00
    @mimzy 虽然我也是 pydantic 的重度使用者。
    SystemLight
        22
    SystemLight  
       2021-03-22 14:44:58 +08:00
    我感觉 marshmallow 这个库还挺好用的 https://github.com/marshmallow-code/marshmallow
    Wicked
        23
    Wicked  
       2021-03-22 15:46:58 +08:00 via iPhone
    你要多快?找个 rapid json 之类的库自己封装一下?
    mimzy
        24
    mimzy  
    OP
       2021-03-22 18:35:21 +08:00
    @abersheeran #20 虚假宣传哈哈哈~确实,像我现在用 FastAPI 有时候觉得还挺怀念 Django 的,啥速度不速度的,再快也超不过 Starlette 天花板,现成的工具又少…
    abersheeran
        25
    abersheeran  
       2021-03-22 18:40:52 +08:00
    @mimzy fastapi 速度还没 aiohttp 快呢。它的性能全靠吹。
    misaka19000
        26
    misaka19000  
       2021-03-22 22:37:45 +08:00
    我们用的 ujson,用了好几年了,效果挺好
    wangyzj
        27
    wangyzj  
       2021-03-23 09:27:29 +08:00
    大到一定体积是多大?
    williamfzc
        28
    williamfzc  
       2021-03-23 15:41:59 +08:00
    一样的问题,我们的需求是 xml -> json -> pydantic object,小文件完全 ok,文件变大之后问题会越来越明显,无论是速度跟内存占用都很爆表。前面的文本处理效率还能接受

    之所以要用 python 主要是一些包调起来方便,现在已经准备慢慢用 java 重写了。。

    @wangyzj 20M 左右就已经慢到不能接受了~
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   986 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 23:05 · PVG 07:05 · LAX 15:05 · JFK 18:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.