Open In App

Creating nested dataclass objects in Python

Last Updated : 29 Aug, 2020
Improve
Improve
Like Article
Like
Save
Share
Report

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:




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


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.




# 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)


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 :




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)


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.



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads