Since it's possible that the outer keys in the dict object might be dynamically populated (i.e. you could add an ID_3 field later, and you don't want the code to break) - I would suggest using a custom outer class, and a nested dataclass Person for this particular task , as shown below.
Note I would add a helper from_dict class method to make this a bit easier, rather than going through the constructor or __init__ method.
from dataclasses import dataclass
class MyClass:
# not needed, but useful for type hinting purposes
ID_1: 'Person'
ID_2: 'Person'
@classmethod
def from_dict(cls, d: dict):
o = cls()
for k, v in d.items():
setattr(o, k, Person(**v))
return o
def __repr__(self):
fields = ', '.join(f'{k}={v!r}' for k, v in self.__dict__.items())
return f'{self.__class__.__qualname__}({fields})'
@dataclass
class Person:
name: str
age: int
data = {'ID_1': {'name': 'Julie', 'age': 19}, 'ID_2': {'name': 'Andre', 'age': 25}}
c = MyClass.from_dict(data)
print(c)
print(c.ID_1.name) # Julie
print(c.ID_2.age) # 25
Output:
MyClass(ID_1=Person(name='Julie', age=19), ID_2=Person(name='Andre', age=25))
Julie
25
Dot Access Dict
Another option which requires less boilerplate code, could be to use my helper library dotwiz, which enables dot or attribute-access for dict objects.
Note: this example requires pip install dotwiz.
import typing
from dotwiz import DotWiz
# define an alias
MyClass = DotWiz
if typing.TYPE_CHECKING: # only runs for static type checkers
class MyClass(DotWiz):
# not needed, but useful for type hinting purposes
ID_1: 'Person'
ID_2: 'Person'
# This is a stub class, only for type hinting purposes; it wouldn't
# work if you wanted to define or use methods on the class.
class Person:
name: str
age: int
data = {'ID_1': {'name': 'Julie', 'age': 19}, 'ID_2': {'name': 'Andre', 'age': 25}}
c = MyClass(data)
print(c)
print(c.ID_1.name) # Julie
print(c.ID_2.age) # 25
Out:
✫(ID_1=✫(name='Julie', age=19), ID_2=✫(name='Andre', age=25))
Julie
25
Full disclaimer: I am the creator and maintainer of this library.