typing icon indicating copy to clipboard operation
typing copied to clipboard

Add a `import type` feature (like typescript)

Open KotlinIsland opened this issue 4 years ago • 4 comments

Importing types can lead to circular imports, longer start up times and unwanted side effects.

I want something like:

from foo.bar import type Baz

def foo(baz: Baz):
    print(baz.bat)

Such that foo, foo.bar and foo.bar.Baz are not actually loaded at all. It could 'import' the symbol as a forward-ref or some type machinery thing. (maybe just the string "Baz")

Alternatives:

from __future__ import annotations

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from foo.bar import Baz

def foo(baz: Baz):
    print(baz.bat)

This is mega boilerplate, gross, and leads to messy wacky 'type-time' side effects (https://github.com/python/mypy/issues/11503).

KotlinIsland avatar Nov 09 '21 06:11 KotlinIsland

+1 to this feature, it would help the code to be much readable to the alternative that we have right now

h-joo avatar Oct 15 '24 09:10 h-joo

I was personally interested in this but if it gave you lazy imports so it'd still be introspectable

Gobot1234 avatar Oct 15 '24 14:10 Gobot1234

An easy solution is to have

from foo import type Baz

be syntactic sugar for

type Baz[_] = __import__('foo').Baz

In other words, create a (generic) type alias whose __value__ is lazily resolved to the actual Baz class.

mkzeender avatar Oct 29 '24 05:10 mkzeender

Would be nice to avoid needing to specify type for a collection of type-only imports.

Python named imports (from ... import ...) are analogous to TypeScript 'named' imports. Python doesn't have an equivalent to TypeScript 'default' imports, so those can be ignored. Python module imports (import ...) are analogous to TypeScript 'namespace' imports. Python wildcard imports (from ... import *) don't have an analogue in TypeScript, but can be considered much the same as named imports for this purpose, I believe.

Current TypeScript import examples (restricted to syntax with appropriate Python analogue)

// region named imports
import { foo, bar } from "..."  // value-only named imports
import { foo, bar } from "..."  // as above, with 'rename'

import type { Foo, Bar } from "..."  // type-only named imports
import type { Foo: FooByAnotherName, Bar } from "..."  // as above, with 'rename'

import { type Foo, type Bar } from "..."  // type-only named imports, specified per-name
import { type Foo: FooByAnotherName, type Bar } from "..."  // as above, with 'rename'

import { foo, type Foo } from "..."  // mixed named imports, specified per-name
import { foo as fooByAnotherName, type Foo } from "..."  // as above, with 'rename'
// endregion

// region namespace imports
import * as module from "..."  // value namespace import with 'rename'
import type * as Module from "..."  // type namespace import with 'rename'
// endregion

Here's the Python syntax I propose, covering Python's various 'flavours' of import:

# region named imports
from ... import foo, bar  # value-only named imports (unparenthesised)
from ... import foo as foo_by_another_name, bar  # as above, with 'rename'
from ... import (foo, bar)  # value-only named imports (parenthesised)
from ... import (foo as foo_by_another_name, bar)  # as above, with 'rename'

from ... import type (Foo, Bar)  # type-only named imports (parenthesis required)
from ... import type (Foo as FooByAnotherName, Bar)  # as above, with 'rename'

from ... import type Foo, type Bar  # type-only named imports, specified per-name (unparenthesised)
from ... import type Foo as FooByAnotherName, type Bar  # as above, with 'rename'
from ... import (type Foo, type Bar)  # type-only named imports, specified per-name (parenthesised)
from ... import (type Foo as FooByAnotherName, type Bar)  # as above, with 'rename'

from ... import foo, type Foo  # mixed named imports, specified per-name (unparenthesised)
from ... import foo as foo_by_another_name, type Foo  # as above, with 'rename'
from ... import (foo, type Foo)  # mixed named imports, specified per-name (parenthesised)
from ... import (foo as foo_by_another_name, type Foo  # as above, with 'rename'
# endregion

# region module imports
import ...  # value module import
import ... as module  # as above, with 'rename'
import ..., ...  # value multiple module import
import ... as module, ...  # as above, with 'rename'
import type ...  # type module import
import type ... as Module  # as above, with 'rename'
import type ..., type ...  # type multiple module import
import type ... as Module, type ...  # as above, with 'rename'
# endregion

# region wildcard imports
from ... import *  # value wildcard import
from ... import type *  # type wildcard import
# endregion

Here's a modified import_stmt grammar that I think matches the above: https://gist.github.com/Lordfirespeed/8a8140d94007c45679a3701b90968c12

An alternative to the 'parenthesis required' rule would be to use types vs type to distinguish between type-only and mixed imports. However, that would conflict with the existing types module, and I don't know enough about grammars/parsers to know whether that would break e.g. import types.

Lordfirespeed avatar Mar 06 '25 17:03 Lordfirespeed