[Python-ideas] Type Hinting Kick-off
Eugene Toder
eltoder at gmail.com
Thu Dec 25 01:50:09 CET 2014
Guido van Rossum <guido at ...> writes:
> A few months ago we had a long discussion about type hinting. I've
thought a
> lot more about this. I've written up what I think is a decent "theory"
> document -- writing it down like this certainly helped *me* get a lot
of
> clarity about some of the important
issues.https://quip.com/r69HA9GhGa7J
(I apologize in advance if some of this was covered in previous
discussions).
1. Since there's the Union type, it's also natural to have the
Intersection
type. A class is a subclass of Intersection[t1, t2, ...] if it's a
subclass
of all t1, t2 etc. The are 2 main uses of the Intersection type:
a) Require that an argument implements multiple interfaces:
class Foo:
@abstractmethod
def foo(self): ...
class Bar:
@abstractmethod
def bar(self): ...
def fooItWithABar(obj: Intersection[Foo, Bar]): ...
b) Write the type of an overloaded function:
@overload
def foo(x: str) -> str: ...
@overload
def foo(x: bytes) -> bytes: ...
foo # type: Intersection[Callable[[str], str], Callable[[bytes], bytes]]
2. Constrained type variables (Var('Y', t1, t2, ...)) have a very
unusual
behavior.
a) "subclasses of t1 etc. are replaced by the most-derived base class
among t1
etc."
This defeats the very common use of constrained type variables: have a
type
preserving function limited to classes inherited from a common base.
E.g. say
we have a function:
def relocate(e: Employee) -> Employee: ...
The function actually always returns an object of the same type as the
argument, so we want to write a more precise type. We usually do it like
this:
XEmployee = Var('XEmployee', Employee)
def relocate(e: XEmployee) -> XEmployee: ...
This won't work with the definition from the proposal.
b) Multiple constraints introduce an implicit Union. I'd argue that type
variables are more commonly constrained by an Intersection rather than a
Union. So it will be more useful if given this definition Y has to be
compatible with all of t1, t2 etc, rather than just one of them.
Alternatively, this can be always spelled out explicitly:
Y1 = Var('Y1', Union[t1, t2, ...])
Y2 = Var('Y2', Intersection[t1, t2, ...])
Pragmatics:
3. The names Union and Intersection are standard terminology in type
checking,
but may not be familiar to many Python users. Names like AnyOf[] and
AllOf[]
can be more intuitive.
4. Similar to allowing None to mean type(None) it's nice to have
shortcuts
like:
(t1, t2, ...) == Tuple[t1, t2, ...]
[t1] == List[t1]
{t1: t2} == Dict[t1, t2]
{t1} == Set[t1]
The last 3 can be Sequence[t1], Mapping[t1, t2] and collections.Set[t1]
if we
want to encourage the use of abstract types.
5. Using strings for forward references can be messy in case of
generics:
parsing of brackets etc in the string will be needed. I propose explicit
forward declarations:
C = Declare('C')
class C(Generic[X]):
def foo(self, other: C[X]): ...
def bar(self, other: C[Y]): ...
6. On the other hand, using strings for unconstrained type variables is
quite
handy, and doesn't share the same problem:
def head(xs: List['T']) -> 'T': ...
Regards,
Eugene
More information about the Python-ideas
mailing list