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)
Just a data point: MacroPy uses the decorator syntax for "case" classes that produce objects with a similar behavior to "attr.s" generated objects https://github.com/lihaoyi/macropy
A few weeks ago, someone posted an article where the author showed how they could "inline" functions by playing with the AST. That was done to increase performance for the case of very simple functions along the lines of:
def mul_by_pi(x):
return x * 3.14
(I'm sure there are better examples, but that was the idea). I've seen production code where methods and functions are used to provide bitmasking with a class-dependent mask, for example (where the mask is a const, not an attribute), which would benefit from inlining.
From what I've seen, it seems MacroPy could do that, but it's not a direct example anywhere, and it seems they haven't tested it. Has it been done before?
My other problem is that the client is extremely averse of adding any new dependency (which is understandable), but I might be able to convince them if I can show a significant speedup on inlined code. The previous post I mentioned isn't a viable alternative because only "toy code" (as the author said) was provided, and in my team we aren't knowledgable enough in AST handling to feel comfortable hacking it together, but a more mature project like MacroPy definitely looks viable.
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)