In Python programming, class definitions are the core paradigm for organizing data and encapsulating logic. However, when it comes to creating simple classes that are solely for data storage, developers often have to write a lot of repetitive boilerplate code. For example, methods like <span>__init__</span> for attribute initialization, <span>__repr__</span> for friendly display of object information, and <span>__eq__</span> for implementing object equality comparison. This kind of code not only consumes development effort but also introduces potential errors due to oversight of details, leading to decreased code readability and maintainability.
To address this industry pain point, Python 3.7 introduced the <span>dataclasses</span> module, which provides the <span>@dataclass</span> decorator, a powerful tool for data class development. This decorator can automatically generate the aforementioned common magic methods, allowing developers to focus on defining core attributes without worrying about redundant low-level implementations, thus enabling the rapid construction of fully functional and user-friendly data classes.

This article will start with basic concepts and provide a detailed breakdown of the core usage of <span>@dataclass</span> with practical examples.
1 Basic Usage
1.1 Basic Methods
In the traditional way, defining a simple data class requires manually writing a lot of boilerplate code:
class Person:
def __init__(self, name: str, age: int, email: str = "[email protected]"):
self.name = name
self.age = age
self.email = email
def __repr__(self):
return f"Person(name='{self.name}', age={self.age}, email='{self.email}')"
def __eq__(self, other):
if not isinstance(other, Person):
return False
return (self.name == other.name and
self.age == other.age and
self.email == other.email)
# Example usage
p1 = Person("Alice", 25)
p2 = Person("Bob", 30, "[email protected]")
print(p1)
print(p1 == Person("Alice", 25))
With the <span>@dataclass</span> decorator, we can achieve the same functionality with much less code:
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
email: str = "[email protected]" # Default value
# Example usage
p1 = Person("Alice", 25)
p2 = Person("Bob", 30, "[email protected]")
print(p1)
print(p1 == Person("Alice", 25))
<span>@dataclass</span> will automatically generate the following methods for us:
<span>__init__</span>: Initialization method that creates an instance based on the defined fields;<span>__repr__</span>: Provides a friendly string representation for debugging and logging;<span>__eq__</span>: Equality comparison based on field values;<span>__hash__</span>: By default, generates a hash method if all fields are immutable types (can be controlled via the<span>unsafe_hash</span>parameter).
Custom methods and <span>@property</span> computed properties can also be added to data classes, balancing data storage with simple business logic:
from dataclasses import dataclass
from datetime import datetime
@dataclass
class Person:
name: str
age: int
email: str = "[email protected]" # Default value
# Custom method: greet
def greet(self) -> str:
"""Returns a personalized greeting"""
return f"Hello, my name is {self.name} and I'm {self.age} years old!"
# Custom method: check if adult
def is_adult(self) -> bool:
"""Determines if the person is of adult age (18 years)"""
return self.age >= 18
# @property computed property: birth year (calculated based on current age)
@property
def birth_year(self) -> int:
"""Calculates the birth year based on current age and year"""
current_year = datetime.now().year
return current_year - self.age
# Example usage
p1 = Person("Alice", 25)
p2 = Person("Bob", 17, "[email protected]")
# Original functionality remains unchanged
print(p1)
print(p1 == Person("Alice", 25))
# Call custom methods
print(p1.greet()) # Output: Hello, my name is Alice and I'm 25 years old!
print(f"Alice is adult? {p1.is_adult()}") # Output: Alice is adult? True
print(f"Bob is adult? {p2.is_adult()}") # Output: Bob is adult? False
# Access computed property (like accessing a normal attribute)
print(f"Alice was born in {p1.birth_year}")
1.2 Advanced Usage
<span>@dataclass</span>‘s power lies not only in simplifying basic code but also in supporting customized development for complex scenarios. With its configuration functions or parameter settings, we can solve issues like mutable type default values and field customization, even integrating custom methods to implement business logic. This section will delve into these core capabilities, and for further extended learning, please refer to:Data Classes: @dataclass Decorator[1].
The Mutable Type Default Value Trap
In Python, mutable objects like lists and dictionaries are not suitable as default parameters for functions or methods. This is because the default parameter’s value is computed and initialized at the time of function definition, not each time it is called. This means that all function calls will share the same mutable object instance, leading to unexpected behavior. For example:
# Incorrect example: all instances share the same list
class BadPerson:
def __init__(self, name: str, hobbies: list = []): # Dangerous!
self.name = name
self.hobbies = hobbies
p1 = BadPerson("Alice")
p1.hobbies.append("reading")
p2 = BadPerson("Bob")
print(p2.hobbies) # Output ['reading'] — p2 unexpectedly shares p1's list!
<span>dataclasses</span> module’s <span>field</span> function can specify a factory function for generating default values through the <span>default_factory</span> parameter, providing a perfect solution to the mutable type default value issue:
from dataclasses import dataclass, field # Import field function
@dataclass
class GoodPerson:
name: str
# Use list as a factory function to generate a new list each time an instance is created
hobbies: list = field(default_factory=list)
p1 = GoodPerson("Alice")
p1.hobbies.append("reading")
p2 = GoodPerson("Bob")
print(p2.hobbies) # Output [], each instance has an independent list!
<span>field</span> function’s core parameters:
<span>default_factory</span>: Specifies a no-argument function (factory function) to generate the default value for the field (e.g.,<span>list</span>,<span>dict</span>,<span>lambda</span>, or custom functions);<span>default</span>: Specifies the default value for immutable types (equivalent to direct assignment, e.g.,<span>field(default=0)</span><code><span>);</span><span>init=False</span>: Indicates that this field does not participate in the<span>__init__</span>method’s parameter list (must be manually assigned or initialized through other means);<span>repr=False</span>: Indicates that this field will not be displayed in the output of the<span>__repr__</span>method;<span>compare=False</span>: Indicates that this field will not participate in the logic of comparison methods like<span>__eq__</span>.
from dataclasses import dataclass, field
import uuid
from datetime import date
@dataclass
class Book:
"""A data class representing book information"""
# Basic information of the book, must be provided when creating an instance
title: str # Book title
author: str # Author
price: float # Price
# Unique identifier for the book, automatically generated using UUID, ignored during object comparison
book_id: str = field(
default_factory=lambda: str(uuid.uuid4())[:6], # Generate a 6-digit unique ID
compare=False # Ignore this field during object comparison
)
# Publication date, defaults to the current date, ignored during object comparison
publish_date: date = field(
default_factory=date.today # Default to today's date
)
# Internal inventory code, has a default value, not displayed when printing the object
inventory_code: str = field(
default="N/A", # Default value is "N/A"
compare=False,
repr=False # Not displayed when printing the object
)
# Create two identical book instances
book1 = Book("Python Programming", "Zhang San", 59.90, inventory_code="PY-001")
book2 = Book("Python Programming", "Zhang San", 59.90, inventory_code="PY-002")
# Print the information of the first book (inventory_code will not be displayed)
print("First book information:", book1)
# Compare whether the two books are equal (only compares title, author, price)
print("Are the two books equal?", book1 == book2)
# Access hidden fields
print("First book's inventory code:", book1.inventory_code)
print("First book's ID:", book1.book_id)
Helper Functions
In addition to the <span>field</span> helper function, Python’s <span>dataclasses</span> module also provides a series of useful utility functions and special types that greatly expand the flexibility and functionality of data classes:
<span>asdict()</span>: Converts a data class instance to a standard dictionary,<span>astuple()</span>: Converts a data class instance to a tuple,<span>replace()</span>: Creates a copy of the data class instance and replaces specified field values as needed,<span>fields()</span>: Retrieves metadata information about the fields of the data class,<span>is_dataclass()</span>: Determines whether an object (class or instance) is a data class,<span>make_dataclass()</span>: Dynamically creates a data class (without a decorator),<span>InitVar</span>: Marks a temporary variable that is only used for<span>__init__</span>initialization (will not become an instance attribute).
Example code is as follows:
from dataclasses import (
dataclass, asdict, astuple, replace, fields,
is_dataclass, make_dataclass, InitVar, field
)
# Define a basic data class (including InitVar demonstration)
@dataclass
class Person:
name: str
age: int
# InitVar mark: address is only for initialization, will not become an instance attribute
address: InitVar[str] = field(default="Unknown Address") # Set default value
def __post_init__(self, address):
# Use InitVar parameter to initialize instance attribute
self.full_info = f"{self.name} ({self.age}), Address: {address}"
# Create an instance
person = Person("Alice", 30, "123 Main St")
# 1. asdict(): Convert to dictionary
print("asdict result:", asdict(person))
# 2. astuple(): Convert to tuple
print("astuple result:", astuple(person))
# 3. replace(): Create a copy and modify fields
new_person = replace(person, age=31)
print("Instance after replace:", new_person)
# 4. fields(): Get field information
print("\nField information:")
for field_info in fields(person):
print(f"Field name: {field_info.name}, Type: {field_info.type}, Is InitVar: {isinstance(field_info.type, InitVar)}")
# 5. is_dataclass(): Determine if it is a data class
print("\nis_dataclass(Person):", is_dataclass(Person))
print("is_dataclass(person):", is_dataclass(person))
print("is_dataclass(dict):", is_dataclass(dict))
# 6. make_dataclass(): Dynamically create a data class
DynamicPerson = make_dataclass(
"DynamicPerson", # Class name
[("name", str), ("age", int)], # Field list
namespace={"greet": lambda self: f"Hello, {self.name}!"} # Additional methods/properties
)
dynamic_person = DynamicPerson("Bob", 25)
print("\nDynamically created data class instance:", dynamic_person)
print("Dynamic class method call:", dynamic_person.greet())
Post-Initialization Processing
In the <span>dataclasses</span> module, <span>__post_init__</span> is a magic method that is called immediately after the automatically generated <span>__init__</span> method has finished executing, mainly used to implement automatic processing logic after initialization (e.g., calculating derived fields, supplementing attribute assignments, etc.); when used in conjunction with fields specified as <span>init=False</span>, this method can flexibly handle derived properties that do not need to be passed as constructor parameters but are generated through post-initialization logic.
from dataclasses import dataclass, field
@dataclass
class Product:
name: str
price: float
quantity: int = 1
total_price: float = field(init=False) # Total price calculated from other fields
def __post_init__(self):
"""Automatically calculates total price after initialization"""
self.total_price = self.price * self.quantity
# Example usage
apple = Product("Apple", 5.5, 10)
banana = Product("Banana", 3.0)
print(f"Apple total: ${apple.total_price}") # Output: 55.0
print(f"Banana total: ${banana.total_price}") # Output: 3.0
Field Order Requirements
When defining fields in a <span>dataclass</span>, fields without default values must be placed before fields with default values.
- Incorrect writing: declare the field with a default value
<span>address</span>before declaring the field without a default value<span>id</span>→ raises a syntax error. - Correct writing: declare the field without a default value
<span>id</span>before declaring the field with a default value<span>address</span>→ runs normally.
This is not a restriction exclusive to <span>dataclass</span>, but rather a basic syntax rule of the Python language.
<span>@dataclass</span> decorator’s core function is to automatically generate the <span>__init__</span> constructor method based on the fields defined in the class.
- When you write:
@dataclass class InvalidFieldOrder: address: str = "Beijing" id: intit will attempt to generate the following
<span>__init__</span>:def __init__(self, address: str = "Beijing", id: int): ...
But this is completely not allowed in Python! When defining a function, parameters with default values (optional parameters) cannot appear before parameters without default values (required parameters).
- Whereas the correct writing:
@dataclass class ValidFieldOrder: id: int address: str = "Beijing"will generate a valid
<span>__init__</span>:def __init__(self, id: int, address: str = "Beijing"): ...
This fully complies with Python’s syntax rules: required parameters come first, optional parameters come later.
Data Class Inheritance
Data classes can be inherited by other data classes or regular Python classes: when a data class inherits another data class, the subclass automatically merges the fields of the parent class; when a regular class inherits a data class, if it needs to use the fields and constructor logic of the parent class, it must manually call the parent class’s constructor and handle the relevant parameters.
from dataclasses import dataclass
# 🟡 Base class: Shape (data class)
@dataclass
class Shape:
color: str
# 🟦 Subclass: Square (data class)
@dataclass
class Square(Shape):
side_length: float = 1.0 # Default side length is 1
# 🟢 Subclass: Circle (regular class, not a data class)
class Circle(Shape):
def __init__(self, color: str, radius: float = 1.0):
# Must manually call the parent constructor to initialize color
super().__init__(color)
self.radius = radius
# If a friendly print format is needed, __repr__ must be implemented manually
def __repr__(self):
return f"Circle(color='{self.color}', radius={self.radius})"
# Example usage
red_square = Square("red")
print(red_square)
blue_circle = Circle("blue", 5.0)
print(blue_circle)
default_circle = Circle("green")
print(default_circle)
@dataclass Decorator Parameter Details
<span>@dataclass</span> decorator provides multiple configurable parameters to adapt to various development scenarios. Below is a detailed explanation of the core parameters, covering functionality, usage constraints, and version requirements:
-
<span>init=True</span>
- Controls whether to automatically generate the
<span>__init__()</span>method. - If set to
<span>False</span>, you need to define the<span>__init__</span>method yourself.
<span>repr=True</span>
- Controls whether to automatically generate the
<span>__repr__()</span>method. - The generated
<span>repr</span>will include the class name and all fields with their values.
<span>eq=True</span>
- Controls whether to automatically generate the
<span>__eq__()</span>method. - Based on the class’s field values for equality comparison.
<span>order=False</span>
- Controls whether to generate comparison operator methods (
<span>__lt__</span>,<span>__le__</span>,<span>__gt__</span>,<span>__ge__</span>). - If set to
<span>True</span>, comparisons will be based on the order of field definitions. - Note: When setting
<span>order=True</span>,<span>eq</span>must be<span>True</span>(default).
<span>unsafe_hash=False</span>
- If
<span>frozen=True</span>, a hash method based on fields will be generated. - If
<span>frozen=False</span>,<span>__hash__</span>will be set to<span>None</span>.
- Controls whether to generate the
<span>__hash__()</span>method. - By default:
- Setting to
<span>True</span>will force the generation of<span>__hash__</span>, but using it on mutable instances may cause issues.
<span>frozen=False</span>
- If set to
<span>True</span>, a “frozen” class will be created, and instance attributes cannot be modified. - Attempts to modify will raise a
<span>dataclasses.FrozenInstanceError</span>.
<span>match_args=True</span> (Python 3.10+)
- Controls whether to generate the
<span>__match_args__</span>attribute for pattern matching.
<span>kw_only=False</span> (Python 3.10+)
- If set to
<span>True</span>, all fields will become keyword-only parameters, and instantiation must be done using keyword arguments, not positional arguments.
<span>slots=False</span> (Python 3.10+)
- If set to
<span>True</span>, the<span>__slots__</span>attribute will be generated, limiting class instances to only have predefined attributes, saving memory and improving attribute access speed.
<span>weakref_slot=False</span> (Python 3.11+)
- When
<span>slots=True</span>, if set to<span>True</span>, a slot for weak references will be added.
Example code is as follows:
from dataclasses import dataclass, FrozenInstanceError
import weakref
# 1. init=False example
@dataclass(init=False)
class Person:
name: str
age: int
# Manually define __init__ method
def __init__(self, name):
self.name = name
self.age = 0 # Set default age
# 2. repr=False example
@dataclass(repr=False)
class Point:
x: int
y: int
# Custom repr
def __repr__(self):
return f"Point at ({self.x}, {self.y})"
# 3. eq=True example
@dataclass(eq=True)
class Product:
id: int
name: str
# 4. order=True example
@dataclass(order=True)
class Student:
score: int
name: str
# 5. unsafe_hash=True example
@dataclass(unsafe_hash=True)
class Book:
title: str
author: str
# 6. frozen=True example
@dataclass(frozen=True)
class ImmutablePoint:
x: int
y: int
# 7. match_args=True example (Python 3.10+)
@dataclass(match_args=True)
class Shape:
type: str
size: int
# 8. kw_only=True example (Python 3.10+)
@dataclass(kw_only=True)
class Car:
brand: str
model: str
# 9. slots=True example (Python 3.10+)
@dataclass(slots=True)
class User:
id: int
username: str
# 10. weakref_slot=True example (Python 3.11+)
@dataclass(slots=True, weakref_slot=True)
class Node:
value: int
# Test code
if __name__ == "__main__":
# 1. Test init=False
p = Person("Alice")
print(f"1. Person: {p.name}, {p.age}")
# 2. Test repr=False
point = Point(3, 4)
print(f"2. Point: {point}")
# 3. Test eq=True
p1 = Product(1, "Apple")
p2 = Product(1, "Apple")
print(f"3. Products equal? {p1 == p2}")
# 4. Test order=True
s1 = Student(90, "Bob")
s2 = Student(85, "Alice")
print(f"4. s1 > s2? {s1 > s2}") # Compare based on parameter definition order
# 5. Test unsafe_hash=True
book = Book("Python", "Guido")
print(f"5. Book hash: {hash(book)}")
# 6. Test frozen=True
immutable_point = ImmutablePoint(1, 2)
try:
immutable_point.x = 3
except FrozenInstanceError as e:
print(f"6. Frozen error: {e}")
# 7. Test match_args=True (Python 3.10+)
shape = Shape("circle", 5)
match shape:
case Shape("circle", size):
print(f"7. Circle with size {size}")
case Shape("square", size):
print(f"7. Square with size {size}")
# 8. Test kw_only=True
car = Car(brand="Toyota", model="Camry")
print(f"8. Car: {car}")
# 9. Test slots=True
user = User(1, "admin")
print(f"9. User: {user}")
try:
user.email = "[email protected]"
except AttributeError as e:
print(f"9. Slots error: {e}")
# 10. Test weakref_slot=True
node = Node(10)
ref = weakref.ref(node)
print(f"10. Weakref node value: {ref().value}")
Welcome to follow my public account “Peng Peng Jia You Ya“, where original technical articles are pushed in real-time.

References
[1]
Data Classes: @dataclass Decorator: https://www.krython.com/tutorial/python/data-classes-dataclass-decorator