Skip to content

How to Validate Function Parameter Rules in Python

Learn how to enforce type safety, value ranges, and format rules for Python function parameters using isinstance, decorators, and Pydantic for robust input validation.

Ensuring that function parameters meet specific criteria (types, ranges, or formats) is the first line of defense against runtime errors and logical bugs. While Python is dynamically typed, robust applications require strict validation logic to guarantee data integrity.

This guide explores native methods for validating parameters using type checking and conditional logic, as well as advanced techniques using decorators and modern libraries like Pydantic.

Basic Type Validation (isinstance)

The most fundamental validation is ensuring an argument is of the expected data type. While Python provides Type Hints (def func(x: int)), these are ignored at runtime. To enforce them, you must use isinstance().

Implementing Type Checks

Use isinstance(value, type) to check types. It supports tuples for checking multiple allowed types (e.g., (int, float)).

def calculate_area(length, width):

    if not isinstance(length, (int, float)) or not isinstance(width, (int, float)):
        raise TypeError(f"Inputs must be numbers. Got {type(length)} and {type(width)}")
    
    return length * width

print(f"Area: {calculate_area(10, 5)}")

try:
    calculate_area("10", 5)
except TypeError as e:
    print(f"Error: {e}")

Output:

Area: 50
Error: Inputs must be numbers. Got <class 'str'> and <class 'int'>

Range and Value Validation

Once the type is confirmed, you often need to ensure the value falls within a logical range (e.g., age cannot be negative).

Logic Checks

Use standard comparison operators (<, >, <=, >=) and raise a ValueError if the data violates business rules.

def set_age(age):
    if not isinstance(age, int):
        raise TypeError("Age must be an integer")
    
    if not (0 <= age <= 120):
        raise ValueError(f"Age {age} is invalid. Must be between 0 and 120.")
    
    return f"Age set to {age}"

try:
    print(set_age(25))
    print(set_age(150))
except ValueError as e:
    print(f"Validation Failed: {e}")

Output:

Age set to 25
Validation Failed: Age 150 is invalid. Must be between 0 and 120.

Pattern Matching (Regex)

For strings, validation often involves checking patterns (emails, phone numbers) using the re module.

import re

def validate_email(email):
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    
    if not re.match(pattern, email):
        raise ValueError("Invalid email format")
    
    return True

try:
    validate_email("user@labex")
except ValueError as e:
    print(f"Error: {e}")

Output:

Error: Invalid email format

Advanced: Reusable Validation with Decorators

If you find yourself repeating the same checks (e.g., "input must not be None" or "input must be positive") across many functions, utilize Decorators. This keeps your core function logic clean.

Creating a Validation Decorator

from functools import wraps

def validate_positive(func):
    """Decorator to ensure all numeric arguments are positive."""
    @wraps(func)
    def wrapper(*args, **kwargs):
        for arg in args:
            if isinstance(arg, (int, float)) and arg < 0:
                raise ValueError(f"Negative argument found: {arg}")
        return func(*args, **kwargs)
    return wrapper

@validate_positive
def calculate_sqrt(x):
    return x ** 0.5

try:
    print(f"Root: {calculate_sqrt(16)}")
    print(f"Root: {calculate_sqrt(-9)}")
except ValueError as e:
    print(f"Decorator caught error: {e}")

Output:

Root: 4.0
Decorator caught error: Negative argument found: -9

Modern Approach: Using Pydantic

For complex applications, writing manual if statements for every parameter is inefficient. The Pydantic library is the industry standard for parsing and validation in modern Python.

It forces types at runtime and provides detailed error messages automatically.

from pydantic import BaseModel, Field, ValidationError

class UserProfile(BaseModel):
    username: str
    age: int = Field(gt=0, le=120)
    email: str

def create_user(data: dict):
    try:
        user = UserProfile(**data)
        print(f"User created: {user.username}")
    except ValidationError as e:
        print("Validation Error:")
        print(e)

create_user({"username": "Alice", "age": 30, "email": "alice@example.com"})

create_user({"username": "Bob", "age": "too old", "email": "bob@example.com"})

Output:

User created: Alice
Validation Error:
1 validation error for UserProfile
age
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='too old', input_type=str]

Conclusion

To validate function parameters effectively:

  1. Use isinstance for basic type safety to prevent AttributeError or TypeError deep in your code.
  2. Raise ValueError for logical constraints (ranges, patterns).
  3. Use Decorators to abstract repetitive checks (like non-null or positive numbers).
  4. Adopt Pydantic for complex data structures to save time and reduce boilerplate code.
Navigation