PEP 649: Deferred evaluation of annotations, tentatively accepted

I realized I failed to accurately state this, I do not think PEP-563 is correct solution either. I think it carries almost entirely the same issues as this pep does, and in many places even worse issues. I work with pydantic daily and have experienced the subtleties it carries. I use active type hints all the time and design a lot of code around them. I wrote a decently sized post on how you can solve large chunks of this problem in a backportable way a few months ago. I was mostly focused on preserving back portability. As well as some other active typing issues. And some ugly design choices were made around that.

Of the main problems that are trying to be resolved. The first is Forward References in some flavor of self referential or defined later. Those as you pointed out are very common and the current default work around of stringifying it is not great. Frankly it also is a major limiting factor in annotations as u can not use ACTUAL literals easily in annotations.

To my understanding of Python bytecode, it should be reasonably simple to create a new bytecode instruction that can be used by annotations, in place of LOAD_NAME, that can lookup the name and if a name error is raised simply create a forwardRef, append it to the globals dict and return the value. That would resolve all the current forward reference issues and be deterministic. Some version of this (like carls above suggestion) has already been determined to be a required by this pep anyway.

For the second issue, my link above has a nice and “clean” design for dealing with circular references/import deferrals from pure python. It also would allow you to preserve import information at runtime, pep 649 does not. A pure python version is crazy hacky but sorta “just works”.

I am going to say there is really a “third” issue, is assigning and evaluating of annotations, that is IMO what is trading determinism for performance. This is where PEP-649 and 563 are not behaving like I expect python code to behave.

I also think there is an easy half way between solution where names in complex annotations are captured eagerly and the expression is evaluated lazily. That would preserve the current default annotation capture behavior and does not have the issue of changing depending on when u look at it. Depending on the mixture of class to function annotations the size of this in memory may end up being smaller than keeping the class namespace alive anyway. And does not interact with metaprogramming modifying the namespace.

2 Likes