On 4/18/21 9:14 AM, Richard Levasseur wrote:
Alternatively: what if the "trigger" to resolve the expression to an
object was moved from a module-level setting to the specific
expression? e.g.
def foo(x: f'{list[int]}') -> f'{str}':
bar: f'{tuple[int]}' = ()
@pydantic_or_whatever_that_needs_objects_from_annotations
class Foo:
blah: f'{tuple[int]}' = ()
I picked f-strings above since they're compatible with existing syntax
and visible to the AST iirc; the point is some syntax/marker at the
annotation level to indicate "eagerly resolve this / keep the value at
runtime". Maybe "as", or ":@", or a "magic"
@typing.runtime_annotations decorator, or some other bikeshed etc. (As
an aside, Java deals with this problem by making its annotations
compile-time only unless you mark them to be kept at runtime)
I genuinely don't understand what you're proposing. Could you elaborate?
I will note however that your example adds a lot of instances of quoting
and curly braces and the letter 'f'. Part of the reason that PEP 563
exists is that users of type hints didn't like quoting them all the
time. Also, explicitly putting quotes around type hints means that
Python didn't examine them at compile-time, so outright syntax errors
would not be caught at compile-time. PEP 563 meant that syntax errors
would be caught at compile-time. (Though PEP 563 still delays other
errors, like NameError and ValueError, until runtime, the same way that
PEP 649 does.)
The reasons I suggest this are:
1. A module-level switch reminds me of __future__.unicode_literals.
Switching that on/off was a bit of a headache due to the action at a
distance.
__future__.unicode_literals changed the default behavior of strings so
that they became Unicode. An important part of my proposal is that it
minimizes the observable change in behavior at runtime. PEP 563 changes
"o.__annotations__" so that it contains stringized annotations, my
proposal changes that so it returns real values, assuming eval() succeeds.
What if the eval() fails, with a NameLookup or whatever? Yes, this
would change observable behavior. Without the compile-time flag
enabled, the annotation fails to evaluate correctly at import time.
With the compile-time flag enabled, the annotation fails to evaluate
correctly at the time it's examined. I think this is generally a
feature anyway. As you observe in the next paragraph, the vast majority
of annotations are unused at runtime. If a program didn't need an
annotation at runtime, then making it succeed at import time for
something it doesn't care about seems like a reasonable change in
behavior. The downside is, nested library code might make it hard to
determine which object had the bad annotation, though perhaps we can
avoid this by crafting a better error message for the exception.
2. It's my belief that the /vast /majority of annotations are unused
at runtime, so all the extra effort in resolving an annotation
expression is just wasted cycles. It makes sense for the default
behavior to be "string annotations", with runtime-evaluation/retention
enabled when needed.
The conversion is lazy. If the annotation is never examined at runtime,
it's left in the state the compiler defined it in. Where does it waste
cycles?
Cheers,
//arry/
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at
https://mail.python.org/archives/list/python-dev@python.org/message/ZJ6YFDWABERFXKI2DVWEEHYGW7DY6G6W/
Code of Conduct: http://python.org/psf/codeofconduct/