The heart of the debate between PEPs 563 and 649 is the question: what
should an annotation be? Should it be a string or a Python value? It
seems people who are pro-PEP 563 want it to be a string, and people who
are pro-PEP 649 want it to be a value.
Actually, let me amend that slightly. Most people who are pro-PEP 563
don't actually care that annotations are strings, per se. What they
want are specific runtime behaviors, and they get those behaviors when
PEP 563 turns their annotations into strings.
I have an idea--a rough proposal--on how we can mix together aspects of
PEP 563 and PEP 649. I think it satisfies everyone's use cases for both
PEPs. The behavior it gets us:
* annotations can be stored as strings
* annotations stored as strings can be examined as strings
* annotations can be examined as values
The idea:
We add a new type of compile-time flag, akin to a "from __future__"
import, but not from the future. Let's not call it "from __present__",
for now how about "from __behavior__".
In this specific case, we call it "from __behavior__ import
str_annotations". It behaves much like Python 3.9 does when you say
"from __future__ import annotations", except: it stores the dictionary
with stringized values in a new member on the function/class/module
called "__str_annotations__".
If an object "o" has "__str_annotations__", set, you can access it and
see the stringized values.
If you access "o.__annotations__", and the object has
"o.__str_annotations__" set but "o.__annotations__" is not set, it
builds (and caches) a new dict by iterating over o.__str_annotations__,
calling eval() on each value in "o.__str_annotations__". It gets the
globals() dict the same way that PEP 649 does (including, if you compile
a class with str_annotations, it sets __globals__ on the class). It
does /not/ unset "o.__str_annotations__" unless someone explicitly sets
"o.__annotations__". This is so you can write your code assuming that
"o.__str_annotations__" is set, and it doesn't explode if somebody
somewhere ever looks at "o.__annotations__". (This could lead to them
getting out of sync, if someone modified "o.__annotations__". But I
suspect practicality beats purity here.)
This means:
* People who only want stringized annotations can turn it on, and only
ever examine "o.__str_annotations__". They get the benefits of PEP
563: annotations don't have to be valid Python values at runtime,
just parseable. They can continue doing the "if TYPE_CHECKING:"
import thing.
* Library code which wants to examine values can examine
"o.__annotations__". We might consider amending library functions
that look at annotations to add a keyword-only parameter,
"str_annotations=False", and if it's true it uses
o.__str_annotations__ instead etc etc etc.
Also, yes, of course we can keep the optimization where stringized
annotations are stored as a tuple containing an even number of strings.
Similarly to PEP 649's automatic binding of an unbound code object, if
you set "o.__str_annotations__" to a tuple containing an even number of
strings, and you then access "o.__str_annotations__", you get back a dict.
TBD: how this interacts with PEP 649. I don't know if it means we only
do this, or if it would be a good idea to do both this and 649. I just
haven't thought about it. (It would be a runtime error to set both
"o.__str_annotations__" and "o.__co_annotations__", though.)
Well, whaddya think? Any good?
I considered calling this "PEP 1212", which is 563 + 649,
//arry/
should an annotation be? Should it be a string or a Python value? It
seems people who are pro-PEP 563 want it to be a string, and people who
are pro-PEP 649 want it to be a value.
Actually, let me amend that slightly. Most people who are pro-PEP 563
don't actually care that annotations are strings, per se. What they
want are specific runtime behaviors, and they get those behaviors when
PEP 563 turns their annotations into strings.
I have an idea--a rough proposal--on how we can mix together aspects of
PEP 563 and PEP 649. I think it satisfies everyone's use cases for both
PEPs. The behavior it gets us:
* annotations can be stored as strings
* annotations stored as strings can be examined as strings
* annotations can be examined as values
The idea:
We add a new type of compile-time flag, akin to a "from __future__"
import, but not from the future. Let's not call it "from __present__",
for now how about "from __behavior__".
In this specific case, we call it "from __behavior__ import
str_annotations". It behaves much like Python 3.9 does when you say
"from __future__ import annotations", except: it stores the dictionary
with stringized values in a new member on the function/class/module
called "__str_annotations__".
If an object "o" has "__str_annotations__", set, you can access it and
see the stringized values.
If you access "o.__annotations__", and the object has
"o.__str_annotations__" set but "o.__annotations__" is not set, it
builds (and caches) a new dict by iterating over o.__str_annotations__,
calling eval() on each value in "o.__str_annotations__". It gets the
globals() dict the same way that PEP 649 does (including, if you compile
a class with str_annotations, it sets __globals__ on the class). It
does /not/ unset "o.__str_annotations__" unless someone explicitly sets
"o.__annotations__". This is so you can write your code assuming that
"o.__str_annotations__" is set, and it doesn't explode if somebody
somewhere ever looks at "o.__annotations__". (This could lead to them
getting out of sync, if someone modified "o.__annotations__". But I
suspect practicality beats purity here.)
This means:
* People who only want stringized annotations can turn it on, and only
ever examine "o.__str_annotations__". They get the benefits of PEP
563: annotations don't have to be valid Python values at runtime,
just parseable. They can continue doing the "if TYPE_CHECKING:"
import thing.
* Library code which wants to examine values can examine
"o.__annotations__". We might consider amending library functions
that look at annotations to add a keyword-only parameter,
"str_annotations=False", and if it's true it uses
o.__str_annotations__ instead etc etc etc.
Also, yes, of course we can keep the optimization where stringized
annotations are stored as a tuple containing an even number of strings.
Similarly to PEP 649's automatic binding of an unbound code object, if
you set "o.__str_annotations__" to a tuple containing an even number of
strings, and you then access "o.__str_annotations__", you get back a dict.
TBD: how this interacts with PEP 649. I don't know if it means we only
do this, or if it would be a good idea to do both this and 649. I just
haven't thought about it. (It would be a runtime error to set both
"o.__str_annotations__" and "o.__co_annotations__", though.)
Well, whaddya think? Any good?
I considered calling this "PEP 1212", which is 563 + 649,
//arry/