How about introducing a global reserved __type_checking__
name, instead?
I think the pragma-like comment makes a lot of sense. It doesnât involve reserving arbitrary names, and since itâs present within the file rather than config, it should work for libraries once standarized.
To expand on that:
I made a library which, among other things, provides two types, Some
and Null
(Null
being an Enum
).
I made those types explicitly inherit from an _Option
protocol to ensure common interface.
Since Protocol
and Enum
use metaclasses, at runtime this initially resulted in an exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".\src\monads\option.py", line 130, in <module>
class Null(_Option[Never], Enum):
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
I alleviated that with a hack.[1]
class Null(_Option[Never] if TYPE_CHECKING else Generic[T], Enum):
Initial attempt used
object
in theelse
branch, which in turn caused MRO issues.Generic
hacks the hack. âŠď¸
Would this be a sufficient reason to introduce an import type
or similar from typescript?
Syntax side, I could be wrong, but it seems like marking imports as being type level imports could make it easy for the runtime to ignore them.
I wonder if you could even backport the behavior using a __future__
import.
Apologies in advance for what is likely a bad idea.
How penalizing would it be (and also how bad of a precedent) to have the runtime check in the import machinery:
- (inside the âfrom X import Yâ machinery)
- is Y one element long?
- is X typing?
- is Y TYPE_CHECKING?
- just expose the constant (set to False obviously)
For EVERY import that doesnât match, most would incur the length check (should be unmeasurably fast), except for ALL imports of length one, in which case itâs a string equivalence check of (up to) less than 10 characters. And finally if youâre importing something else out of (slow) typing module, two string checks.
The only regression that comes to mind is that âtypingâ hasnât actually been loaded, so all the downstream code shouldnât run (e.g. donât put it in sys.modules).
You can implement that yourself by overwriting __import__
globally and see if it has any surprising side effects. (Ofcourse, this would be slower than a direct C level implementation)
When using micropython with stubs, I wanted to use typing.Literal
but could not as typing
is not available on micropython.
A solution is to import typing
in an if TYPE_CHECKING
block and use forward reference in the code.
So having a way to reference the TYPE_CHECKING
in code without importing typing
is important for the typed micropython ecosystem.
Also PEP 749 is very likely to never be implemented in micropython.
An idea to address performance issues might be to import typing in an if __debug__:
block and then run the script with python -O
when using it (as opposed to developing it).
I like the idea but absolutely hate the solution. I specifically agree with Eric when he writes:
Though disagree with pretty much everything else he writes. Eric, it might behoove you to be a bit more open minded to other perspectives and use some empathy when interfacing with others. This is something I have noticed you routinely forgetting to do in your discussions.
I think that reducing startup time is absolutely worth it. It might seem extreme, but I wouldnât be opposed to just adding a new soft keyword. The type statement added to python shows that the language is willing to go pretty far into adjusting the language just for typing. Giving some special consideration to access some variable or anything to determine whether type checking is enabled doesnât seem too far out there.
Hereâs a wild idea:
if __name__ == "__typing__":
...
(take this half-seriously :D)
wicked. i propose
match __name__:
case "__main__":
main()
case "__typing__":
from âŚ
thatâs down to a quarter.
that would be useful in the context of multiprocessing, where youâd have
match __name__:
case "__main__":
main()
case "__mp_main__":
child_main()
case "__typing__":
from ...
Thanks all! This invited more discussion that I expected, for something I thought would be somewhat niche
Despite both mypy and pyright currently supporting it, no one seemed thrilled about my suggestion to encode type checker only special casing of TYPE_CHECKING
name in the typing spec (Iâm curious if @erictraut plans to remove this support)
The most popular suggestions on this thread all seemed to involve runtime suggestions (like changing builtins). As I said in my initial post, Iâm not a fan of this. If someone wishes to pursue it, itâd need to be a larger discussion and probably a PEP
Iâm therefore not taking any actions here. If someone in the future wants to pursue making the type checker only special casing official, youâd have to make a PR to the spec and open an issue on GitHub - python/typing-council: Decisions by the Python Typing Council
I only use mypy so if itâs not added to the spec, I hope at least mypy doesnât remove support for TYPE_CHECKING = False
, because as noted above, itâs useful for CLIs and small scripts that want to minimise the total number of imports, especially as itâs getting slower to import typing
in 3.14. Otherwise I fear some projects may remove type checking.
I prefer adding __type_checking__
constant, not builtin.
For example, SQLAlchemy is large library, and uses a lot of if TYPE_CHECKING:
.
See elements.py for example.
When replacing if (typing\.)?TYPE_CHECKING
with if False
, bytecode size reduced about 4%.
$ wc -c *.pyc
211087 elements.cpython-313.pyc # original
192163 elements_false.cpython-313.pyc # replaced with `if False`
But if False
is not looks good for readability.
This expression does not clearly express the intention.
Adding constant allow us to add rich type hints with zero runtime cost.
Can I start writing PEP for _type_checking__
using this thread as the Discussion, or should I create a new thread before writing the PEP?
I am thinking about the details of __type_checking__
.
Would that be implementable as a soft keyword?
The dunder method is reserved, and I donât see it being used when I search at Sourcegaraph, so I donât think it would break a lot of code to make it a keyword.
Also, would it be a good idea to treat __type_checking__ = False
as a no-op instead of an error, making it easier to write code compatible with Python versions up to 3.13?
Since by design it can be used as an expression, it IMO shouldnât be - unless you want reassignment to be a meaningful operation. Otherwise itâs a confusing situations that some people are going run into.
Since all dunder names are reserved we can pretty freely use it. Backwards compatibility is not something we should worry ourself with here.
I think âverify that Falseâ is a better choice. Cost should still be neglible and it again prevents confusion where people try to assign true to it but that doesnât work.
Write a PEP that summarizes the arguments made in this thread, then open a new thread once the PEP has been merged into the python/peps repo. The idea is that people reviewing the PEP wonât have to re-read all of the previous discussions that led up to it; itâs the PEPâs job to make sure the discussion is summarized well.
Most Python builtins can be freely overriden. I wouldnât write this code myself but note e.g. sphinx/sphinx/ext/autodoc/importer.py at d066c2be731df5f1ffed5d657c696b57f39a4f39 ¡ sphinx-doc/sphinx ¡ GitHub
Assigning to __debug__
fails, which might be the inspiration here. I imagine it would also be relevant for implementing compiler optimisations, and @methane mentioned bytecode size above. Allowing __type_checking__ = False
(and updating type checkers to recognise it) would mean that adoption could be much quicker, though.
I suppose the question is how important/useful is the invariant of __type_checking__ is False
. I implemented the code that @hauntsaninja linked to as a (mildly awful) workaround for trying to identify the source of annotations with PEP 563 [1], where reloading a module after an initial import with TYPE_CHECKING = True
can work and mean that weâre able to resolve more type annotations at runtime (as Sphinx imports the code that it is auto-documenting).
Iâm not aware of many other use-cases for setting TYPE_CHECKING
to True
at runtime, though, so it may be that the gains from specifying the new constant as always False
outweigh the drawbacks.
A
I havenât had time yet to properly investigate, but my hope is that PEP 649/749 makes things better âŠď¸