PYTHON - Data Classes
Python - Dataclass
Thedataclassis a feature in Python that helps for automatically adding special methods to user-defined classes. For example, a dataclass can be used to automatically generate a constructor method for your class, so you can easily create instances of the class. In this chapter, we will explain all the features of the dataclass module with the help of examples.
What is a Dataclass?
Adataclassis a Python class denoted with the@dataclassdecorator from thedataclassesmodule. It is used to automatically generate special methods like constructor method__init__(), string representation method repr(), equality method__eq__(), and others based on the class attributes defined in the class body. This simply means that using dataclasses can reduce boilerplate code in your class definitions.
Syntax of Dataclass
The syntax to define a dataclass is as follows −
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)Each argument is taking a boolean value, which indicates whether corresponding special methods should be automatically generated.
The@dataclassdecorator can take these options:
- init (default: True)− Automatically creates an init() method for initializing class instances.
- repr (default: True)− Automatically creates a repr() method to provide a readable string representation of the object.
- eq (default: True)− Generates an eq() method for comparing objects using the
==operator. - order (default: False)− If set to True, comparison methods (like
< and >) are generated for sorting objects. - unsafe_hash (default: False)− If false, it produces a default hash() method based on how equality and mutability are defined.
- frozen (default: False)− Creates immutable instances (they can’t be changed after creation).
Creating a Dataclass
In the code below, you can see that we have defined a simple dataclass namedStudentwith three attributes:name, age,andpercent. Even though we are not using constructor or other special methods, we can still create instances of the Student class and use its attributes. This is because the dataclass decorator automatically generates these methods for us.
from dataclasses import dataclass
@dataclass
class Student:
name: str
age: int
percent: float
s1 = Student("Alice", 20, 90.0)
s2 = Student("Bob", 22, 85.5)
print(s1)
print(s1 == s2)Theoutputof the above code will be −
Student(name='Alice', age=20, percent=90.0)
FalseExample without Dataclass
The above code is equivalent to the following code in traditional class definition without using dataclass −
class Student:
def __init__(self, name: str, age: int, percent: float):
self.name = name
self.age = age
self.percent = percent
def __repr__(self):
return f"Student(name={self.name}, age={self.age}, percent={self.percent})"
def __eq__(self, other):
if not isinstance(other, Student):
return NotImplemented
return (self.name == other.name and self.age == other.age and self.percent == other.percent)
s1 = Student("Alice", 20, 90.0)
s2 = Student("Bob", 22, 85.5)
print(s1)
print(s1 == s2)Theoutputof this code will same as above −
Student(name='Alice', age=20, percent=90.0)
FalseDefault Values in Dataclass
You can also provide default values for the attributes in a dataclass. If a value is not provided during the creation of an instance, then the default value will be used. In the code below, we have provided a default value of 0.0 for the percent attribute.
from dataclasses import dataclass
@dataclass
class Student:
name: str
age: int
percent: float = 0.0 # Default value for percent
s1 = Student("Alice", 20)
s2 = Student("Bob", 22, 85.5)
print(s1)
print(s2)Theoutputof the above code will be −
Student(name='Alice', age=20, percent=0.0)
Student(name='Bob', age=22, percent=85.5)Dataclass with Mutable Default Values
Mutable default values refers to default values that can be modified after the instance is created, such as lists or dictionaries. When using mutable default values in a dataclass, it is recommended to use thefield()function from the dataclasses module with thedefault_factoryparameter. Because if you use a mutable object as a default value directly, it will be shared across all instances of the dataclass. This can lead security issues and unexpected behavior.
In the code below, we have defined a dataclass namedCoursewith a mutable default value for the students attribute.
from dataclasses import dataclass, field
from typing import List
@dataclass
class Course:
name: str
students: List[str] = field(default_factory=list) # Mutable default value
course1 = Course("Math")
course2 = Course("Science", ["Alice", "Bob"])
course1.students.append("Charlie")
print(course1)
print(course2)Theoutputof the above code will be −
Course(name='Math', students=['Charlie'])
Course(name='Science', students=['Alice', 'Bob'])Explantion: If you usedstudents: List[str] = []directly, all the instances ofCoursewill get the same list, because default values are evaluated only once, i.e., during creation of class. By usingfield(default_factory=list), Python ensures that every Course instance gets its own separate list and avoids security vulnerabilities.
Immuatable/Frozen Dataclasses
An immutable or frozen dataclass indicates that the instances of the dataclass cannot be modified after they are created. This can be achieved by setting thefrozenparameter toTruein the @dataclass decorator. When a dataclass is frozen, any attempt to modify its attributes will create aFrozenInstanceError.
The frozen dataclasses are often used to secure applications by preventing unauthorized access or modification of sensitive data. In the code below, we have defined a frozen dataclass namedPointwith two attributes: x and y. We will catch the FrozenInstanceError when trying to modify the x attribute.
from dataclasses import dataclass, FrozenInstanceError
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
print(p)
try:
p.x = 10 # This will raise an error
except FrozenInstanceError as e:
print(e)Theoutputof the above code will be −
Point(x=1, y=2)
cannot assign to field 'x'Setting Up Post-Initialization
Thepost-Initializationrefer to the additional initialization logic that can be added to a dataclass after the automatic init() method has been called. This can be done by defining a special method named__post_init__()in the dataclass. The post_init() method is called automatically after the instance is created and all the attributes have been initialized.
In the code below, we have defined a dataclass namedRectanglewith default value for the area as 0.0. The area is then calculated in the post_init() method based on the recived width and height values.
from dataclasses import dataclass
@dataclass
class Rectangle:
width: float
height: float
area: float = 0.0 # This will be calculated in __post_init__
def __post_init__(self):
self.area = self.width * self.height # Calculate area after initialization
r = Rectangle(5.0, 10.0)
print(r)
print(f"Area of the rectangle: {r.area}")Theoutputof the above code will be −
Rectangle(width=5.0, height=10.0, area=50.0)
Area of the rectangle: 50.0Convert Dataclass to Dictionary
You can convert a dataclass instance to a dictionary using theasdict()function from the dataclasses module. This function recursively converts the dataclass and its fields into a dictionary. This is useful when you want to serialize the dataclass instance or work with its data in a dictionary format.
from dataclasses import dataclass, asdict
from typing import List
@dataclass
class Student:
name: str
age: int
grades: List[float]
student = Student("Alice", 20, [88.5, 92.0, 79.5])
student_dict = asdict(student)
print(student_dict)Theoutputof the above code will be −
{'name': 'Alice', 'age': 20, 'grades': [88.5, 92.0, 79.5]}Conclusion
In this chapter, we have learned that dataclass is feature introduced in Python to reduce boilerplate code in class definitions. We have seen how to create a dataclass, provide default values, handle mutable default values, etc. One thing to note is that dataclasses are available inPython 3.7and later versions. If you are using an earlier version of Python, you will need to use traditional class definitions without the dataclass decorator.