# 142. Using dataclasses for Structured Data

Using Python's `dataclasses` module simplifies the creation of classes by automatically generating special methods like `__init__`, `__repr__`, `__eq__`, and others. These classes are especially useful for handling structured data in a clear and concise manner. Below are 10 Python code snippets demonstrating how to use `dataclasses` for structuring data.

**1. Basic Dataclass**

A simple `dataclass` for storing structured data without writing an explicit `__init__` method.

```python
from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

# Create a Person object
person = Person("Alice", 30)
print(person)
```

**Output:**

```
Person(name='Alice', age=30)
```

**Explanation:**

* The `dataclass` decorator automatically adds an `__init__` method, which initializes the `name` and `age` attributes.
* The `__repr__` method is also automatically generated to provide a string representation of the object.

**2. Default Values in Dataclass**

Setting default values for fields in a `dataclass`.

```python
from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float = 0.0
    in_stock: bool = True

# Create a Product object
product = Product("Laptop", 999.99)
print(product)
```

**Output:**

```
Product(name='Laptop', price=999.99, in_stock=True)
```

**Explanation:**

* Default values for `price` and `in_stock` are provided, so they do not need to be explicitly passed when creating an instance of `Product`.

**3. Post-init Processing with `__post_init__`**

Performing additional setup after the class is initialized using the `__post_init__` method.

```python
from dataclasses import dataclass

@dataclass
class Circle:
    radius: float
    area: float = 0.0

    def __post_init__(self):
        self.area = 3.14 * self.radius ** 2

# Create a Circle object
circle = Circle(5)
print(circle)
```

**Output:**

```
Circle(radius=5, area=78.5)
```

**Explanation:**

* The `__post_init__` method is called automatically after the `__init__` method, allowing you to perform additional operations like calculating the area of the circle based on its radius.

**4. Immutable Dataclasses with `frozen=True`**

Making a `dataclass` immutable by setting `frozen=True`.

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: int
    y: int

# Create a Point object
point = Point(3, 4)

# Attempting to modify an attribute will raise an error
# point.x = 5  # This will raise a dataclasses.FrozenInstanceError
print(point)
```

**Output:**

```
Point(x=3, y=4)
```

**Explanation:**

* The `frozen=True` argument makes the instance of `Point` immutable. Any attempt to change the attributes will raise a `FrozenInstanceError`.

**5. Comparing Dataclasses with `__eq__`**

Dataclasses automatically generate the `__eq__` method, allowing easy comparison of instances.

```python
from dataclasses import dataclass

@dataclass
class Rectangle:
    width: float
    height: float

# Create two Rectangle objects
rect1 = Rectangle(4, 6)
rect2 = Rectangle(4, 6)

print(rect1 == rect2)  # True
```

**Output:**

```
True
```

**Explanation:**

* The `__eq__` method is automatically generated by the `dataclass`, making it easy to compare two instances of the class for equality based on their attribute values.

**6. Dataclass with `field()` for Customization**

Customizing the behavior of fields using the `field()` function.

```python
from dataclasses import dataclass, field

@dataclass
class Employee:
    name: str
    salary: float
    is_active: bool = field(default=True)

# Create an Employee object
employee = Employee("John", 50000)
print(employee)
```

**Output:**

```
Employee(name='John', salary=50000, is_active=True)
```

**Explanation:**

* The `is_active` field uses `field()` to set a default value and allows further customization if needed.

**7. Dataclass with Default Factory for Mutable Types**

Using `default_factory` to set default values for mutable types like lists or dictionaries.

```python
from dataclasses import dataclass, field

@dataclass
class Student:
    name: str
    courses: list = field(default_factory=list)

# Create a Student object
student = Student("Alice")
student.courses.append("Math")
print(student)
```

**Output:**

```
Student(name='Alice', courses=['Math'])
```

**Explanation:**

* The `default_factory` argument is used to initialize `courses` with a new empty list, ensuring each instance has its own list.

**8. Type Annotations in Dataclasses**

Using type annotations in `dataclasses` for better readability and type checking.

```python
from dataclasses import dataclass

@dataclass
class Car:
    make: str
    model: str
    year: int

# Create a Car object
car = Car("Toyota", "Corolla", 2020)
print(car)
```

**Output:**

```
Car(make='Toyota', model='Corolla', year=2020)
```

**Explanation:**

* Type annotations (`str`, `int`) provide better clarity and can be used for type checking tools like `mypy`.

**9. Dataclass with Multiple Inheritance**

Dataclasses can be used with multiple inheritance.

```python
from dataclasses import dataclass

@dataclass
class A:
    a: int

@dataclass
class B:
    b: int

@dataclass
class C(A, B):
    c: int

# Create an instance of C
obj = C(1, 2, 3)
print(obj)
```

**Output:**

```
C(a=1, b=2, c=3)
```

**Explanation:**

* `C` inherits from both `A` and `B`, and the `dataclass` decorator works across multiple inheritance hierarchies.

**10. Converting Dataclass to Dictionary**

Converting a `dataclass` instance into a dictionary using the `asdict()` function.

```python
from dataclasses import dataclass, asdict

@dataclass
class Book:
    title: str
    author: str
    year: int

# Create a Book object
book = Book("1984", "George Orwell", 1949)

# Convert the dataclass instance to a dictionary
book_dict = asdict(book)
print(book_dict)
```

**Output:**

```
{'title': '1984', 'author': 'George Orwell', 'year': 1949}
```

**Explanation:**

* The `asdict()` function from the `dataclasses` module converts the instance into a dictionary, making it easier to work with.

***

**Conclusion:**

Using `dataclasses` in Python simplifies the process of defining classes that primarily store data. The automatic generation of methods such as `__init__`, `__repr__`, `__eq__`, and others, along with support for default values, mutable types, and post-initialization logic, makes it a great tool for managing structured data.
