I've done some truly amazing things with attrs because of that composability. If I'd wanted the same things with Pydantic, it would have had to be a feature request.
Yes, you can do validation in attrs, but it's not meant to be used the same way as pydantic. For serialization, you need cattrs, which is a completely different package.
If you’re worried about the performance hit of extra crap happening at runtime… dear lord use another programming language.
Dataclasses is just… meh. Pydantic and Attrs just have so many great features, I would never use dataclasses unless someone had a gun to my head to only use the standard library. I don’t know of a single Python project that uses dataclasses where Pydantic or Attrs would do (I’m sure they exist, but I’ve never run across it).
Dataclasses honestly seems very reactionary by the Python devs, since Attrs was getting so popular and used everywhere that it got a little embarrassing for Python that something so obviously needed in the language just wasn’t there. Those that weren’t using Attrs runtime validators often did something similar to Attrs by abusing NamedTuple with type hints. There were tons of “why isnt Attrs in the stdlib” comments, which is an annoying type of comment to make, but it happens. So they added dataclasses, but having all the many features that Attrs has isn’t a very standard-library-like approach, so we got… dataclasses. Like “look, it’s what you wanted, right!?”. Well no not really, thanks we’ll just keep using Attrs and then Pydantic
As I think I made clear in PEP 557, and every time I discuss this with anyone, dataclasses owes a lot to attrs. I think attrs made some great design decisions, in particular to metaclasses or base classes.
Python can be used quite successfully in high-performance environments if you are judicious about how you use it; set performance budgets, measure continuously, make sure to have vectorized interfaces, and have a tool on hand, like PyO3, Cython, or mypyc (you should probably NOT be using C these days, even if "rewrite in C" is the way this advice was phrased historically) ready to push very hot loops into something with higher performance when necessary. But if you redundantly validate everything's type on every invocation at runtime, it does eventually become untenable for anything but slow batch jobs if you have any significant volume of data.
Attrs just has the features I need for now. It certainly feel a touch verbose but I’m happy to pay the price.
id: UUID = attr.ib(validator=instance_of(UUID), …other parameters)
The type hint helps mypy and pylint work, while the validator is a runtime check by AttrsIf your attribute name is longer or you have other parameters to set, that can be a very long line (or lines) of code that you repeat for every attribute
For any larger program, pervasive type annotations and "compile" time checking with mypy is a really good idea though, which somewhat lessens the need for runtime checking.
I don’t expect any type-related thing to be remotely safe in Python without applying at least mypy and pylint, potentially pyright as well, plus, as always with an interpreted language, unit tests for typing issues that would be caught by a compiler in another language
Using mypy gives you the type safety equivalent of a compiled language. If you're using mypy, you don't need any additional validation that you wouldn't use in java or c++. I didn't downvote you, but the needless defense in depth is weird.
Yeah mypy will get you maybe 90% of the way there, but the swiss cheese approach of stacking a few tools, even though there are redundancies, helps plug almost all of the holes.
People can argue about unit tests all day, but there’s very little cost to stacking multiple static analysis tools.
Lots of love to Attrs, which is a great library and is a component of a lot of great software. It was my go-to library for years before Pydantic matured, but I think a lot of people have rightly started to move on to Pydantic, particularly with the popularity of FastAPI