我正在使用英雄联盟API中的数据学习Python、JSON和数据类。使用dacite,我创建了允许使用以下语法:champs.data['Ahri']['key']访问数据的父类和子类。但是,我想知道是否有一种方法可以创建一个类,将键作为字段返回,这样就可以使用以下语法访问数据:champs.data.Ahri.key。
以下是工作代码:
from dataclasses import dataclass
from dacite import from_dict
j1 = {'type': 'champion',
'data': {'Aatrox': {'id': 'Aatrox', 'key': '266', 'name': 'Aatrox'},
'Ahri': {'id': 'Ahri', 'key': '103', 'name': 'Ahri'}}}
@dataclass
class C:
type: str
data: dict
@dataclass
class P:
type: str
data: dict
champs = from_dict(data_class=P, data=j1)
champs.data['Ahri']['key']发布于 2022-03-02 22:04:11
您可能能得到的最接近的(至少以一种足够安全的方式)是使用@JonSG建议 (使用champs.data['Ahri'].key )。
下面是一个使用dataclass-wizard的简单示例。它不会像我所知道的dacite那样进行严格的类型检查。
相反,它选择在可能的情况下执行隐式强制割礼,这在某些情况下是有用的;在本例中,您可以看到下面的示例-- str到带注释的int。
注意:这个例子应该适用于带有包含的3.7+导入的Python。
from __future__ import annotations
from dataclasses import dataclass
from dataclass_wizard import fromdict
data = {
'type': 'champion',
'data': {
'Aatrox': {'id': 'Aatrox', 'key': '266', 'name': 'Aatrox'},
'Ahri': {'id': 'Ahri', 'key': '103', 'name': 'Ahri'},
}
}
@dataclass
class P:
type: str
data: dict[str, Character]
@dataclass
class Character:
id: str
key: int
name: str
champs = fromdict(P, data)
print(champs)
print(champs.data['Ahri'].key)输出:
P(type='champion', data={'Aatrox': Character(id='Aatrox', key=266, name='Aatrox'), 'Ahri': Character(id='Ahri', key=103, name='Ahri')})
103发布于 2022-03-02 19:52:26
如何做到这一点
d = {
"type": "champion",
"data": {
"Aatrox": {"id": "Aatrox", "key": "266", "name": "Aatrox"},
"Ahri": {"id": "Ahri", "key": "103", "name": "Ahri"},
},
}
def dict_to_class(d) -> object:
if isinstance(d, dict):
class C:
pass
for k, v in d.items():
setattr(C, k, dict_to_class(v))
return C
else:
return d
champ = dict_to_class(d)
print(champ.data.Ahri.key)
# 103这里的关键是setatter内置方法,它接受一个对象、一个字符串和一些值,并在该对象上创建一个属性(字段),该属性(字段)根据字符串命名并包含该值。
别这么做!
我必须强调,几乎没有任何好的理由这样做。在处理形状未知的JSON数据时,表示它的正确方法是a dict。
如果您确实知道数据的形状,您应该创建一个专门的dataclass,如下所示:
from dataclasses import dataclass
d = {
"type": "champion",
"data": {
"Aatrox": {"id": "Aatrox", "key": "266", "name": "Aatrox"},
"Ahri": {"id": "Ahri", "key": "103", "name": "Ahri"},
},
}
@dataclass
class Champion:
id: str
key: str
name: str
champions = {name: Champion(**attributes) for name, attributes in d["data"].items()}
print(champions)
# {'Aatrox': Champion(id='Aatrox', key='266', name='Aatrox'), 'Ahri': Champion(id='Ahri', key='103', name='Ahri')}
print(champions["Aatrox"].key)
# 266发布于 2022-03-02 20:31:39
dacite文档中有一个关于嵌套结构的部分,非常接近您想要的内容。他们使用的逐字示例如下:
@dataclass
class A:
x: str
y: int
@dataclass
class B:
a: A
data = {
'a': {
'x': 'test',
'y': 1,
}
}
result = from_dict(data_class=B, data=data)
assert result == B(a=A(x='test', y=1))我们可以访问任意深度的字段,例如result.a.x == 'test'。
这与您的数据之间的关键区别在于,data键下的字典具有带有任意值的键(Aatrox、Ahri等)。dacite并不是为了动态创建新的字段名而设置的,所以您将得到的最佳结果类似于@JonSG的回答的后半部分,后者使用setattr动态地构建新字段。
不过,让我们想象一下您将如何使用这些数据。您可能希望某个点能够迭代您的冠军,以便执行筛选/转换/等操作。在python中迭代字段是可能的,但您必须深入研究python内部,这意味着代码的可读性和通俗性较差。
更好的办法是:
j1预处理为适合您想要使用的结构的形状,然后使用dacite和适合新结构的dataclass。例如,可能将data的值提取到列表中是有意义的。from dataclasses import dataclass
from dacite import from_dict
@dataclass
class TopLevel:
type: str
data: dict
j1 = {
"type": "champion",
"data": {
"Aatrox": {"id": "Aatrox", "key": "266", "name": "Aatrox"},
"Ahri": {"id": "Ahri", "key": "103", "name": "Ahri"},
},
}
champions = from_dict(data_class=TopLevel, data=j1)
# champions.data is a dict of dicts
@dataclass
class Champion:
id: str
key: str
name: str
# transform champions.data into a dict of Champions
for k, v in champions.data.items():
champions.data[k] = from_dict(data_class=Champion, data=v)
# now, you can do interesting things like the following filter operation
start_with_a = [
champ for champ in champions.data.values() if champ.name.lower().startswith("a")
]
print(start_with_a)
# [Champion(id='Aatrox', key='266', name='Aatrox'), Champion(id='Ahri', key='103', name='Ahri')]https://stackoverflow.com/questions/71327925
复制相似问题