this post was submitted on 06 Jul 2023
8 points (100.0% liked)

Python

6317 readers
13 users here now

Welcome to the Python community on the programming.dev Lemmy instance!

📅 Events

PastNovember 2023

October 2023

July 2023

August 2023

September 2023

🐍 Python project:
💓 Python Community:
✨ Python Ecosystem:
🌌 Fediverse
Communities
Projects
Feeds

founded 1 year ago
MODERATORS
 

After learning about TYPE_CHECKING i made it a habit to put all imports that were only needed for type checking into an if TYPE_CHECKING: guard. But now I am wondering if that is actually intended to be used like that. Checking whether an import is only needed at type checking time can get quite tedious and sometimes you run into situations were you introduced some code that made the import a requirement at runtime.

How do you use TYPE_CHECKING? Whenever it is possible or only when using it actually solves a circular import?

top 13 comments
sorted by: hot top controversial new old
[–] thomasloven 3 points 1 year ago* (last edited 1 year ago) (3 children)

I don’t like having to quote the types, so I use it exclusively for avoiding circular imports.

[–] [email protected] 3 points 1 year ago (1 children)
from __future__ import annotations
[–] thomasloven 1 points 1 year ago

Thanks for the tip

[–] [email protected] 3 points 1 year ago (1 children)

Why not use from __future__ import annotations?

[–] thomasloven 1 points 1 year ago

Never heard of it. Thanks for the tip.

[–] [email protected] 1 points 1 year ago (1 children)

You still need to import the type before using it in a stringified type annotation for it to be valid though, so you'd need the import in an if TYPE_CHECKING: block either way, no?

[–] thomasloven 2 points 1 year ago (1 children)

Yes, but if it’s in a TYPE_CHECKING block I can ONLY use the annotation with quotes*, which is why I only use that method if I must.

  • except with from __future__ import annotations as I’ve just learned.
[–] [email protected] 1 points 1 year ago

Ah yeah, I see what you meant.

[–] rusty 2 points 1 year ago* (last edited 1 year ago)

That's a very cool feature, had no clue about it!

If it doesn't have any visible downsides, it's be nice use it whenever possible. This should provide the additional benefit of having the imports clearly separated.

The tediousness aspect of it makes me wonder though. I'd probably just only use it when I'm specifically importing something only for typing .

Maybe could be a cool feature request for an lsp as well.

[–] [email protected] 2 points 1 year ago

I only use it to avoid circular imports. Otherwise, I can import the type plainly.

[–] [email protected] 1 points 1 year ago

Any time you need different behavior between static type checking and runtime.

in 3.10 I’m using it to work around issue with NamedTuple generics. typing_extensions.NamedTuple allows Generics at runtime but typing.NamedTuple doesn’t. But the type checker we are using doesn’t support typing_extensions.NamedTuple like it does for the typing version so we lie at type checking time to get the typing to make sense but have different runtime type because otherwise its a TypeError

[–] [email protected] 1 points 1 year ago

A cheeky answer: whenever Ruff/flake8-type-checking tells me to. Though I'd only enable that check now that there's an autofix in Ruff as well.

[–] [email protected] 0 points 1 year ago

You should have part of your test harness perform a separate import of every module. If your module is idempotent (most good code is) you could do this in a single process by cleaning sys.modules I guess ... but it still won't be part of your pytest process.

Static analyzers can only detect some cases, so can't be fully trusted.

I've also found there are a lot of cases where performant Python code has to be implemented in a distinct way from what the type-checker sees. You can do this with aggressive type: ignore but I often find it cleaner to use separate if blocks.