attrs: Python Classes Without Boilerplate
Python Classes Without Boilerplate
At a glance
- What is it?
- attrs generates __init__, __repr__ and equality methods from a class decorator and typed fields. It is a mature, MIT-licensed library, but it is not a validation framework, and its last push was on 2026-09-01.
- Who is it for?
- Adopt attrs when you want generated dunder methods, slotted classes and optional type annotations without pulling in a validation engine; skip it if you need coercion and schema validation at the boundary, where pydantic fits better.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What attrs is for, and who should reach for it
Writing a Python class usually means writing the same methods again: an initializer that assigns every argument, a __repr__ that prints something readable, and __eq__ that compares the right fields. attrs exists to remove that work. You declare the attributes on a class, apply a decorator, and the library writes those methods for you. The README describes the goal as helping you write concise and correct software without slowing down your code, and the project's own description is simply classes without boilerplate.
The audience is ordinary application and library developers who already model data as objects. The README gives a compact example of the shape: a class decorated with @define, two annotated attributes, one with a default of 42 and one using Factory(list), plus a normal method that does arithmetic on those attributes. Instances compare equal when their fields match, repr prints the field values, and asdict turns an instance into a plain dictionary.
One detail worth noticing for anyone with older code: the README states that the modern APIs introduced in version 20.1.0 and the attrs import name added in version 21.3.0 sit alongside the classic @attr.s and attr.ib APIs, and that the classic APIs and the attr package import name will remain indefinitely. That is a deliberate compatibility promise, and it means a codebase written years ago does not have to be rewritten to keep working.
How the decorator and generated methods actually work
attrs is a code-generation library, not a runtime container. You hand it a class and a list of fields; it hands back the same class with methods attached. The field list comes from either type annotations or explicit attrs.field() calls, and the README presents those as equivalent choices rather than a preferred style. The example for people who dislike annotations assigns attrs.field() with default=42 and factory=list, which produces the same behaviour as the annotated version.
The generated surface, as the README lists it, is an initializer, a human-readable __repr__, equality-checking methods, and what it calls a concise and explicit overview of the class's attributes. Because the methods are generated at class creation time rather than resolved through attribute lookup on every call, there is no runtime performance penalty according to the README's own claim.
The project does not sit still on the language's own features. The README states that dataclasses are a descendant of attrs, and it points at a comparison page for the differences. Among the ones it names directly: special handling of NumPy arrays for equality checks, more ways to plug into the initialization process, a replacement for __init_subclass__, and the ability to step through the generated methods in a debugger. That last point matters in practice. Generated code that you cannot single-step is generated code you cannot debug when a field is assigned the wrong value.
The package has no runtime dependencies, per its pyproject.toml, and it declares support for CPython and PyPy across Python 3.10 through 3.15.
Installing attrs and defining a first class
attrs is published on PyPI as attrs, and the project's requires-python setting is >=3.10, so check your interpreter before anything else. The install is a single pip command with no extra dependencies pulled in.
pip install attrsAfter that, import from the attrs package name and decorate a class. The README's own example defines two fields, one with a plain default and one with a factory, then calls a method on the instance.
from attrs import asdict, define, Factory
@define
class SomeClass:
a_number: int = 42
list_of_numbers: list[int] = Factory(list)
def hard_math(self, another_number):
return self.a_number + sum(self.list_of_numbers) * another_numberConstructing SomeClass(1, [1, 2, 3]) and calling hard_math(3) returns 19, according to the README's session. Two instances built from the same values compare equal, and asdict returns {'a_number': 1, 'list_of_numbers': [1, 2, 3]}. Calling SomeClass() with no arguments produces SomeClass(a_number=42, list_of_numbers=[]). The factory matters here: a mutable default shared across instances would be a bug, and Factory(list) gives each instance its own list.
If you would rather not annotate, the README shows the same class written with attrs.field() instead, using default=42 and factory=list. If you need a class generated from a list of names at runtime rather than written out, the README demonstrates make_class, which takes a name and a list of attribute names and returns a class.
Where attrs stops and validation begins
The honest limitation is in the name of the category. attrs is about class mechanics: construction, representation, comparison, and the plumbing around initialization. It is not a data validation framework, and nothing in the README positions it as one. If your problem is untrusted JSON arriving over HTTP and needing to be coerced into typed objects with clear error messages, attrs is the wrong layer of the stack.
The README does mention plugging into the initialization process and does show validators in the wider ecosystem, but the primary framing is the removal of boilerplate, not the enforcement of schemas. A team that adopts attrs expecting pydantic-style parsing behaviour will spend its first week discovering the gap.
There is a second, quieter constraint: the modern API is recent relative to the library's history. Version 20.1.0 introduced it and version 21.3.0 added the attrs import name, both of which are years old at this point but still newer than the classic API that a lot of existing code and documentation uses. Mixed codebases will contain both spellings, and reviewers need to know that this is intentional rather than drift.
Finally, the optionality of types cuts both ways. The README is explicit that types are entirely optional, which is friendly to dynamic code and less friendly to teams that want the type checker to be the source of truth about a class's shape.
attrs versus dataclasses and pydantic
The most direct alternative is the standard library's dataclasses module, and the relationship is not a rivalry so much as a lineage: the README states plainly that dataclasses are a descendant of attrs. The practical difference is that attrs offers more hooks. The README lists NumPy-aware equality, more ways to plug into initialization, a replacement for __init_subclass__, and debugger-steppable generated methods as things attrs does that the standard library approach does not do in the same way. If you need none of those, dataclasses costs nothing and adds no dependency, which is a real argument in a small project.
The other common comparison is pydantic, and the difference is one of purpose rather than quality. attrs generates methods for classes you define; pydantic is built around validation and parsing of external data. The README does not discuss pydantic, so any claim about relative speed or behaviour would be guesswork. The honest framing is that they overlap on the surface (both produce classes with typed fields) and diverge at the boundary, where attrs leaves validation to you and pydantic does not.
A third option in the same space is msgspec, which also appears in search traffic around this project but is not mentioned anywhere in the README. Without documentation to compare against, the only defensible statement is that attrs makes no performance claims beyond the README's assertion of no runtime penalties, and that assertion is about generated methods, not about serialization throughput.
Maintenance, releases and the MIT licence
The repository is not archived, and the last push was on 2026-09-01, which is recent. The release cadence visible in the release list is roughly twice a year: 26.1.0 on 2026-03-19, 25.4.0 on 2025-10-06, and 25.3.0 on 2025-03-13. That is a slow, deliberate rhythm rather than a constant stream, which suits a library whose job is to keep generated code stable.
Upgrade cost is low by design. The README commits to keeping the classic @attr.s and attr.ib APIs and the attr import name indefinitely, so an upgrade does not force a migration. The requires-python floor of 3.10 is the constraint most likely to bite, and it will rise over time as the classifiers in pyproject.toml show support extending to Python 3.15.
The licence is MIT, declared in pyproject.toml with an SPDX identifier and a license-files entry pointing at LICENSE. The package has no runtime dependencies, which keeps the licence surface small: you are not inheriting the terms of a transitive dependency by installing it. The project also lists a Tidelift subscription for enterprise support, and the README asks readers to consider sponsoring maintenance. None of this changes the MIT terms, and none of it is legal advice; teams with strict licence review processes should read LICENSE themselves.
Editorial conclusion
Adopt attrs when you want generated dunder methods, slotted classes and optional type annotations without pulling in a validation engine; skip it if you need coercion and schema validation at the boundary, where pydantic fits better. Before committing, check the requires-python floor of 3.10 against your runtime, read the comparison page on why.html for the dataclasses differences, and confirm that the field-level behaviour you need (converters, validators, slots) is covered by the stable documentation.
Frequently asked questions
What is attrs in Python?
attrs is a Python package that generates the boilerplate methods on a class, including an initializer, __repr__ and equality checks, from a decorator and a list of declared fields. Its stated goal is concise and correct software without slowing down your code.
How do you use attrs?
Decorate a class with @define from the attrs package, declare attributes either as type annotations or with attrs.field(), and the library supplies the initializer, repr and equality methods. The README's example also shows asdict for converting an instance to a dictionary and make_class for generating a class from a list of names.
How does attrs compare to dataclasses?
The README states that dataclasses are a descendant of attrs, and that attrs does more in practice. It names special handling of NumPy arrays for equality, more ways to plug into initialization, a replacement for __init_subclass__, and the ability to step through generated methods with a debugger.
How does attrs compare to pydantic?
The README does not discuss pydantic, so no direct comparison is documented. What is documented is that attrs focuses on generating class methods from declared fields, while validation of external data is outside the scope it describes for itself.
What is attrs?
In this project's context, attrs is a Python library that removes class boilerplate by generating dunder methods from declared attributes. The README describes it as the Python package that brings back the joy of writing classes.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/python-attrs-attrs)