[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