
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?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>