Skip to main content

How to Validate Class Attribute Values in Python Classes

In object-oriented programming, data integrity is paramount. If a class attribute like age is set to -5 or an email attribute is set to an integer, it can cause downstream errors in your application.

Python provides several mechanisms to validate class attributes, ranging from simple property setters to powerful descriptors and validation libraries.

Method 1: Using Property Setters (@property)

The most common and Pythonic way to validate attributes is using the @property decorator. This allows you to add logic (validation) to attribute assignment while keeping the standard obj.attr = value syntax.

Implementation

class User:
def __init__(self, age):
self.age = age # This calls the setter automatically

@property
def age(self):
return self._age

@age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError("Age must be an integer")
if value < 0 or value > 120:
raise ValueError("Age must be between 0 and 120")
self._age = value

# ✅ Valid assignment
user = User(25)
print(f"User age: {user.age}")

# ⛔️ Invalid assignment
try:
user.age = -5
except ValueError as e:
print(f"Error: {e}")

Output:

User age: 25
Error: Age must be between 0 and 120
note

Properties are ideal for validating a few specific attributes. If you need to validate many attributes similarly, this code becomes repetitive.

Method 2: Using the __setattr__ Magic Method

If you need to validate all attribute assignments or apply a general rule (e.g., "all string attributes must be uppercase"), you can override __setattr__.

class StrictModel:
def __setattr__(self, name, value):
# Example rule: Any attribute starting with 'is_' must be boolean
if name.startswith('is_') and not isinstance(value, bool):
raise TypeError(f"Attribute '{name}' must be a boolean")

# Call the superclass method to actually set the value
super().__setattr__(name, value)

obj = StrictModel()
obj.is_active = True # ✅ OK
# obj.is_admin = "yes" # ⛔️ Raises TypeError

Method 3: Using Descriptors (Advanced)

Descriptors allow you to create reusable validation logic that can be shared across multiple attributes and classes. This is how libraries like Django and SQLAlchemy implement their model fields.

class IntegerField:
def __init__(self, min_val=None, max_val=None):
self.min_val = min_val
self.max_val = max_val
self._name = None

def __set_name__(self, owner, name):
self._name = name

def __get__(self, instance, owner):
return instance.__dict__[self._name]

def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError(f"{self._name} must be an integer")
if self.min_val is not None and value < self.min_val:
raise ValueError(f"{self._name} must be >= {self.min_val}")
instance.__dict__[self._name] = value

class Product:
price = IntegerField(min_val=0)
stock = IntegerField(min_val=0)

def __init__(self, price, stock):
self.price = price
self.stock = stock

p = Product(100, 50)
# p.price = -10 # ⛔️ Raises ValueError: price must be >= 0

Method 4: Using Pydantic (Modern Standard)

For modern Python applications, especially those dealing with data exchange (APIs, JSON), the Pydantic library is the industry standard. It handles validation, type coercion, and error reporting automatically.

Installation: pip install pydantic

from pydantic import BaseModel, Field, ValidationError

class User(BaseModel):
username: str
age: int = Field(ge=0, le=120) # ge: greater/equal, le: less/equal
email: str

try:
user = User(username="alice", age=150, email="alice@example.com")
except ValidationError as e:
print(e)

Output:

1 validation error for User
age
Input should be less than or equal to 120 [type=less_than_equal, input_value=150, input_type=int]

Conclusion

  1. Use @property for simple, specific attribute validation logic.
  2. Use Descriptors if you need reusable validation logic across multiple classes.
  3. Use Pydantic for robust, declarative data validation in modern applications.