Abusing function annotations to add checks
In my ongoing quest to torture the Python language, I came across an interesting project yesterday: Zach Mitchell describes in a blog post how he abused type annotations to build what he calls “macros” (inspired by Rust): Annotating class members to restrict them to certain values. He uses the “new” type annotation feature of Python, with strings:
This defines a class with a property called var
that is only allowed to be set
to a value between 0 and 10.
After having presented this idea at a lightning talk, during discussion he got another idea: Using lambdas instead of strings:
As far as I know, this second part remains unimplemented.
When I saw this, I had an thought: Can we do what he proposed… but drop the lambda? At first thought, that seems impossible, or rather impractical:
While the above is syntactically valid, it will crash with a NameError
,
unless one first defines foo
somewhere else, the reason being that the
annotation has to be evaluated before it is attached to the variable.
My first thought was to use some special name that takes the role of any variable, e.g.:
While this would work, it seemed needlessly restrictive. Then I had another
idea: PEP 563 (“Postponed Evaluation of Annotations”). I know about it, because
it breaks several hacky things I have written in the past (namely
Aceto and
OIL). Essentially, one can use a future
import (annotations
) in order to stop the evaluation of function annotations
(all of them will become strings instead). Instead, they are attached in string
form to the __annotations__
attribute of a class or function. Importantly
however, they still have to be valid Python syntax, and are parsed normally.
It is only during evaluation that they are converted to strings.
The only missing piece to actually do something with it was a way to get the
code of the class we’ll decorate: Unlike functions, classes have no __code__
attribute.
Luckily, there’s a built-in module that’s always there for you when the others
give up: inspect
.
And finally, it worked:
If you want to see exactly how it works, then sit down at go here.