Re: Type annotations, PEP 649 and PEP 563
typing features from future Python versions
I second both of these uses, but especially this (which seems to be missing from the original post), it’s been by far the main reason I’ve used this mode and I’ve seen this used, and is the main feature to look forward to when dropping Python 3.7 support. The new features coming to typing make static typing much easier, but no libraries can drop Python 3.7/3.8/3.9 support; but static typing doesn’t need old versions. When talking about speed, one of the important things to consider here is the difference between the two proposals. PEP 649 was about the same as the current performance, but PEP 563 was significantly faster, since it doesn’t instantiate or deal with objects at all, which both the current default and PEP 563 do. You could even protect imports with TYPE_CHECKING with PEP 563, and further reduce the runtime cost of adding types - which could be seen as a reason to avoid adding types. To the best of my knowledge, it hasn’t been a blocker for packages, but something to include. Also, one of the original points for static typing is that strings can be substituted for objects. “Type” is identical, from a static typing perspective, to Type. You can swap one for the other, and for a Python 3.6+ codebase, using something like “A | B” (with the quotes) is a valid way to have static types in 3.6 that pass MyPy (though are not usable at runtime, obviously, but that’s often not a requirement). NumPy, for example, makes heavy usage of unions and other newer additions in static typing.
On Thu, Oct 21, 2021 at 1:08 PM Henry Fredrick Schreiner
typing features from future Python versions
I second both of these uses, but especially this (which seems to be missing from the original post), it’s been by far the main reason I’ve used this mode and I’ve seen this used, and is the main feature to look forward to when dropping Python 3.7 support. The new features coming to typing make static typing much easier, but no libraries can drop Python 3.7/3.8/3.9 support; but static typing doesn’t need old versions.
I agree with this point. We shouldn't emit DeprecationWarning for `from __future__ import annotations` at least 3 versions (3.11 ~ 3.13).
When talking about speed, one of the important things to consider here is the difference between the two proposals. PEP 649 was about the same as the current performance, but PEP 563 was significantly faster, since it doesn’t instantiate or deal with objects at all, which both the current default and PEP 563 do. You could even protect imports with TYPE_CHECKING with PEP 563, and further reduce the runtime cost of adding types - which could be seen as a reason to avoid adding types. To the best of my knowledge, it hasn’t been a blocker for packages, but something to include.
Also, one of the original points for static typing is that strings can be substituted for objects. “Type” is identical, from a static typing perspective, to Type. You can swap one for the other, and for a Python 3.6+ codebase, using something like “A | B” (with the quotes) is a valid way to have static types in 3.6 that pass MyPy (though are not usable at runtime, obviously, but that’s often not a requirement). NumPy, for example, makes heavy usage of unions and other newer additions in static typing.
We may be able to provide tool to rewrite Python sources like 2to3:
* Remove `from __future__ import annotations`
* Stringify annotations, if it is not constants (e.g. `None`, `42`,
`"foo"` are not rewrote).
This tool can ease transition from PEP 563 to 649, and solve
performance issues too.
PEP 649 can have the performance as PEP 563 if all annotations are constants.
--
Inada Naoki
On 10/21/21 5:01 AM, Henry Fredrick Schreiner wrote:
PEP 649 was about the same as the current performance, but PEP 563 was significantly faster, since it doesn’t instantiate or deal with objects at all, which both the current default and PEP 563 do.
I don't understand what you're saying about how PEP 563 both does and doesn't instantiate objects. PEP 649, and the current implementation of PEP 563, are definitely both faster than stock behavior when you don't examine annotations; both of these approaches don't "instantiate or deal with objects" unless you examine the annotations. PEP 649 is roughly the same as stock when you do examine annotations. PEP 563 is faster if you only ever examine the annotations as strings, but becomes /enormously/ slower if you examine the annotations as actual Python values. The way I remember it, most of the negative feedback about PEP 649's performance concerned its memory consumption. I've partially addressed that by always lazy-creating the function object. But, again, I suggest that performance is a distraction at this stage. The important thing is to figure out what semantics we want for the language. We have so many clever people working on CPython, I'm sure this team will make whatever semantics we choose lean and performant. //arry/
Sorry for the naive question but why doesn't "TYPE_CHECKING" work under PEP 649? I think I've seen others mention this but as the code object isn't executed until inspected then if you are just using annotations for type hints it should work fine? Is the use case wanting to use annotations for type hints and real time inspection but you also don't want to import the objects at run time? If that's really such a strong use cause couldn't PEP 649 be modified to return a repr of the code object when it gets a NameError? Either by attaching it to the NameError exception or as part of a ForwardRef style object if that's how PEP 649 ends up getting implemented? Damian (he/him) On Thu, Oct 21, 2021, 00:05 Henry Fredrick Schreiner < henry.fredrick.schreiner@cern.ch> wrote:
typing features from future Python versions
I second both of these uses, but especially this (which seems to be missing from the original post), it’s been by far the main reason I’ve used this mode and I’ve seen this used, and is the main feature to look forward to when dropping Python 3.7 support. The new features coming to typing make static typing much easier, but no libraries can drop Python 3.7/3.8/3.9 support; but static typing doesn’t need old versions.
When talking about speed, one of the important things to consider here is the difference between the two proposals. PEP 649 was about the same as the current performance, but PEP 563 was significantly faster, since it doesn’t instantiate or deal with objects at all, which both the current default and PEP 563 do. You could even protect imports with TYPE_CHECKING with PEP 563, and further reduce the runtime cost of adding types - which could be seen as a reason to avoid adding types. To the best of my knowledge, it hasn’t been a blocker for packages, but something to include.
Also, one of the original points for static typing is that strings can be substituted for objects. “Type” is identical, from a static typing perspective, to Type. You can swap one for the other, and for a Python 3.6+ codebase, using something like “A | B” (with the quotes) is a valid way to have static types in 3.6 that pass MyPy (though are not usable at runtime, obviously, but that’s often not a requirement). NumPy, for example, makes heavy usage of unions and other newer additions in static typing. _______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/L3IKBO5Y... Code of Conduct: http://python.org/psf/codeofconduct/
On Thu, Oct 21, 2021 at 10:44 AM Damian Shaw
Sorry for the naive question but why doesn't "TYPE_CHECKING" work under PEP 649?
I think I've seen others mention this but as the code object isn't executed until inspected then if you are just using annotations for type hints it should work fine?
Is the use case wanting to use annotations for type hints and real time inspection but you also don't want to import the objects at run time?
Yes, you're right. And I don't think PEP 649 and PEP 563 are really all that different in this regard: if you have an annotation using a non-imported name, you'll be fine as long as you don't introspect it at runtime. If you do, you'll get a NameError. And with either PEP you can work around this if you need to by ensuring you do the imports first if you're going to need the runtime introspection of the annotations. The difference is that PEP 563 makes it easy to introspect the annotation _as a string_ without triggering NameError, and PEP 649 takes that away, but I haven't seen anyone describe a really compelling use case for that.
If that's really such a strong use cause couldn't PEP 649 be modified to return a repr of the code object when it gets a NameError? Either by attaching it to the NameError exception or as part of a ForwardRef style object if that's how PEP 649 ends up getting implemented?
It could, but this makes evaluation of annotations oddly different from evaluation of any other code, which is something that it's reasonable to try to avoid if possible. Carl
On Thu, Oct 21, 2021 at 10:44 AM Damian Shaw
wrote: Sorry for the naive question but why doesn't "TYPE_CHECKING" work under PEP 649?
I think I've seen others mention this but as the code object isn't executed until inspected then if you are just using annotations for type hints it should work fine?
Is the use case wanting to use annotations for type hints and real time inspection but you also don't want to import the objects at run time?
Yes, you're right. And I don't think PEP 649 and PEP 563 are really all that different in this regard: if you have an annotation using a non-imported name, you'll be fine as long as you don't introspect it at runtime. If you do, you'll get a NameError. And with either PEP you can work around this if you need to by ensuring you do the imports first if you're going to need the runtime introspection of the annotations.
The difference is that PEP 563 makes it easy to introspect the annotation _as a string_ without triggering NameError, and PEP 649 takes that away, but I haven't seen anyone describe a really compelling use case for that.
I would want this for my type checker, pyanalyze. I'd want to get the raw annotation and turn it into a type. For example, if the annotation is `List[SomeType]` and `SomeType` is imported in `if TYPE_CHECKING`, I'd at least still be able to extract `List[Any]` instead of bailing out completely. With some extra analysis work on the module I could even get
El jue, 21 oct 2021 a las 10:31, Carl Meyer (
On Thu, Oct 21, 2021 at 12:06 PM Jelle Zijlstra
I would want this for my type checker, pyanalyze. I'd want to get the raw annotation and turn it into a type. For example, if the annotation is `List[SomeType]` and `SomeType` is imported in `if TYPE_CHECKING`, I'd at least still be able to extract `List[Any]` instead of bailing out completely. With some extra analysis work on the module I could even get the real type out of the `if TYPE_CHECKING` block.
If you're up for the extra analysis on the module, wouldn't it be just as possible to front-load this analysis instead of doing it after the fact, and perform the imports in the `if TYPE_CHECKING` block prior to accessing the annotation data? Or perform a fake version of the imports that just sets every name imported in `if TYPE_CHECKING` to `Any`? Carl
On 10/21/21 5:42 PM, Damian Shaw wrote:
Sorry for the naive question but why doesn't "TYPE_CHECKING" work under PEP 649?
I think I've seen others mention this but as the code object isn't executed until inspected then if you are just using annotations for type hints it should work fine?
Yes, it works fine in that case.
Is the use case wanting to use annotations for type hints and real time inspection but you also don't want to import the objects at run time?
If that's really such a strong use cause couldn't PEP 649 be modified to return a repr of the code object when it gets a NameError? Either by attaching it to the NameError exception or as part of a ForwardRef style object if that's how PEP 649 ends up getting implemented?
That's the use case. Your proposal is one of several suggesting that type annotations are special enough to break the rules. I don't like this idea. But you'll be pleased to know there are a lot of folks in the "suppress the NameError" faction, including Guido (IIUC). See also this PR against co_annotations, proposing returning a new AnnotationName object when evaluating the annotations raises a NameError. https://github.com/larryhastings/co_annotations/pull/3 Cheers, //arry/
On Thu, Oct 21, 2021 at 10:35 AM Larry Hastings
.
Your proposal is one of several suggesting that type annotations are special enough to break the rules. I don't like this idea. But you'll be pleased to know there are a lot of folks in the "suppress the NameError" faction, including Guido (IIUC).
Yes, I still want this part of your PEP changed. I find your characterization of my position misleading -- there is no rule to break here(*), just an API. (*) The Zen of Python does not have rules. -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...
It's certainly not my goal to be misleading. Here's my perspective. In Python, if you evaluate an undefined name, Python raises a NameError. This is so consistent I'm willing to call it a "rule". Various folks have proposed an exception to this "rule": evaluating an undefined name in an PEP 649 delayed annotation wouldn't raise NameError, instead evaluating to some yet-to-be-determined value (ForwardRef, AnnotationName, etc). I don't think annotations are special enough to "break the rules" in this way. Certainly this has the potential to be irritating for code using annotations at runtime, e.g. Pydantic. Instead of catching the exception, it'd have to check for this substitute value. I'm not sure if the idea is to substitute for the entire annotation, or for just the value that raised NameError; if the latter, Pydantic et al would have to iterate over every value in an annotation to look for this special value. As a consumer of annotations at runtime, I'd definitely prefer that they raise NameError rather than silently substitute in this alternative value. //arry / On 10/21/21 8:01 PM, Guido van Rossum wrote:
On Thu, Oct 21, 2021 at 10:35 AM Larry Hastings
mailto:larry@hastings.org> wrote: .
Your proposal is one of several suggesting that type annotations are special enough to break the rules. I don't like this idea. But you'll be pleased to know there are a lot of folks in the "suppress the NameError" faction, including Guido (IIUC).
Yes, I still want this part of your PEP changed. I find your characterization of my position misleading -- there is no rule to break here(*), just an API.
(*) The Zen of Python does not have rules.
-- --Guido van Rossum (python.org/~guido http://python.org/~guido) /Pronouns: he/him //(why is my pronoun here?)/ http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...
On Thu, Oct 21, 2021 at 04:48:28PM -0400, Larry Hastings wrote:
In Python, if you evaluate an undefined name, Python raises a NameError. This is so consistent I'm willing to call it a "rule". Various folks have proposed an exception to this "rule": evaluating an undefined name in an PEP 649 delayed annotation wouldn't raise NameError, instead evaluating to some yet-to-be-determined value (ForwardRef, AnnotationName, etc). I don't think annotations are special enough to "break the rules" in this way.
Can we have a code snippet illustrating that? I think this is what you mean. Please correct me if I have anything wrong. If I have this: from typing import Any def function(arg:Spam) -> Any: ... then we have four sets of (actual or proposed) behaviour: 1. The original, current and standard behaviour is that Spam raises a NameError at function definition time, just as it would in any other context where the name Spam is undefined, e.g. `obj = Spam()`. 2. Under PEP 563 (string annotations), there is no NameError, as the annotations stay as strings until you attempt to explicitly resolve them using eval. Only then would it raise NameError. 3. Under PEP 649 (descriptor annotations), there is no NameError at function definition time, as the code that resolves the name Spam (and Any for that matter) is buried in a descriptor. It is only on inspecting `function.__annotations__` at runtime that the code in the descriptor is run and the name Spam will generate a NameError. 4. Guido would(?) like PEP 649 to be revised so that inspecting the annotations at runtime would not generate a NameError. Since Spam is unresolvable, some sort of proxy or ForwardRef (or maybe just a string?) would have to be returned instead of raising. Am I close? My initial thought was to agree with Larry about special cases, but perhaps we could borrow part of PEP 563 and return a string if the name Spam is unresolvable. Runtime type checkers already have to deal with forward refs that are strings, as this is legal, and always will be: def function(arg:'Spam') -> Any: ... so we're not putting any extra burden on them. And we had already agreed to implicitly use strings for annotations. So if I have understood the options correctly, I like the idea of a hybrid descriptor + stringy annotations solution. - defer evaluation of the annotations using descriptors (PEP 649); - on runtime evaluation, if a name does not resolve, stringify it (as PEP 563 would have done implicitly); - anyone who really wants to force a NameError can eval the string. More practically, folks will more likely delay evaluating the string until Spam has been created/imported and will resolve. -- Steve
On 10/21/21 1:17 AM, Steven D'Aprano wrote:
On Thu, Oct 21, 2021 at 04:48:28PM -0400, Larry Hastings wrote:
In Python, if you evaluate an undefined name, Python raises a NameError. This is so consistent I'm willing to call it a "rule". Various folks have proposed an exception to this "rule": evaluating an undefined name in an PEP 649 delayed annotation wouldn't raise NameError, instead evaluating to some yet-to-be-determined value (ForwardRef, AnnotationName, etc). I don't think annotations are special enough to "break the rules" in this way. Can we have a code snippet illustrating that? I think this is what you mean. Please correct me if I have anything wrong.
If I have this:
from typing import Any def function(arg:Spam) -> Any: ...
then we have four sets of (actual or proposed) behaviour:
1. The original, current and standard behaviour is that Spam raises a NameError at function definition time, just as it would in any other context where the name Spam is undefined, e.g. `obj = Spam()`.
2. Under PEP 563 (string annotations), there is no NameError, as the annotations stay as strings until you attempt to explicitly resolve them using eval. Only then would it raise NameError.
3. Under PEP 649 (descriptor annotations), there is no NameError at function definition time, as the code that resolves the name Spam (and Any for that matter) is buried in a descriptor. It is only on inspecting `function.__annotations__` at runtime that the code in the descriptor is run and the name Spam will generate a NameError.
4. Guido would(?) like PEP 649 to be revised so that inspecting the annotations at runtime would not generate a NameError. Since Spam is unresolvable, some sort of proxy or ForwardRef (or maybe just a string?) would have to be returned instead of raising.
Am I close?
Your description of the four behaviors is basically correct.
So if I have understood the options correctly, I like the idea of a hybrid descriptor + stringy annotations solution.
- defer evaluation of the annotations using descriptors (PEP 649);
- on runtime evaluation, if a name does not resolve, stringify it (as PEP 563 would have done implicitly);
- anyone who really wants to force a NameError can eval the string.
You might also be interested in my "Great Compromise" proposal from back in April: https://mail.python.org/archives/list/python-dev@python.org/thread/WUZGTGE43... Naturally I'd prefer PEP 649 as written. The "compromise" I described would have the same scoping limitations as stringized annotations, one area where PEP 649 is a definite improvement. Cheers, //arry/
On Thu, Oct 21, 2021 at 5:24 PM Steven D'Aprano
Runtime type checkers already have to deal with forward refs that are strings, as this is legal, and always will be:
def function(arg:'Spam') -> Any: ...
so we're not putting any extra burden on them. And we had already agreed to implicitly use strings for annotations.
I'll take your word for it. However, other runtime uses for annotations may not already need to support strings as types. Pydantic is the classic example. I'm not entirely sure how it does its thing, but I have code built on dataclasses that is likely similar -- it expects the annotation to be an actual type object -- an actual class, one that can be instantiated, not even the types that are in the typing module, and certainly not a string that needs to be evaluated. So having them sometimes, maybe sometimes be strings would be a big pain in the @$$ On the other hand, having them be some odd "ForwardReference" type, rather than a NameError might be OK -- as long as SOME exception was raised as soon as I tried to use it. Though it would make for far more confusing error messages :-( My first choice would be to get a NameError at module load time, like we do now. Second would be a NameError as soon as it is accessed. Getting a special value is OK though, now that I'm thinking about it, I could probably put that special case code in one place, and provide a nice error message. -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
On Thu, Oct 21, 2021 at 09:36:20PM -0700, Christopher Barker wrote:
On Thu, Oct 21, 2021 at 5:24 PM Steven D'Aprano
wrote: Runtime type checkers already have to deal with forward refs that are strings, as this is legal, and always will be:
def function(arg:'Spam') -> Any: ...
so we're not putting any extra burden on them. And we had already agreed to implicitly use strings for annotations.
I'll take your word for it. However, other runtime uses for annotations may not already need to support strings as types.
Pydantic is the classic example.
Pydantic supports stringified annotations. https://pydantic-docs.helpmanual.io/usage/postponed_annotations/ Any other runtime annotation tool has to support strings, otherwise the "from __future__ import annotations" directive will have already broken it. If the tool does type-checking, then it should support stringified annotations. They have been a standard part of type-hinting since 2014 and Python 3.5: https://www.python.org/dev/peps/pep-0484/#forward-references Any type-checking tool which does not already support stringified references right now is broken. -- Steve
Any other runtime annotation tool has to support strings, otherwise the "from __future__ import annotations" directive will have already broken it.
Exactly- isn’t that it was deferred in 3.10, and may never be implemented? I’ll leave it to the Pydantic developers to discuss that, but they were pretty clear that PEP 563 would break things. If the tool does type-checking, then it should support stringified
annotations.
I guess I wasn’t clear — my point was there are uses for annotations that are NOT type checking. And my understanding of what the SC said was that they want those uses to continue to be supported. -CHB -- Christopher Barker, PhD (Chris) Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
On 10/22/21 1:45 AM, Steven D'Aprano wrote:
Any other runtime annotation tool has to support strings, otherwise the "from __future__ import annotations" directive will have already broken it. If the tool does type-checking, then it should support stringified annotations. They have been a standard part of type-hinting since 2014 and Python 3.5:
https://www.python.org/dev/peps/pep-0484/#forward-references
Any type-checking tool which does not already support stringified references right now is broken.
It's an debatable point since "from future" behavior is always off by default. I'd certainly agree that libraries /should/ support stringized annotations by now, considering they were nearly on by default in 3.10. But I wouldn't say stringized annotations were a "standard" part of Python, yet. As yet they are optional. Optional things aren't standard, and standard things aren't optional. //arry/
On Sat, Oct 23, 2021 at 09:49:10AM -0400, Larry Hastings wrote:
It's an debatable point since "from future" behavior is always off by default. I'd certainly agree that libraries /should/ support stringized annotations by now, considering they were nearly on by default in 3.10. But I wouldn't say stringized annotations were a "standard" part of Python, yet. As yet they are optional. Optional things aren't standard, and standard things aren't optional.
You misunderstand me. I'm not referring to PEP 563, which is still optional and requires the user to opt-in with a future import. I'm referring to the *manual* use of strings for forward references, which has been part of type-hinting since PEP 484 way back in 2014. https://www.python.org/dev/peps/pep-0484/#forward-references I expect that people were using strings for forward references before PEP 484, but it was 484 that made it official. -- Steve
(Off-topic)
On Sat, Oct 23, 2021 at 07:42 Steven D'Aprano
I expect that people were using strings for forward references before PEP 484, but it was 484 that made it official.
I doubt it. We invented that specifically for mypy. I am not aware of any prior art. —Guido
--
--Guido (mobile)
Larry Hastings wrote:
In Python, if you evaluate an undefined name, Python raises a NameError. This is so consistent I'm willing to call it a "rule".
Would it help the think of the function creation as catching that exception, and then finishing construction with its own version of NaN?
participants (10)
-
Carl Meyer
-
Christopher Barker
-
Damian Shaw
-
Guido van Rossum
-
Henry Fredrick Schreiner
-
Inada Naoki
-
Jelle Zijlstra
-
Jim J. Jewett
-
Larry Hastings
-
Steven D'Aprano