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.