Files
gh-jamie-bitflight-claude-s…/skills/python3-development/references/modern-modules/attrs.md
2025-11-29 18:49:58 +08:00

14 KiB

title, library_name, pypi_package, category, python_compatibility, last_updated, official_docs, official_repository, maintenance_status
title library_name pypi_package category python_compatibility last_updated official_docs official_repository maintenance_status
attrs: Python Classes Without Boilerplate attrs attrs dataclasses 3.9+ 2025-11-02 https://www.attrs.org https://github.com/python-attrs/attrs active

attrs: Python Classes Without Boilerplate

Core Purpose

attrs eliminates the drudgery of implementing object protocols (dunder methods) by automatically generating __init__, __repr__, __eq__, __hash__, and other common methods. It predates Python's built-in dataclasses (which was inspired by attrs) and offers more features and flexibility.

What problem does it solve?

  • Removes repetitive boilerplate code for class definitions
  • Provides declarative attribute definitions with validation and conversion
  • Offers slots, frozen instances, and performance optimizations
  • Enables consistent, correct implementations of comparison and hashing

This prevents "reinventing the wheel" by:

  • Auto-generating special methods that are error-prone to write manually
  • Providing battle-tested validators and converters
  • Handling edge cases in equality, hashing, and immutability correctly
  • Offering extensibility through field transformers and custom setters

Official Information

Python Version Compatibility

  • Minimum: Python 3.9+ (@source: PyPI metadata)
  • Maximum: Python 3.14 (tested and supported)
  • PyPy: Fully supported
  • Feature notes:
    • Supports slots by default in modern API (@define)
    • Works with all mainstream Python versions including PyPy
    • Implements cell rewriting for super() calls in slotted classes
    • Compatible with functools.cached_property on slotted classes

Installation

pip install attrs

For serialization/deserialization support:

pip install attrs cattrs

Core Usage Patterns

1. Basic Class Definition (Modern API)

from attrs import define, field

@define
class Point:
    x: int
    y: int

# Automatically generates __init__, __repr__, __eq__, etc.
p = Point(1, 2)
print(p)  # Point(x=1, y=2)
print(p == Point(1, 2))  # True

(@source: Context7 /python-attrs/attrs documentation, attrs README)

2. Default Values and Factories

from attrs import define, field, Factory

@define
class SomeClass:
    a_number: int = 42
    list_of_numbers: list[int] = Factory(list)

# Factory prevents mutable default gotchas
sc1 = SomeClass()
sc2 = SomeClass()
sc1.list_of_numbers.append(1)
print(sc2.list_of_numbers)  # [] - separate instances

(@source: attrs README, Context7 documentation examples)

3. Validators

from attrs import define, field, validators

@define
class User:
    email: str = field(validator=validators.matches_re(
        r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
    ))
    age: int = field(validator=[
        validators.instance_of(int),
        validators.ge(0),
        validators.lt(150)
    ])

# Custom validator with decorator
@define
class BoundedValue:
    x: int = field()
    y: int

    @x.validator
    def _check_x(self, attribute, value):
        if value >= self.y:
            raise ValueError("x must be smaller than y")

(@source: Context7 /python-attrs/attrs validators documentation)

4. Converters

from attrs import define, field, converters

@define
class C:
    x: int = field(converter=int)

c = C("42")
print(c.x)  # 42 (converted from string)

# Optional converter
@define
class OptionalInt:
    x: int | None = field(converter=converters.optional(int))

OptionalInt(None)  # Valid
OptionalInt("42")  # Converts to 42

(@source: Context7 /python-attrs/attrs converters documentation)

5. Frozen (Immutable) Classes

from attrs import frozen, field

@frozen
class Coordinates:
    x: int
    y: int

c = Coordinates(1, 2)
# c.x = 3  # Raises FrozenInstanceError

# Post-init with frozen classes
@frozen
class FrozenWithDerived:
    x: int
    y: int = field(init=False)

    def __attrs_post_init__(self):
        # Must use object.__setattr__ for frozen classes
        object.__setattr__(self, "y", self.x + 1)

(@source: Context7 /python-attrs/attrs frozen documentation)

6. Slots for Performance

from attrs import define

# Slots enabled by default with @define
@define
class SlottedClass:
    x: int
    y: int

# More memory efficient, faster attribute access
# Cannot add attributes not defined in class

(@source: Context7 /python-attrs/attrs slots documentation, attrs glossary)

7. Without Type Annotations

from attrs import define, field

@define
class NoAnnotations:
    a_number = field(default=42)
    list_of_numbers = field(factory=list)

(@source: attrs README)

Real-World Examples

Example Projects Using attrs

  1. Black - The uncompromising Python code formatter

  2. cattrs - Composable custom class converters

  3. Eradiate - Radiative transfer model

Common Patterns from Real Code

Pattern 1: Deep validation for nested structures

from attrs import define, field, validators

@define
class Measurement:
    tags: dict = field(
        validator=validators.deep_mapping(
            key_validator=validators.not_(
                validators.in_({"id", "time", "source"}),
                msg="reserved tag key"
            ),
            value_validator=validators.instance_of((str, int))
        )
    )

(@source: Context7 /python-attrs/attrs deep_mapping validator documentation)

Pattern 2: Custom comparison for special types

import numpy as np
from attrs import define, field, cmp_using

@define
class ArrayContainer:
    data: np.ndarray = field(eq=cmp_using(eq=np.array_equal))

(@source: Context7 /python-attrs/attrs comparison documentation)

Pattern 3: Hiding sensitive data in repr

from attrs import define, field

@define
class User:
    username: str
    password: str = field(repr=lambda value: '***')

User("admin", "secret123")
# Output: User(username='admin', password=***)

(@source: Context7 /python-attrs/attrs examples)

Integration Patterns

With cattrs for Serialization

from attrs import define
from cattrs import structure, unstructure

@define
class Person:
    name: str
    age: int

# Serialize to dict
data = unstructure(Person("Alice", 30))
# {'name': 'Alice', 'age': 30}

# Deserialize from dict
person = structure({"name": "Bob", "age": 25}, Person)

(@source: python-attrs/cattrs repository, Context7 cattrs documentation)

Field Transformers for Advanced Use Cases

from attrs import define, frozen, field
from datetime import datetime

def auto_convert_datetime(cls, fields):
    results = []
    for f in fields:
        if f.converter is not None:
            results.append(f)
            continue
        if f.type in {datetime, 'datetime'}:
            converter = lambda d: datetime.fromisoformat(d) if isinstance(d, str) else d
        else:
            converter = None
        results.append(f.evolve(converter=converter))
    return results

@frozen(field_transformer=auto_convert_datetime)
class Event:
    name: str
    timestamp: datetime

# Automatically converts ISO strings to datetime
event = Event(name="deploy", timestamp="2025-10-21T10:00:00")

(@source: Context7 /python-attrs/attrs field_transformer documentation)

When to Use attrs

Use attrs when

  • You want more features than dataclasses provide
  • You need robust validation and conversion
  • You require frozen/immutable instances with complex post-init
  • You want extensibility (field transformers, custom setters)
  • You need to support Python 3.9+ with modern features
  • Performance matters (slots optimization)
  • You want better debugging experience (cell rewriting for super())
  • You prefer a mature, battle-tested library (used by NASA)

Use dataclasses when

  • You need stdlib-only solution (no dependencies)
  • Your use case is simple (basic data containers)
  • You don't need validators or converters
  • You're comfortable with limited customization
  • You only support Python 3.10+ (for slots with super())

Use Pydantic when

  • You need runtime type validation (attrs validates on-demand)
  • You're building APIs with automatic schema generation
  • You need JSON Schema / OpenAPI integration
  • You want coercion-heavy validation (Pydantic is more aggressive)
  • You need ORM-like features

Decision Matrix

Feature attrs dataclasses Pydantic
Validators Extensive Manual only Automatic + extensive
Converters Built-in Manual only Automatic coercion
Slots Default in @define 3.10+ only Optional
Frozen Full support Basic support Via Config
Performance Fast (slots) Fast Slower (validation)
Type coercion Opt-in No Automatic
Dependencies Zero Zero (stdlib) Multiple
Extensibility High (transformers) Limited Medium
Python support 3.9+ 3.7+ 3.8+
Schema export Via cattrs No Built-in
API stability Very stable Stable Evolving

(@source: Context7 /python-attrs/attrs comparison with dataclasses, research from comparison articles)

When NOT to Use

  1. Simple data containers without validation

    • If you just need __init__ and __repr__, dataclasses suffice
    • Example: Simple config objects, DTOs without business logic
  2. When you need JSON Schema / OpenAPI integration

    • Pydantic provides this out-of-the-box
    • attrs requires additional libraries (cattrs + schema generators)
  3. Heavy runtime type validation requirements

    • Pydantic validates automatically; attrs requires explicit validators
    • If every field needs type checking at runtime, Pydantic is more convenient
  4. No external dependencies allowed

    • Use dataclasses from stdlib
    • Though attrs has zero dependencies itself
  5. Working with ORMs requiring specific metaclasses

    • Some ORMs conflict with attrs' class generation
    • Check compatibility before adopting

Performance Characteristics

  • Slots: Enabled by default in @define, reducing memory overhead (~40-50% less memory)
  • Frozen classes: Slightly slower instantiation due to immutability checks
  • Validation: Only runs when explicitly called via attrs.validate() or during __init__
  • Comparison: Generated methods are as fast as hand-written equivalents

(@source: Context7 /python-attrs/attrs performance benchmarks)

Common Gotchas

  1. Mutable defaults: Always use Factory for mutable defaults
  2. Frozen post-init: Must use object.__setattr__ in __attrs_post_init__
  3. Slots and dynamic attributes: Cannot add attributes not defined in class
  4. Pickling slotted classes: Attributes with init=False must be set before pickling
  5. Validator order: Converters run before validators

(@source: Context7 /python-attrs/attrs documentation, glossary)

Migration Path

From dataclasses to attrs

# Before (dataclass)
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int = 0

# After (attrs)
from attrs import define

@define
class Point:
    x: int
    y: int = 0

Minimal changes required; attrs is largely a drop-in replacement with more features.

From Pydantic to attrs

# Before (Pydantic)
from pydantic import BaseModel, validator

class User(BaseModel):
    name: str
    age: int

    @validator('age')
    def check_age(cls, v):
        if v < 0:
            raise ValueError('age must be positive')
        return v

# After (attrs + cattrs for serialization)
from attrs import define, field, validators

@define
class User:
    name: str
    age: int = field(validator=[
        validators.instance_of(int),
        validators.ge(0)
    ])

Note: Pydantic does automatic validation; attrs requires explicit calls.

Additional Resources

Conclusion

attrs is the mature, feature-rich choice for defining classes in Python. It predates dataclasses, offers significantly more functionality, and maintains excellent performance through slots optimization. Choose attrs when you need validators, converters, extensibility, or when building production systems requiring robust data structures. It's the foundation used by major projects like Black and is trusted by NASA for critical missions.

For simple cases, dataclasses may suffice. For API validation and schema generation, Pydantic excels. But for general-purpose class definition with powerful features and minimal dependencies, attrs is the gold standard.


Research methodology: Information gathered from official documentation (attrs.org), PyPI metadata, GitHub repository analysis, Context7 code examples, and comparison with alternative libraries. All sources are cited inline with @ references.