PEP 649 states:
The semantics established in this PEP also hold true when executing code in Python’s interactive REPL shell, except for module annotations in the interactive module (
__main__
) itself. Since that module is never “finished”, there’s no specific point where we can compile the__annotate__
function.
For the sake of simplicity, in this case we forego delayed evaluation. Module-level annotations in the REPL shell will continue to work exactly as they do with “stock semantics”, evaluating immediately and setting the result directly inside the__annotations__
dict.
I implemented this behavior:
>>> x: int = 3
>>> __annotations__
{'x': <class 'int'>}
>>> class X:
... y: doesntexist
...
>>> X.__annotations__
Traceback (most recent call last):
File "<python-input-4>", line 1, in <module>
X.__annotations__
File "<python-input-2>", line 2, in <annotations of X>
y: doesntexist
^^^^^^^^^^^
NameError: name 'doesntexist' is not defined
But I’m not sure it’s a good idea. The interactive console would be the only place in the language that eagerly evaluates annotations, and it seems confusing to have core language behavior that differs based on whether the “interactive” compilation mode was used.
If there are multiple statements in a line, the new 3.13 REPL actually compiles all but the final statement in exec
(i.e., module) mode, so my current code applies PEP 649 semantics to them.
>>> x: x = 3; pass
>>> __annotate__(1)
{'x': 3}
There is surely some way to fix that and make all of the REPL code get compiled as “interactive”, but it shows how subtle this behavior change is.
Another implication of the proposed behavior is that pasting code into the REPL line by line will sometimes not work:
x: X | None = None
class X: pass
With PEP 649 applied, this code will work fine when placed in a module. But if you paste it into the console line by line, the first line will throw an error.
Here’s an alternative proposal: treat the interactive console like any other module-level code, and make annotations lazily evaluated. This makes the language more consistent and avoids subtle behavior changes between modules and the REPL.