| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
@JelleZijlstra Could you please pre-review this? What do you think about this spec change? |
Sorry, something went wrong.
|
I think I have integrated all the changes. Is it time to open an issue on the Typing Council’s issue tracker asking for a decision? |
Sorry, something went wrong.
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
There was a problem hiding this comment.
One wording nit, one formatting nit, and one conformance suite nit :) But overall this looks great to me.
Sorry, something went wrong.
There was a problem hiding this comment.
Looks good to me - much more consistent and clearly specified than before
Sorry, something went wrong.
There was a problem hiding this comment.
This looks good to me. Thanks @davidhalter for getting this clarified.
Sorry, something went wrong.
|
I have integrated all of Carl's suggestions. I will update the conformance tests as soon as the typing council approves this change. If I update it now we probably just run into merge conflicts, since especially pyrefly changes a lot. @carljm Please let me know if you think something needs more work. |
Sorry, something went wrong.
|
Hi, can someone explain the intent of this change to me? Given, under python 3.14: Python 3.14.0 (main, Oct 20 2025, 16:44:45) [GCC 14.3.1 20250808 (Red Hat 14.3.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class A: pass
...
>>> class X:
... def A(self) -> "A": pass
...
>>> class Y:
... def A(self) -> A: pass
...
>>> typing.get_type_hints(X.A)
{'return': <class '__main__.A'>}
>>> typing.get_type_hints(Y.A)
{'return': <function Y.A at 0x7f2052bc3c10>}
>>>
does this change propose that it would be impossible for get_type_hints(X.A) to return class A under any circumstances, even with the quotes? |
Sorry, something went wrong.
|
Yes |
Sorry, something went wrong.
|
are you going to change the behavior of get_type_hints() ? is this a 3.15 change? is there a pep? it should be apparent that this is an enormous backwards-incompatible change I hope? edit: the pep is pep-749 |
Sorry, something went wrong.
|
existing libraries which use this pattern will have to use: >>> _a_cls = A
>>> class Z:
... def A(self) -> _a_cls: pass
...
>>> typing.get_type_hints(Z.A)
{'return': <class '__main__.A'>}
|
Sorry, something went wrong.
|
this would be on top of this breaking change in 3.13 -> 3.14 Python 3.13.9 | packaged by Anaconda, Inc. | (main, Oct 21 2025, 19:09:58) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> class A: pass
...
>>> class F:
... def A(self)->A: pass
...
>>> import typing
>>> typing.get_type_hints(F.A)
{'return': <class '__main__.A'>}
Python 3.14.3 | packaged by Anaconda, Inc. | (main, Feb 24 2026, 22:45:56) [MSC v.1942 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> class A: pass
...
>>> class F:
... def A(self) -> A: pass
...
>>> import typing
>>> typing.get_type_hints(F.A)
{'return': <function F.A at 0x0000025520D22610>}
|
Sorry, something went wrong.
|
I went to where I knew I'd find examples of this, in datetime.pyi, and indeed they are using an alias to work around the ambiguity: https://github.com/python/typeshed/blob/main/stdlib/datetime.pyi#L296 |
Sorry, something went wrong.
There was a problem hiding this comment.
When reading this at first I was a bit worried that this would cause issues for NumPy, where builtin shadowing (e.g. numpy.bool) and class shadowing (numpy.dtype / numpy.ndarray.dtype) are pretty common things. However, this was recently fixed by @JelleZijlstra in numpy/numpy#31951, so that's no longer an issue.
Anyway, this change reduces ambiguity while improving precision in a directly testable way. The main selling point of string annotations are forward references, so it seems natural for it to shadow "as above, so below". The other explicit "no special casing" vibes I'm getting from this I also really like.
Sorry, something went wrong.
Not "on top of" - it's the same change. What this PR does is reflect that very same change in runtime semantics of annotations in the specified type checker behavior, so that the two are consistent.
I think this is a really good question, and personally I would be open to specifying a more gradual transition path here, even though it would mean type checkers have to maintain more different behaviors at once. Specifically, we could say that stringified annotations (whether explicitly strings, or implicitly stringified via from __future__ import annotations) would retain semantics compatible with the runtime behavior of typing.get_type_hints on strings (avoiding the pressure to change that runtime behavior in a backwards-incompatible way), while specifying that all annotations in Python 3.14+ use deferred-evaluation semantics with normal Python scoping rules, just like 3.14+ does at runtime. |
Sorry, something went wrong.
|
I was struggling to wrap my head around the matrix of runtime behaviors involved here, so I put together a little table for myself. Copying it below in case it helps anyone else (all code snippets are in the body of a class A):
@carljm If I understand correctly, you're proposing that all stringified annotations be resolved exactly as they are at runtime, while non-string annotations are resolved as they would be in 3.14? If so, then at least for the above examples, that seems to simplify down to "match 3.14 runtime behavior exactly, even where it's conceptually inconsistent," which I can get behind, even though it's a little unsatisfying. |
Sorry, something went wrong.
|
That table is really useful. |
Sorry, something went wrong.
|
I'd rather not rely on the behavior of get_type_hints() too much, it's deliberately buggy for backwards compatibility. |
Sorry, something went wrong.
|
I fully agree, get_type_hints definitely shouldn't be used as any kind of a reference here -- it has very questionable behaviour even on the latest Python version |
Sorry, something went wrong.
|
Is there a better reference than typing.get_type_hints we can use then? My (possibly mistaken) understanding was that get_type_hints is the closest thing we have to an "official" runtime interpretation of type hints and therefore the thing to look at if we're concerned about compatibility with runtime behavior and previous versions. To be clear, I think that if we were starting from a clean slate, a simple and consistent interpretation of type annotations as proposed here would be ideal. But we're not, so I'm trying to understand what a compromise that still moves us in the right direction would look like. |
Sorry, something went wrong.
Hmm this is also news to me. Then in that case I agree that we shouldn't build upon unstable grounds. But at the same time I also think it's a good idea to minimize the differences between the static- and runtime-typing worlds. |
Sorry, something went wrong.
|
The non-buggy modern alternative is annotationlib.get_annotations, which is already backported in typing_extensions. I don't think it's workable to change get_type_hints; its questionable behaviour is too deeply embedded |
Sorry, something went wrong.
Ah perfect; that sounds like a good alternative to typing.get_type_hints that we could reference here instead. |
Sorry, something went wrong.
|
Generally the reference should be what happens if you don't stringify the annotation in 3.14+. Annotations are now always lazily evaluated. |
Sorry, something went wrong.
|
Perfect, thanks. I retried using annotationlib.get_annotations in 3.14 and typing_extensions.get_annotations in 3.12. [1] The matrix came out the same as it did for typing.get_type_hints. If the intended reference is the non-stringified 3.14 results, then this PR matches the intended runtime behavior in 3.14 onward, which is great. def int(self) -> int: ... still gives a different result from def int(self) -> "int": ... with get_annotations(eval_str=True); is that intentional? That particular example seems to be the exact case in which the backwards compatibility implications of this change are the most concerning. [1] using this code, if anyone wants to check my homework ;) I added from __future__ import annotations and eval_str=True to see how stringified annotations evaluate. from annotationlib import get_annotations class A: str: str class B: x: int def int(self): ... y: int class C: def int(self) -> int: ... print(get_annotations(A)) print(get_annotations(B)) print(get_annotations(C.int)) |
Sorry, something went wrong.
|
@rchen152 Yes, I think that's a good summary of my suggestion above: to match the runtime behavior of 3.14 exactly, assuming that typing.get_type_hints or annotationlib.get_annotations(eval_str=True) is used to access the annotations and resolve stringified ones. Let's call this proposal "compatible". The alternative proposal on the table we could call "simple" -- that proposal is that all annotations (including stringified ones) should be resolved the way they would resolve at runtime if they were non-stringified annotations on 3.14+. To the extent that stringified annotations (mostly) eventually go away, the two proposals will converge, since they differ only in the handling of stringified annotations. The advantage of "simple" is that there is a single consistent model of name resolution for type checkers to implement. The disadvantages are:
The "compat" model preserves backwards-compatibility and full consistency with runtime behavior, allowing users to migrate to the new name-resolution semantics on their own schedule (by moving from stringified annotations to non-stringified PEP 647/747 in 3.14+). The cost is more complexity in type checker implementations. It increasingly seems to me that "compat" is the choice that better serves users of the type system. (There is a third possible choice, which is to fully embrace the backwards-incompatible change by also changing the runtime behavior of typing.get_type_hints, but I don't see any appetite for that.) |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
I added this after the discussion here: https://discuss.python.org/t/annotation-string-references-in-class-scope-in-conformance-tests/105439
I'm not 100% sure about the wording, but I hope the direction is fine. I would like to gather some feedback before presenting this to the typing council.
Please also merge #2139 before this pull request. Otherwise it will be very hard to update Zuban's conformance test results in this pull request.