hansontechsolutions.com

Comparing Python's Dataclass, Attrs, and Pydantic for Class Definition

Written on

Understanding Python Class Definitions

This article delves into the differences among attrs, dataclass, and Pydantic when defining classes. It evaluates their performance concerning type violations, handling of positional arguments, and the ability to add new attributes (immutability). Generally speaking, attrs occupies a middle ground between dataclass and Pydantic.

Target Audience

This content is geared towards all Python developers.

Outline

  • Pydantic raises errors for type violations.
  • Pydantic does not accept positional arguments.
  • Dataclass is mutable by default.
  • A comparison of class definitions using dataclass, Pydantic, and attrs.

In a previous article, "Python Class vs. Data Class vs. Pydantic: Write to CSV with Data Validation," I focused solely on comparing Pydantic with dataclass. Tom Brown mentioned attrs, which I wasn't very familiar with at the time. After reviewing its documentation, I realized the need to include attrs in this comparison.

Pydantic and Type Violations

Let’s attempt to define a class using dataclass, Pydantic, and attrs. For this example, I will create a function to calculate an employee's severance package based on their duration of service and performance rating. (Note: This is a simplified example and should not be taken too seriously.)

Here is the code that uses dataclass to define our function:

from dataclasses import dataclass from datetime import datetime

@dataclass class EmployeeDataclass:

name: str

email: str

joining_date: datetime

passed_probation: bool

monthly_salary: float

performance_rating: int

def calculate_months_of_service(self) -> int:

import math

"""Calculate the number of months since joining the company."""

months_service = math.floor((datetime.today() - self.joining_date).days / 30)

return months_service

def calculate_severance_package(self) -> float:

"""Calculate the severance package based on months of service and performance rating.

Note: This is a simplified calculation for demonstration purposes.

"""

months_service = self.calculate_months_of_service()

if self.passed_probation:

if months_service < 12:

severance_package = self.monthly_salary * months_service / 12

else:

severance_package = (

self.monthly_salary * months_service / 12

  • self.monthly_salary * self.performance_rating

)

else:

severance_package = 0

return severance_package

Let's test it using correct type values first, then deliberately introduce a type mismatch.

Dataclass Handling of Types

The passed_probation attribute should be a boolean. Will Python raise an error? Surprisingly, it will not. Dataclass does not enforce strict type checks.

Using Attrs to Define a Class

Now, let’s see how attrs handles the same situation:

from attrs import define from datetime import datetime

@define class EmployeeAttrs:

name: str

email: str

joining_date: datetime

passed_probation: bool

monthly_salary: float

performance_rating: float

def calculate_months_of_service(self) -> int:

import math

"""Calculate the number of months since joining the company."""

months_service = math.floor((datetime.today() - self.joining_date).days / 30)

return months_service

def calculate_severance_package(self) -> float:

"""Calculate the severance package based on months of service and performance rating.

Note: This is a simplified calculation for demonstration purposes.

"""

months_service = self.calculate_months_of_service()

if self.passed_probation:

if months_service < 12:

severance_package = self.monthly_salary * months_service / 12

else:

severance_package = (

self.monthly_salary * months_service / 12

  • self.monthly_salary * self.performance_rating

)

else:

severance_package = 0

return severance_package

Similar to dataclass, attrs also allows for type violations.

Pydantic's Strict Type Enforcement

Next, let’s define a class using Pydantic:

from pydantic import BaseModel from datetime import datetime

class EmployeePydantic(BaseModel):

name: str

email: str

joining_date: datetime

passed_probation: bool

monthly_salary: float

performance_rating: float

def calculate_months_of_service(self) -> int:

import math

"""Calculate the number of months since joining the company."""

months_service = math.floor((datetime.today() - self.joining_date).days / 30)

return months_service

def calculate_severance_package(self) -> float:

"""Calculate the severance package based on months of service and performance rating.

Note: This is a simplified calculation for demonstration purposes.

"""

months_service = self.calculate_months_of_service()

if self.passed_probation:

if months_service < 12:

severance_package = self.monthly_salary * months_service / 12

else:

severance_package = (

self.monthly_salary * months_service / 12

  • self.monthly_salary * self.performance_rating

)

else:

severance_package = 0

return severance_package

In this case, Pydantic will raise an error if a type mismatch occurs, as it treats type hints as constraints rather than mere suggestions.

Only Pydantic Enforces Keyword Arguments

I seldom use positional arguments, but it’s noteworthy how each of these classes responds to them.

Using Positional Arguments with Dataclass

Using positional arguments with dataclass works seamlessly. Dataclass accepts positional arguments without issues.

To enforce keyword-only arguments in dataclass, you can specify kw_only=True in the decorator.

Using Positional Arguments with Attrs

Similarly, attrs accepts positional arguments by default. However, if you want to restrict it to keyword arguments, there is no straightforward method—each attribute must be marked as kw_only=True.

Pydantic's Restriction on Positional Arguments

Pydantic, on the other hand, only accepts keyword arguments by default. It does not allow for changing this behavior.

Immutability in Class Definitions

Mutability of Dataclass

Dataclass is mutable by default. You can add new attributes even after its definition:

@dataclass class EmployeeDataclass:

# existing attributes...

# Adding a new attribute employee = EmployeeDataclass(...) employee.remarks = "Good performance"

To make a dataclass immutable, set frozen=True in the decorator.

Immutability of Attrs

In contrast, attrs is immutable by default. Attempting to add a new attribute after defining an attrs class will result in an error.

Pydantic's Immutability

Pydantic behaves similarly to attrs, enforcing immutability by default. Adding a new attribute after class definition will also raise an error.

Conclusion

This article does not cover data validation decorators; I am currently working on that aspect. For those interested in the code presented above, please follow the provided links.

Follow me on LinkedIn | 👏🏽 for my story | Follow me on Medium

The first video, "Attrs, Pydantic, or Python Data Classes?" provides insights into the differences among these three approaches.

The second video, "Which Python @dataclass is best? Feat. Pydantic, NamedTuple, attrs..." explores which option might be the most suitable for your needs.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Understanding the Roots of Jealousy: A Deep Dive

Explore the complex nature of jealousy, its emotional roots, and how it affects relationships and mental health.

# Nine Distinctive Types of Individuals Who Achieve Lasting Success

Explore the nine unique types of people who consistently find success through resilience, vision, and collaboration.

Understanding Why Men Lose Interest: A Guide for Women

Explore the reasons why men may lose interest in relationships and how women can protect their hearts when faced with ghosting.

The Future of DevOps Teams: Trends and Insights

Explore the evolution of DevOps, the shift from dedicated teams, and key metrics for success in the industry.

Understanding COVID-19 Hospitalizations Among the Vaccinated

An exploration of the reasons behind rising hospitalizations among vaccinated individuals and the importance of booster shots.

Unveiling the Truth: Did a Cosmic Event Cause Sodom's Fate?

New research suggests a natural phenomenon may explain the destruction of Sodom and Gomorrah, linking ancient tales with modern science.

Understanding Equity: A Guide for Founders and Entrepreneurs

Discover the importance of equity distribution for startup success and learn from industry insights.

Embracing Responsibility: The Journey of Men's Value Creation

Understanding the distinction between inherent women's value and men's value creation is crucial for personal growth and accountability.