【问题标题】:Deserialize class with generated field value使用生成的字段值反序列化类
【发布时间】:2021-11-20 01:35:33
【问题描述】:

我有这样的课:

class Pathology:
    """
    Represents a pathology, which is initialized with a name and description.
    """

    def __init__(self: str, name: str, description: str):
        self.id = str(uuid.uuid4())
        self.name = name
        self.description = description
        self.phases = []

    def to_json(self):
        return jsonpickle.encode(self, make_refs=False, unpicklable=False)

在这个类中,我不希望用户传递id 的值,我总是希望在构造时生成它。

从 JSON 反序列化时,我希望执行以下操作:

    with open('data/test_case_1.json', 'r') as test_case_1_file:
        test_case_1 = test_case_1_file.read()

    # parse file
    obj = jsonpickle.decode(test_case_1)
    assert pathology == Pathology(**obj)

但是,我遇到了错误TypeError: __init__() got an unexpected keyword argument 'id'

我怀疑这是因为 init 构造函数没有可用的字段id

支持这种行为的pythonic方法是什么?

【问题讨论】:

  • 为什么将unpicklable=False 传递给jsonpickle.encode?如果你不传递这个参数,那么完整的对象将从jsonpickle.decode返回,你不需要自己初始化实例
  • 正在使用不同的设置。我会删除。

标签: python json serialization


【解决方案1】:

在这个类中,我不希望用户为 id 传递一个值,我总是希望在构造时生成它。

基于上述预期结果,我的建议是将 id 定义为(只读)property。将它定义为属性的好处是它不会被视为实例属性,巧合的是它不会通过构造函数接受值;主要缺点是它不会显示在类的 __repr__ 值(假设我们使用从数据类中生成的值)或 dataclasses.asdict 辅助函数中。

我还在实现中添加了一些额外的更改(希望变得更好):

  • 将类重新声明为 dataclass,我个人更喜欢这样,因为它减少了一些样板代码,例如 __init__ 构造函数,或者需要定义 __eq__ 方法(例如,后者通过== 检查两个类对象是否相等。 dataclasses 模块还提供了一个有用的asdict 函数,我们可以在序列化过程中使用它。

  • 通过json 模块使用内置的 JSON(反)序列化。做出这个决定的部分原因是我个人从未使用过jsonpickle 模块,而且我对酸洗的一般工作原理只有初步的了解。我觉得类对象与 JSON 之间的转换更自然,而且在任何情况下都可能表现得更好。

  • 添加一个from_json_file 辅助方法,我们可以使用它从本地文件路径加载新的类对象。

import json
import uuid
from dataclasses import dataclass, asdict, field, fields
from functools import cached_property
from typing import List


@dataclass
class Pathology:
    """
    Represents a pathology, which is initialized with a name and description.
    """
    name: str
    description: str
    phases: List[str] = field(init=False, default_factory=list)

    @cached_property
    def id(self) -> str:
        return str(uuid.uuid4())

    def to_json(self):
        return json.dumps(asdict(self))

    @classmethod
    def from_json_file(cls, file_name: str):

        # A list of only the fields that can be passed in to the constructor.
        # Note: maybe it's worth caching this for repeated runs.
        init_fields = tuple(f.name for f in fields(cls) if f.init)

        if not file_name.endswith('.json'):
            file_name += '.json'

        with open(file_name, 'r') as in_file:
            test_case_1 = json.load(in_file)

        # parse file
        return cls(**{k: v for k, v in test_case_1.items() if k in init_fields})

这是我整理的一些快速代码,以确认一切都按预期进行:

def main():
    p1 = Pathology('my-name', 'my test description.')
    print('P1:', p1)
    p_id = p1.id
    print('P1 -> id:', p_id)
    assert p1.id == p_id, 'expected id value to be cached'

    print('Serialized JSON:', p1.to_json())

    # Save JSON to file
    with open('my_file.json', 'w') as out_file:
        out_file.write(p1.to_json())

    # De-serialize object from file
    p2 = Pathology.from_json_file('my_file')

    print('P2:', p2)

    # assert both objects are same
    assert p2 == p1

    # IDs should be unique, since it's automatically generated each time (we
    # don't pass in an ID to the constructor or store it in JSON file)
    assert p1.id != p2.id, 'expected IDs to be unique'


if __name__ == '__main__':
    main()

【讨论】:

  • 这太不可思议了,非常感谢您抽出时间来帮助我。这很有意义,我不知道数据类。谢谢!
  • @Gabe 没问题,很乐意提供帮助。是的,Python 中的数据类真的很酷。另外,我意识到我忘了添加上面的 phases 属性,所以我只是在示例中添加了它。
猜你喜欢
  • 2018-03-23
  • 2020-04-08
  • 1970-01-01
  • 2017-06-12
  • 1970-01-01
  • 2011-02-15
  • 1970-01-01
  • 2018-06-20
  • 1970-01-01
相关资源
最近更新 更多