I understand that spooky action-at-a-distance magic can really ruin a codebase's maintainability. But what you've developed here is not a judicious appreciation of its danger and its power, but a blanket aversion to both its risks and its benefits.
An apt metaphor would be, let's say: toast. If you try to make some toast, but accidentally burn your house down, it's understandable that you might want to have your bread un-toasted for a while. But it doesn't make sense to switch your diet to be entirely raw as a result, especially if you eat a lot of chicken.
In python, metaprogramming is like fire. People who haven't seen it before are fascinated before it, and try to touch it. This always goes badly. But that doesn't mean it's useless or we shouldn't have it. There are many things it's good for, if it's carefully and thoughtfully applied.
attrs goes out of its way to avoid any behind-the-scenes effects. It even generates Python code, rather than using setattr(), so that if any error happens in the constructor, you can step through it normally in the debugger, and see it in the traceback. Its semantics are clear and direct. It doesn't have these problems.
I like toast, and a toaster is better than a folded wire clothes hanger perched over the stove burner, but this robot toast-o-matic is too fancy for my kitchen at work. We gotta toast that toast and ain't nobody got time to debug some fancy toaster.
:-) Metaphors. Heh.
Let me reiterate the first part of my statement above, to wit: "this is wonderful and cute as hell".
I like it.
I could use it if it generated complete code for the classes you define. That would actually put to rest my "metaprogramming BAD" problem in this case.
Tucking __init__ source in linecache for the debugger is neat but it's also more magic. You might wind up stepping through code that doesn't exist in any file. Is this wonderful hacky weirdness mentioned in the docs? I didn't see it.
And what about __repr__? No precomputed string template?
Don't imagine that I'm crouching in the dirt tentatively reaching out to the monolith of metaprogramming. I'm into it. I read GvR's "The Killing Joke" for fun. But he called it that for a reason. :-)
I still recall a junior dev slamming into a wall trying to extend a Django HTML form widget. The reason? The cowboys over at Django made it with a metaclass.
We should look at a problem and think, "What's the simplest thing that will work?" Or, more to the point, "Does this problem require metaprogramming to solve?" ("or am I just showing off?" could be the rest of that question...)
HTML form widgets don't require metaclasses. The problem attrs solves doesn't require metaprogramming. It's a simple DSL for data models. Right? Riiiight?
If attrs took in a spec (as Python code or whatever) and emitted all your boilerplate, ready to go, I would love it so much. You would get all of the benefits without any of the downside. I know that's boring and not sexy or fun, but I just want to get this bug fixed and ship it and get on with my life. I'll play with attrs at home, on my own time, but I'm sick of magic at work.
I understand that spooky action-at-a-distance magic can really ruin a codebase's maintainability. But what you've developed here is not a judicious appreciation of its danger and its power, but a blanket aversion to both its risks and its benefits.
An apt metaphor would be, let's say: toast. If you try to make some toast, but accidentally burn your house down, it's understandable that you might want to have your bread un-toasted for a while. But it doesn't make sense to switch your diet to be entirely raw as a result, especially if you eat a lot of chicken.
In python, metaprogramming is like fire. People who haven't seen it before are fascinated before it, and try to touch it. This always goes badly. But that doesn't mean it's useless or we shouldn't have it. There are many things it's good for, if it's carefully and thoughtfully applied.
attrs goes out of its way to avoid any behind-the-scenes effects. It even generates Python code, rather than using setattr(), so that if any error happens in the constructor, you can step through it normally in the debugger, and see it in the traceback. Its semantics are clear and direct. It doesn't have these problems.