To answer the first point: this is exactly why i used “eventually”. Once this is accepted, there will be an old way to do the thing and a new way to do the thing. I don’t like the new way to do the thing (because i don’t deem it worth the speedup, because i don’t think it’s right solution, because i don’t like embedding this so deep into the language, etc. etc.), but i WILL, at some point, have to (i.e. be forced to) do it because nothing deprecated stays forever, and such outcome simply doesn’t sound awesome.
To answer the second point: i’m not saying that 10 ms isn’t a speedup, it is, it absolutely is. What i’m arguing is that on the scale that Python operates right now (in terms of performance), avoiding 10 milliseconds that are only spent once during a program’s whole lifetime isn’t gonna do much for Python’s performance. The “actual” code that a program consists of can and often does easily take up 100 times these 10ms. I just really don’t see how adding a language builtin (!) to squeeze out 10ms of runtime and a line of import code can be a good trade.
A program can also take much less time than this. A simple CLI script using Python is very often limited in speed purely by startup time. The performance for short processes is also important.
This also improves the compatability story for libraries with micropython, as importing typing on micropython is a nogo for reasons other than startup.
I appreciate that, but I think the PEP needs to take a clearer stance on how the new constant’s intended to be used and what the broader implications for the open source community would be, because this has the potential to cause quite a bit of churn, depending on how it ends up being received by the userbase.
If a script is so short, would it make sense to say that it doesn’t make any difference if it executes in 10ms or 20ms (or 100ms vs 110ms; you get the point)? No matter the scale, in Python’s case 10ms of import time just don’t make a difference:
On small scales, you won’t notice it because the program is already too bloody fast
On medium to large scales, you won’t notice it because other stuff eats way more runtime than the typing import
It might sound like i’m saying “whatevs it’s not a problem if you don’t notice it”, but i’ll repeat just for clarity that i’m not arguing that this isn’t a problem, i’m arguing that this solution is a case of “the medicine is worse than the disease”, i.e. it’s simply not worth it.
The proposed feature is useless (i believe?) if even a single (possibly transitive) dependency imports TYPE_CHECKING the “old” (current) way, so i think if this proposal does get through, it needs to strongly tell that the new way is “the” way, and it would be highly useful for tools like ruff to add lints that flag the old way. Some depreciation warnings too, maybe, if that’s possible/feasible.
I’m going to draw a line here and not respond further. Yes it absolutely makes a difference whether a script runs in 50ms or 40ms which is the difference that importing typing makes, That is a 20% difference in run time for a short program and if you run many short processes then clearly that is significant.
You have come into this discussion basically arguing the position that “Python is slow so it is not worth trying to make it faster” (even though there is no particular cost that you object to). You are not the only person to make this argument but that is why I am challenging your position: it is very unhelpful and also plainly absurd. If Python is slow (which it is) then we should make it faster. Current slowness is not a reasonable argument against future speed improvements.
The kind of discussion I would like to have is not whether “10ms is worth it” but rather how we can get from 50ms to 1ms. I would like to see people having some actual ambition for making things faster rather than arguing that trivial features are not worth only a 20% improvement in startup time. To quote myself from before:
The fact that anyone considers it reasonable to question whether shaving 10ms from startup time is “worth it” shows that the attitudes here are all wrong. In a “fast language” if you tried to argue that 10ms of purely wasted overhead on absolutely every startup was not worth fixing you would rightly be shouted out.
This proposal is not intended to recommend avoiding imports from typing.
The avoidance of imports from typing is already being done, and this proposal is intended to standardize that method.
So, even if some of the dependent libraries import typing, the value of this proposal is not diminished. If some of the libraries already have TYPE_CHECKING = False or if False: # TYPE_CHECKING, that alone is valuable for this proposal.
I plan to modify the proposal to deprecate typing.TYPE_CHECKING in the same way as typing.Tuple.
It will be eventually removed, but no schedule.
No DeprecationWarning.
Type checkers would warn if minimum Python version is 3.14 or higher.
Importing those from typing is deprecated. Due to PEP 563 and the intention to minimize the runtime impact of typing, this deprecation will not generate DeprecationWarnings. Instead, type checkers may warn about such deprecated usage when the target version of the checked program is signalled to be Python 3.9 or newer. It’s recommended to allow for those warnings to be silenced on a project-wide basis.
The deprecated functionality may eventually be removed from the typing module. Removal will occur no sooner than Python 3.9’s end of life, scheduled for October 2025.
Not a great way to engage in a discussion, i must say, but hopefully others will pick up where you left off. Your stance is clear, though i will say that if you run many short processes, you should probably convert your logic into a loop inside the process (script) itself.
This simply ignores my actual position which i intentionally clarified multiple times throughout my messages, the last case being:
I don’t argue that these 10ms don’t exist or that it wouldn’t be good if we got rid of them or otherwise “see through” the issue this proposal aims to solve, i SEE it, i AGREE it exists and should be SOLVED. My argument is not “the problem is not a problem”, my argument is “the solution solves the problem in a suboptimal way”.
To respond to the “even though there is no particular cost that you object to” part specifically: i somehow never explicitly pronounced my gripe with the solution - my bad, let’s fix this. My main gripe with the solution is that it’s a very targeted, very invasive solution to a problem that i see as much more general. We have a problem where a module, typing, takes too long to load. Making importing it unnecessary as the proposed solution already rings some bells on the “why specifically that solution”, and the exact way it has been done (adding a builtin to the language (!)) straight-up triggers a fire alarm in my head.
If i had a problem like that in my module/my language, i would, well, try optimizing them? I would try reducing the impact of module’s contents or contents themselves. I would try optimizing the import routine. I would try quite a bunch of things before concluding that making a change to the core of the language to fix one module (even if an important one) is the right/only way to go. [1] I don’t see in the proposal any mention of “previous attempts”, or “alternative solutions”, which, to someone who’s not “in the loop” on what’s happening in typing on the development side, implies that no such past attempts occurred (would love to be proven wrong, of course).
I AGREE THAT 10MS SPEEDUP IS GOOD. I have said this many, many times and i beg you, HEAR me: i would LOVE to see Python become a viable language for performance-sensitive tasks, i would LOVE to see Python just “go fast”. What i am saying is that you can’t slap a hack on a hack on a hack no matter how fast it lets you go. A CPU doesn’t randomly skip instructions just because it’ll go faster. A programmer doesn’t name all of their functions, classes, and variables a1, a2, a3 just because it’s faster to type. A business doesn’t survive by squeezing out every penny from their employees and users just because it’ll make them a buck quicker. [2] You can’t afford to take the convenient/“least resistance” way out because even if it makes the short term situation better, the long run inevitably suffers.
I see this particular solution as a very suboptimal solution, a solution that’s this close to being a hack, and so far i haven’t seen anyone explain anywhere why this particular way of fixing the problem was chosen over all the other ones. Reassurance that this solution is, in fact, the best way possible to go is the bare minimum NEEDED, not just for this proposal but for all proposals ever. Once and only once there is that “proof” that alternatives were considered, weighted, and rightfully ruled out, then it would be time for the council or whoever’s in charge to make the final call on whether we want those 10ms or not (i.e. if the solution costs less than the benefits it brings). I want to ensure that this solution is really, truly the best one (or find the “actual” best one if there’s any), so that the council has all the information they could possibly utilize to make The Right Call (whichever it would be). [3]
I’m not, however, anyhow claiming that any of that is an easy thing to do. I’m not a 10x developer with decades of language design experience or anything like that. ↩︎
Please remember the guidelines for d.p.o, including engage the topic, not the person.
typing.TYPE_CHECKING was originally introduced to replace if False, so that the name could be special cased by type checkers. At its heart, this proposal does the same thing, but avoids a dependency cycle.
As mentioned in this thread, there are many ways in Python to expose a given name in builtins, e.g. setattr(__builtins__, '__type_checking__', False). None of these work for this use-case though, because type-checkers don’t know that the name exists.
I would frame this proposal not as blessing the __type_checking__ approach forever, but as solving a specific usability problem we currently have with static typing in Python, whilst the wider design space is still worked on.
Indeed, I don’t think anyone will disagree that we should also be searching for other solutions. This thread has already mentioned type-only imports or lazy decorators. Taking the liberty to quote Jacob & Michael:
The perfomance problem is not just with the typing module – it is with all code that exists only to provide a more accurate representation of runtime semantics to type checkers, but is not needed for runtime execution. As an example, in Sphinx, we define every Protocol we use in a TYPE_CHECKING block. This is code that is useful for type checkers, but has no effect the runtime execution. The ‘10ms’ is the cost to access the name that allows us to remove other, larger costs (importing costly modules solely for type hints, defining type aliases or protocols, resolving import cycles).
I would also make a teaching argument. From the perspective of a beginner, reading the following snippet is very ‘magical’:
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Sequence
def process_sequence(seq: Sequence) -> None:
pass
The beginner is required to understand that the assignment to TYPE_CHECKING is ignored by type checkers, and that this name is special cased, but only built in to (some) type checkers, but other type checkers require either special configuration or only to use the typing.TYPE_CHECKING name.
In contrast, a built-in can abstract the ‘magic’, and it can be explained that this special built-in is used to provide code that is needed by type checkers but not at runtime.
As I alluded to above, I agree that TYPE_CHECKING at present is suboptimal, in effectively having two models of execution for Python. I hope to see a future where we will have made improvements and changes that mean both that we don’t need __type_checking__ and that Python is in the ‘fast language’ category that Oscar mentioned. We would welcome proposals, especially those that would lead to a 50x speed up!
However, PEP 781 is a pragmatic idea, and the one we have in front of us. As such, I still support it – the proposal is simple, small, and straightforward.
Sorry, i wasn’t trying to attack them, just expressing sadness about the reluctance to engage in a healthy argument. I will choose my wording more carefully moving forward.
That is a very important clarification, though i can’t tell if me not understanding this from the get-go is a “skill issue”, so to say, or it’s truly unclear and should be emphasized in the proposal. I, for one, would certainly prefer a small clause that clarifies this.
I feel like even if we were to consider typing.TYPE_CHECKING not a hack, manually defining TYPE_CHECKING = False most definitely is one. While i don’t think hacks should be “taught” per se (and i certainly don’t expect beginners to see this hack being used in the wild, given i, not a beginner by all accounts, have only discovered this hack through this proposal), i agree that consistency is one problem this proposal definitely solves. (Not that i ever doubted it, but..)
I would be in favor of TYPE_CHECKING even if that’s inconsistent with the other builtins, primarily for the aforementioned reason of not needing to change most of the code when migrating. At the ssme time though, how hard can it be to Find & Replace?
I voted for __type_checking__ assuming we’re adding something new, but I’m not convinced this is really needed. Optimising typing’s import time and sticking with the existing variable would be better, IMHO.
I agree. Is it not possible to just cache the interpreter state after each standard library import? So long as the standard library imports come first (which they often do), then you wouldn’t need to run the library code.
IMHO the discussion about generally improving import times is a distraction here. We know there is no easy solution to that problem. “Cache the interpreter state” is the kind of idea that sounds simple conceptually but is hard to implement (the “interpreter state” is an intricate graph of objects with myriads of pointers between each other).