On 10/21/21 1:17 AM, Steven D'Aprano wrote:
On Thu, Oct 21, 2021 at 04:48:28PM -0400, Larry Hastings wrote:

In Python, if you evaluate an undefined name, Python raises a 
NameError.  This is so consistent I'm willing to call it a "rule".  
Various folks have proposed an exception to this "rule": evaluating an 
undefined name in an PEP 649 delayed annotation wouldn't raise 
NameError, instead evaluating to some yet-to-be-determined value 
(ForwardRef, AnnotationName, etc).  I don't think annotations are 
special enough to "break the rules" in this way.
Can we have a code snippet illustrating that? I think this is what you 
mean. Please correct me if I have anything wrong.

If I have this:

    from typing import Any
    def function(arg:Spam) -> Any: ...

then we have four sets of (actual or proposed) behaviour:

1. The original, current and standard behaviour is that Spam raises a 
NameError at function definition time, just as it would in any other 
context where the name Spam is undefined, e.g. `obj = Spam()`.

2. Under PEP 563 (string annotations), there is no NameError, as the 
annotations stay as strings until you attempt to explicitly resolve them 
using eval. Only then would it raise NameError.

3. Under PEP 649 (descriptor annotations), there is no NameError at 
function definition time, as the code that resolves the name Spam (and 
Any for that matter) is buried in a descriptor. It is only on inspecting 
`function.__annotations__` at runtime that the code in the descriptor is 
run and the name Spam will generate a NameError.

4. Guido would(?) like PEP 649 to be revised so that inspecting the 
annotations at runtime would not generate a NameError. Since Spam is 
unresolvable, some sort of proxy or ForwardRef (or maybe just a 
string?) would have to be returned instead of raising.

Am I close?


Your description of the four behaviors is basically correct.


So if I have understood the options correctly, I like the idea of a 
hybrid descriptor + stringy annotations solution.

- defer evaluation of the annotations using descriptors (PEP 649);

- on runtime evaluation, if a name does not resolve, stringify it (as 
PEP 563 would have done implicitly);

- anyone who really wants to force a NameError can eval the string.


You might also be interested in my "Great Compromise" proposal from back in April:

https://mail.python.org/archives/list/python-dev@python.org/thread/WUZGTGE43T7XV3EUGT6AN2N52OD3U7AE/

Naturally I'd prefer PEP 649 as written.  The "compromise" I described would have the same scoping limitations as stringized annotations, one area where PEP 649 is a definite improvement.


Cheers,


/arry