Or perhaps

type:
    import a
    from b import c
    def f() -> int: pass

(You could even add an 'else:' clause. :-)

On Fri, Apr 16, 2021 at 10:47 AM Carl Meyer <carl@oddbird.net> wrote:
This proposal also naturally extends to:

    type import mod

and

    from mod type import X, Y

These use cases don't need the custom syntax support, but with the
same lazy evaluation they would form a natural replacement for
guarding imports with `if TYPE_CHECKING:`, which would also be
compatible with PEP 649 and runtime evaluation of annotations.

Carl

On Fri, Apr 16, 2021 at 11:00 AM Jukka Lehtosalo <jlehtosalo@gmail.com> wrote:
>
> Many people seem to agree that Python typing still has some rough edges. Various PEPs have fixed warts (or proposed fixes), including PEPs 585, 563, and 649. Still, several issues remain:
>
> 1. Type annotation syntax is not always elegant (consider callable types). There's no good way to add custom type syntax without "polluting" non-typing use cases.
>
> 2. String literal escaping is ugly, and it's still needed from time to time.
>
> 3. Runtime introspection of types feels like a second-class citizen, even though it's pretty popular (pydantic, etc.).
>
> Instead of trying to tackle these issues piecemeal, I've tried to come up with ideas to solve all of the above issues. They don't form a complete proposal, and I haven't worked out many details yet, but I'm hoping that this could be a discussion starter, and perhaps it can help us eventually design something that is workable.
>
> Here I explain my first idea -- type assignment. Right now, we can't easily add dedicated syntax  for callable types, for example, without either making this syntax available everywhere, or making it *un*available in type aliases and other common use cases, which don't use the annotation syntax.
>
> Supporting new syntax in annotations seems manageable:
>
>   def doit(f: () => int) -> None: ...  # Could be supported
>
> However, what about type aliases:
>
>   A = () => int  # Hmm?
>
> If this works, nothing stops people from writing weird code like this:
>
>   d = {'x': (1 + y) => foo(1, 2)}
>
> I'd rather have dedicated typing syntax reserved for type-related uses, mainly to avoid confusing Python users who don't use typing. If typing syntax is only available via a small number of special syntactic forms, it's easier to teach users to ignore it, if they don't care about it.
>
> I'm proposing a new construct called "type assignment" where the RHS is lazily evaluated, and in the future it might support typing-only syntax. There would be two variants. First, we can define a type alias like this:
>
>   type A = list[int]
>
> If we'd use the new syntax for callable types, we could write an alias like this:
>
>   type C = (int) => str  # No problem
>
> However, the following would be a syntax error, since it's not in a "type context":
>
>   C = (int) => str  # Error, as it probably should be
>
> Since the right hand side of type assignment contains types, we can avoid evaluating them during module import time, using either PEP 563 semantics (stringification) or something resembling PEP 649 (lazy evaluation). This means that we won't need to use explicit string literal escaping:
>
>   type A = B  # OK!
>   class B: ...
>
> If the RHS is a function call, we'd call the function at runtime, but arguments would be (partially) stringified/lazy and they can contain type syntax (I'm glossing over some details):
>
>   type N = NewType(int)
>   type T = TypeVar(bound=(int) => str)  # Type-specific syntax is ok
>   type D = typing.TypedDict({"x": C})  # C can be a forward reference!
>   class C: ...
>
> Note that the results here are usable in runtime contexts, since we call the function:
>
>   x = N(2)  # Ok
>
> The general syntax would be "type <id> = <name>(...)". We'd pass the stringified name as an implicit first argument for a cleaner syntax.
>
> We can write the earlier examples today like this:
>
>   T = TypeVar("T", bound=Callable[[int], str])
>   N = NewType("N", int)
>   D = typing.TypedDict("D", {"x": "C"})  # Need escaping here
>   class C: ...
>
> The new syntax uses 'type', which is widely used as a name. The new meaning would only apply in this assignment context, and all existing uses are still fine. 'type' wouldn't be a keyword.
>
> To support the function call syntax lazily, we want to be able to distinguish between types and non-types in arguments.
>
> First, any literals would be eagerly calculated and not escaped:
>
>   type T = f(1, "y", kw=False)  # evaluated as T = f(1, "y", kw=False)
>
> Name expressions are lazy, to allow forward references:
>
>   type T = f(C, kw=D)  # T = f("C", kw="D") [if using PEP 563]
>
> If using delayed evaluation instead of stringification, we can create some "lazy type reference" objects instead of strings (this is kind of hand-wavy).
>
> Similarly, tuples, lists and dictionaries would be eager (but items can be lazy):
>
>   type T = f(C, [("x", list[C])])  # T = f("C", [("x", "list[C]")])
>
> This would resemble PEP 563 stringification, but it needs to be a little more flexible.
>
> The type statement syntax is general enough to support various existing use cases, including NewType, TypeVar, TypedDict, NamedTuple, and others, and it's also extensible to new forms.
>
> I'll write separate posts about my other ideas I mentioned earlier.
>
> Jukka
> _______________________________________________
> Typing-sig mailing list -- typing-sig@python.org
> To unsubscribe send an email to typing-sig-leave@python.org
> https://mail.python.org/mailman3/lists/typing-sig.python.org/
> Member address: carl@oddbird.net
_______________________________________________
Typing-sig mailing list -- typing-sig@python.org
To unsubscribe send an email to typing-sig-leave@python.org
https://mail.python.org/mailman3/lists/typing-sig.python.org/
Member address: guido@python.org


--
--Guido van Rossum (python.org/~guido)
Pronouns: he/him (why is my pronoun here?)