使用生成的字段值反序列化 class
Deserialize class with generated field value
我有一个 class 像:
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)
在这个class中,我不希望用户为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 方式是什么?
In this class, I do not ever want a user to pass in a value for id, I always wish to generated it upon construction.
基于上述期望的结果,我的建议是将 id 定义为(只读)property
。将它定义为 属性 的好处是它不会被视为实例属性,巧合的是它不会通过构造函数接受值;主要缺点是它不会显示在 class 的 __repr__
值(假设我们使用从数据 classes 中获得的生成值)或 dataclasses.asdict
辅助函数。
我还在实施中添加了一些额外的更改(希望更好):
将 class 重新声明为 dataclass,我个人更喜欢这样做,因为它减少了一些样板代码,例如 __init__
构造函数,或者例如,需要定义一个 __eq__
方法(后者通过 ==
检查两个 class 对象是否相等)。 dataclasses 模块还提供了一个有用的 asdict
函数,我们可以在序列化过程中使用它。
通过 json 模块使用内置的 JSON(反)序列化。做出这个决定的部分原因是我个人从未使用过 jsonpickle
模块,而且我对 pickling 的一般工作原理只有初步的了解。我觉得转换 class 对象 to/from JSON 更自然,并且在任何情况下都可能表现得更好。
添加一个 from_json_file
辅助方法,我们可以使用它从本地文件路径加载新的 class 对象。
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()
我有一个 class 像:
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)
在这个class中,我不希望用户为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 方式是什么?
In this class, I do not ever want a user to pass in a value for id, I always wish to generated it upon construction.
基于上述期望的结果,我的建议是将 id 定义为(只读)property
。将它定义为 属性 的好处是它不会被视为实例属性,巧合的是它不会通过构造函数接受值;主要缺点是它不会显示在 class 的 __repr__
值(假设我们使用从数据 classes 中获得的生成值)或 dataclasses.asdict
辅助函数。
我还在实施中添加了一些额外的更改(希望更好):
将 class 重新声明为 dataclass,我个人更喜欢这样做,因为它减少了一些样板代码,例如
__init__
构造函数,或者例如,需要定义一个__eq__
方法(后者通过==
检查两个 class 对象是否相等)。 dataclasses 模块还提供了一个有用的asdict
函数,我们可以在序列化过程中使用它。通过 json 模块使用内置的 JSON(反)序列化。做出这个决定的部分原因是我个人从未使用过
jsonpickle
模块,而且我对 pickling 的一般工作原理只有初步的了解。我觉得转换 class 对象 to/from JSON 更自然,并且在任何情况下都可能表现得更好。添加一个
from_json_file
辅助方法,我们可以使用它从本地文件路径加载新的 class 对象。
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()