I know how decorators work and you might be missing the point I was making by a fair mile
The following don't mutate the mental map of how the function works, its "this function is still what I typed":
"add logging" "add tracing" "register functions/objects for various reasons"
The following act as a filtering role, the inside isn't "changed", the mental map becomes "This function will operate within this feature in some way, but otherwise is still what I typed":
"enable type validation" "change output formats to json/xml/yaml" "swap out implementations for global functions/vars" (assuming the implementations are comparable)
The following is "What I've typed is not a proper representation, and some things may act differently than usual" e.g. things that can take lists can no longer, due to needing to be serializable (and the least common decorator case):
"take a class, inspect a database schema and bind various internal attributes to update items in a row for that schema (somewhat common in ORMs)"
(and that is usually implemented via a subclassing of a Model class, which then in turn can implement a metaclass, to get the full mutate-class-on-creation funs, iirc)
- - -
> In particular, I don't see why mutating a type via a metaclass is preferable to using a decorator.
Its not a technical topic, its a semantics topic. while both can do whatever silliness they want, decorators are usually reserved for incidental tasks on the side, and wrapping the original function in some light filtering. Metaclasses are expected to do mutation to the class (hence requiring a metaclass), so semantically a subclass or metaclass would be more appropriate to use for this task, compared to a decorator (from the standard library, Enums come to mind as an example for this)