Creating nested dataclass objects in Python

Dataclasses is an inbuilt Python module which contains decorators and functions for automatically adding special methods like __init__() and __repr__() to user-defined classes.

Dataclass Object is an object built into the Dataclasses module. This function is used as a decorator to add special methods directly to a user-defined class. This decorator examines class to find fields(a class variable that contains a type annotation). Then the dataclass decorator adds special methods.

Syntax:

@dataclass
class user_defined_class:

Here, we’ll tackle the idea of having nested dataclass objects in our program. Even though dataclasses are easy to use, they still increase the complexity of the program by one bar, and nesting such objects can be seen a little challenging but here we’ll take on each scenario and how to handle it.

Nested Dataclass Object

Carefully examine the following code:



filter_none

edit
close

play_arrow

link
brightness_4
code

@dataclass
class A:
    a: int
    b: str
  
@dataclass
class B:
    c: str
    d: A

chevron_right


Starting with class A, it is being decorated by a dataclass. This class is then being nested within class B as a field of B which also is being decorated by a dataclass object. So far this code just shows the nesting of dataclass objects, next we discuss how do we employ such implementation.

filter_none

edit
close

play_arrow

link
brightness_4
code

# importing module
from dataclasses import dataclass
  
@dataclass
class A:
    a: int
    b: str
  
@dataclass
class B:
    c: str
    d: A
  
# FIRST APPROACH
# creating object for class b with following values 
# c ='hello'
# a = 4
# b ='bye'
data ={'c':'hello', 'd':{'a':4, 'b':'bye'}}
b = B(**data)
print (b)
  
# SECOND APPROACH
data ={'c':'hello', 'd': A(**{'a':4, 'b':'bye'})}
c = B(**data)
print(c)

chevron_right


Output:

B(c='hello', d={'a': 4, 'b': 'bye'})
B(c='hello', d=A(a=4, b='bye'))

The problem with the first approach is that the output gives no idea about the nested object or the class A and its attributes, and if that is ones requirement then we are good to go. The second approach does the trick but it seems tedious if you have multiple nested objects in your dataclass objects, not just this, with increase in number of nested objects the complexity of the program will also increase and so will the method for calling them. Thus, we require a way to achieve the output of the second approach but without making the calling and initializing process complex.

The above problem can be resolved by wrapping generated __init__() method that will check for parameters passed to kwargs, check if any field belongs to a dataclass field type and if it does generate the nested object prior to the original __init__().

What this means is shown below :

filter_none

edit
close

play_arrow

link
brightness_4
code

from dataclasses import dataclass, is_dataclass
  
# decorator to wrap original __init__
def nested_deco(*args, **kwargs):
      
    def wrapper(check_class):
          
        # passing class to investigate
        check_class = dataclass(check_class, **kwargs)
        o_init = check_class.__init__
          
        def __init__(self, *args, **kwargs):
              
            for name, value in kwargs.items():
                  
                # getting field type
                ft = check_class.__annotations__.get(name, None)
                  
                if is_dataclass(ft) and isinstance(value, dict):
                    obj = ft(**value)
                    kwargs[name]= obj
                o_init(self, *args, **kwargs)
        check_class.__init__=__init__
          
        return check_class
      
    return wrapper(args[0]) if args else wrapper
  
  
@dataclass
class A:
    a: int
    b: str
  
@nested_deco
class B:
    c: str
    d: A
  
  
data ={'c':'hello', 'd':{'a':4, 'b':'bye'}}
b = B(**data)
print (b)

chevron_right


Output:

B(c='hello', d=A(a=4, b='bye'))

Note that apart from problems generated by __init__() this also doesn’t allow __init__=false to be returned to the code.

Attention geek! Strengthen your foundations with the Python Programming Foundation Course and learn the basics.

To begin with, your interview preparations Enhance your Data Structures concepts with the Python DS Course.




My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.


Article Tags :

Be the First to upvote.


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.