Hm. It's unfortunate that this would break code using what is *currently* the best practice.

The saving grace seems that for *many* use cases the best practice is to call typing.get_type_hints(). This is particularly useful for classes because it includes annotations from base classes.

Also, for functions and modules I would recommend `getattr(o, "__annotations__", None)` (perhaps with `or {}` added).

I would also honestly discount what and have to do. But what do 3rd party packages do when they don't want to use get_type_hints() and they want to get it right for classes? That would give an indication of how serious we should take breaking current best practice.

On Mon, Jan 18, 2021 at 1:10 PM Larry Hastings <> wrote:

On 1/18/21 12:16 PM, Guido van Rossum wrote:
I do worry about the best practice getting worse if your PEP 649 is accepted.

A good part of what motivated me to start this second thread ("Let's Fix ...") was how much worse best practice would become if PEP 649 is accepted.  But if we accept PEP 649, and take steps to fix the semantics of annotations, I think the resulting best practice will be excellent in the long-run.

Let's assume for a minute that PEP 649 is accepted more-or-less like it is now.  (The name resolution approach is clearly going to change but that won't affect the discussion here.)  And let's assume that we also change the semantics so annotations are always defined (you can't delete them) and they're guaranteed to be either a dict or None.  (Permitting __annotations__ to be None isn't settled yet, but it's most similar to current semantics, so let's just assume it for now.)

Because the current semantics are kind of a mess, most people who examine annotations already have a function that gets the annotations for them.  Given that, I really do think the best approach is to gate the code on version 3.10, like I've described before:

if python version >= 3.10:
    def get_annotations(o):
        return o.__annotations__
    def get_annotations(o):
        if isinstance(o, (type, types.ModuleType)):
            return o.__dict__.get("__annotations__", None)
            return o.__annotations__

This assumes returning None is fine.  If it had to always return a valid dict, I'd add "or {}" to the end of every return statement.

Given that it already has to be a function, I think this approach is readable and performant.  And, at some future time when the code can drop support for Python < 3.10, we can throw away the if statement and the whole else block, keeping just the one-line function.  At which point maybe we'd refactor away the function and just use "o.__annotations__" everywhere.

I concede that, in the short term, now we've got nine lines and two if statements to do something that should be relatively straightforward--accessing the annotations on an object.  But that's where we find ourselves.  Current best practice is kind of a mess, and unfortunately PEP 649 breaks current best practice anyway.  My goal is to fix the semantics so that long-term best practice is sensible, easy, and obvious.



--Guido van Rossum (
Pronouns: he/him (why is my pronoun here?)