PEP649 means that PEP563 will see *more* usage, not less, and will break runtime typecheckers

Hello! I’m sitting at the PyCon sprints with @Jelle, who is now working on implementing PEP 649 for Python 3.14 (since it didn’t happen in time for Python 3.13.) We wanted to follow up on this thread to ensure the concerns raised here (and in the linked discussion at The First Annual Meeting of the Extraordinary League of Runtime Typers · beartype/beartype · Discussion #335 · GitHub) are at least addressed clearly.

I’ll attempt to summarize each concrete concern that I was able to glean from reading this thread and the linked GitHub discussion (in quotes to clarify that these are my attempted summary, not my own assertions), and offer my thoughts on each:

  1. “PEP 649 will increase usage of PEP 563 in the short term, because Python library authors will start expecting bare forward references to work, and the only way to make them work on older Python versions is PEP 563.”

This may be true; it’s hard to predict the extent of this effect. If someone wasn’t willing to use PEP 563 to get bare forward references in Python 3.12 or 3.13 today, it’s not clear why the addition of bare forward references support in 3.14 would suddenly change their mind.

It seems the only concrete proposal that’s been made to address this is to backport PEP 649 to older Python versions. I think there is zero chance of this, for good reasons.

If we introduce PEP 649 under a __future__ import, and don’t make it default until the oldest supported Python has that __future__ import, that would minimize this effect: bare forward-refs would continue to be an experimental opt-in-only feature until PEP 563 is no longer needed for them at all. But this comes at the cost of lengthening the whole transition period (a LOT – we wouldn’t be able to make PEP 649 the default behavior until 2029); everyone would need to consider three possible annotation semantics for the next several Python feature releases. Do we want to rip off the band-aid with more short-term pain, or spread the pain out over years to come? (Related recent discussion in PEP 649: Deferred evaluation of annotations, tentatively accepted - #44 by Jelle).

  1. “PEP 649 should default to FORWARDREFS format, rather than VALUES format, to avoid causing NameErrors in runtime-checking decorators when people start using bare forward-refs in their annotations.”

This is framed as a compatibility concern, but I think compatibility is precisely the reason we can’t do this. Existing code assumes that __annotations__ contains normal Python objects, without replacing unbound names with ForwardRef objects. Changing that behavior should be opt-in.

Existing code does not assume that it can use bare forward refs in annotations without a __future__ import. If someone tries to start using bare forward-refs in their annotations, and that breaks with a runtime-checking decorator, that’s no different from the behavior today, so it’s not a compatibility break.

The runtime checking decorators can (and should) request annotations explicitly using FORWARDREFS format (under PEP 649) if they want to enable bare forward refs.

  1. “PEP 649 FORWARDREFS mode will be slow, and there’s no built-in caching of the results.”

This is true. I think that we should provide memoization of FORWARDREFS-style annotations, but probably as a function in the inspect module, rather than built-in to the object model.

With the benefit of some experience and real-world use cases, I also expect that we can better optimize the FORWARDREFS mode.

  1. “Why weren’t we consulted?”

I don’t want to get too derailed on this meta-issue, but I do want to avoid it recurring in the future, if possible. I admit that I don’t really understand the implicit expectation here. There were lengthy public discussions of the details of PEP 649 on this forum over a period of months prior to its acceptance. Anyone who wants to influence the direction of Python is always welcome to keep an eye on the PEPs category (which is not too noisy) and participate in discussions here. It’s also fine not to do that, of course, but that’s a choice not to be involved in the decisions.

(There are also some questions about the behavior of modifying annotations at runtime; I’ll leave these for a separate post since this one is long enough already.)

10 Likes