If you never examine __annotations__, then you can refer to symbols that are never defined and nothing bad happens.  It's just like writing a function that refers to undefined symbols, then never calling that function.

If you examine __annotations__, and the annotations refer to values that aren't defined, the evaluation fails.  This too works like you'd expect: the __co_annotation__ function raises NameError.  So your IPython use case would raise a NameError.

Note that the code is deliberately written to allow you to fix the name errors and try again.  (The __co_annotations__ attribute is only cleared if calling it succeeds and it returns a legal value.)  So, if you examine an annotation in IPython, and it fails with a NameError, you could import the missing module--or otherwise do what is needed to fix the problem--and try again.

If your imports are complicated, you could always hide them in a function.  I just tried this and it seems to work fine:

def my_imports():
    global other_mod
    import other_mod

So, you could put all your imports in such a function, run it from inside a "if typing.TYPE_CHECKING" block, and you'd have a convenient way of doing all your imports from inside IPython too.

One final note: with PEP 649, you can still use strings as annotations if you prefer.  You just have to write them as strings manually.  So the IPython use case could continue to work correctly that way.  I realize that this itself causes minor problems--it means no syntax checking is done on the annotation, and it causes a little extra work for the user--but I assume this is a rare use case and most users won't need to bother.


Cheers,


/arry


On 1/16/21 11:43 PM, Inada Naoki wrote:
This PEP doesn't cover about what happened when __co_annotation__()
failed (e.g. NameError).

Forward reference is a major reason, but not a only reason for using
string annotation. There are two other reasons:

* Avoid importing heavy module.
* Avoid circular imports.

In these cases, this pattern is used:

```
from __future__ import annotations
import typing
from dataclasses import dataclass

if typing.TYPE_CHECKING:
    import other_mod  # do not want to import actually

@dataclass
class Foo:
    a: other_mod.spam
    b: other_mod.ham

def fun(a: other_mod.spam, b: other_mod.ham) -> None: ...
```

Of course, mypy works well with string annotation because it is static checker.
IPython shows signature well too:

```
In [3]: sample.Foo?
Init signature: sample.Foo(a: 'other_mod.spam', b: 'other_mod.ham') -> None
Docstring:      Foo(a: 'other_mod.spam', b: 'other_mod.ham')
```

PEP 563 works fine in this scenario. How PEP 649 works?

Regards,