[Python-3000] Questions on optional type annotations

Guido van Rossum guido at python.org
Thu May 11 07:50:41 CEST 2006


On 5/10/06, Collin Winter <collinw at gmail.com> wrote:
> I haven't been able to find any mention of these things in the
> python-3000 archive (nor in Bill Birch's proto-PEP on the subject
> [1]). These are all issues I've encountered while writing my typecheck
> package [2], which I think still need to be discussed and addressed
> for py3k's proposed type annotations.
>
> 1. Will there be some way of specifying "I expect parameter B to be a
> callable with signature X"? If yes, how does this affect the
> optional-ness of the type annotations?
>
> Put another way: let's say I've annotated a function using the
> following Haskell-derived strawman syntax. I want the sole parameter
> to be a callable that accepts two ints and returns an int:
>
> def foo((int, int -> int)): ...
>
> What happens if I pass foo a function that doesn't have type
> annotations, ie, a function where the system can't know its parameter
> and return types (for example, functions from C extension modules)? Is
> a TypeError raised (ie, you're forced to supply annotations)? Does the
> system simply accept the function and trust you've done the right
> thing? Is the function accepted but a warning is issued?
>
> If a TypeError is raised, do we risk heading toward C's const
> propagation issues?

Well, remember that the default use of type annotations is to ignore
them! You can write your own decorator that implements a specific
interpretation of the annotations, and you can make it do anything you
like.

I think it would be useful to have a notation that can express
signatures. I haven't spent much time thinking about what this would
look like, but I'd like it to require no new syntax beyond the concept
of type annotations. If this means you can't have the inline
equivalent of your (int int -> int), then perhaps it could be done by
referencing some prototype with the appropriate annotations. Or
perhaps someting as crude as Function(type, type, ..., returns=type)
would be good enough for inlining this.

Regarding the question what to do if something un-annotated is passed,
you could have a strict and a lenient mode, sort of the equivalents of
guilty-unless-proven-innocent and innocent-unless-proven-guilty. I
guess the latter is more Pythonic, but the former is more in style
with type checking systems... :-)

> 2. On a related note, will there be a way to add annotations to
> callables in extension modules?

It makes sense to allow this. We already allow the C code to specify a
doc string; I think it makes sense to also specify a signature.

> 3. Let's say you have two classes, each of which has methods that
> return instances of the other class. How do you supply type
> annotations for these? One has to be written above (and hence parsed
> and compiled) before the other, meaning that without some way of
> deferring the lookup of the type used in the annotation, you get
> NameErrors (or the equivalent in this case).

Good question. I'm glad you're thinking about these issues; I hope
you're working on a PEP (I'm losing track of these things since I'm
only able to spend the occasional two-hour session looking at Py3k
lately).

> typecheck solves this in an ugly way, using a Class() utility class to
> delay the lookup. Roughly translated in to BDFL syntax:
>
> """
> class A:
>    def foo(self, b_instance: B): ...
>
> class B:
>     def bar(self, a_instance: A): ...
> """
>
> becomes
>
> """
> class A:
>    def foo(self, b_instance: Class('B')): ...
>
> class B:
>     def bar(self, a_instance: A): ...
> """
>
> The first time we need Class('B') for typechecking purposes, some
> sys._getframe hackery is used to grab the real class instance and
> cache it for later. (By the way, I'd love alternative suggestions
> about how to do this in current Python.)

Instead of getframe, the argument to Class() could be a fully
qualified name: package.subpackage.module.class.

> Bill mentions converting the name of the type/interface/whatever to
> later-evaluated lambdas in the case of recursive datatypes, but I
> don't see any mention of generalising this idea.

Well, *some* kind of hack will be needed given the decision (which I
don't want to revert just yet) that the expression is a plain standard
expression that's evaluated at function-definition time.

If there's no other use for lambdas in the type notation, then you
could safely spell a forward reference to B as lambda: B. (This
appears to ironically require the use of callable(), which in a
different thread I'm still trying to ban; but since a class is also
callable, callable() would actually be useless here, and we could
instead dispatch specifically on the defining characteristics of
lambdas: it has type 'function' and name "<lambda>". )

> 4. How will tuples be treated? Some in the wider Python community
> treat tuples as simple frozen lists, while others view them as a data
> structures unto themselves. My question: will (0,), (0, 1) and (0, 1,
> 2) all match (int,) (frozen list view), or are (int,), (int, int) and
> (int, int, int) totally separate datatypes (more of a functional
> programming view)?

This I actually covered in last year's blog entries; I believe my
notation was tuple[int, int, str] to indicate a three-tuple containing
two ints and a str, forcing the "data structure" interpretation. If
you want to accept tuples but interpret them as sequences, you should
declare your argument as Sequence[some_type].

-- 
--Guido van Rossum (home page: http://www.python.org/~guido/)


More information about the Python-3000 mailing list