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/

Reply via email to