[Python-ideas] Optional static typing -- the crossroads

Steven D'Aprano steve at pearwood.info
Sun Aug 17 09:26:02 CEST 2014


On Sat, Aug 16, 2014 at 11:02:01PM -0700, Andrew Barnert wrote:
> On Aug 16, 2014, at 22:03, Guido van Rossum <guido at python.org> wrote:
> 
> > Moving on to (2), the proposal is elegant enough by itself, and indeed has the advantage of being clear and concise: [T] instead of List[T], {T: U} instead of Dict[T, U], and so on. However, there are a few concerns.
> > 
> > My first concern is that these expressions are only unambiguous in the context of function annotations.
> 
> Good point. Together with your third point (that [str] could be 
> meaningful as a different type of annotation, while Iterable[str] is 
> incredibly unlikely to mean anything other than a static type 
> check--except maybe a runtime type check, but I think it's reasonable 
> to assume they can share annotations), I think this kills the idea. 
> Pity, because I like the way it looked.

[str] looks nice, but it looks like a list of str, or possibly an 
optional str, e.g. from the docstring of int:

    int(x[, base]) -> integer

What the [str] syntax doesn't look like is an Iterable of str. Or should 
that be Sequence of str? MutableSequence perhaps? If [str] means 
something other than list of str, it is going to be some arbitrary 
special case to be memorized.

Have pity on people teaching Python. I don't want to have to try to 
explain to beginners why [str] sometimes means a list and sometimes an 
arbitrary Iterable (or whatever). This is just downright confusing:

def func(arg:[str]):
    x = [str]
    assert isinstance(x, list)  # Always passes.
    assert isinstance(arg, list)  # Sometimes fails.

func(iter("abc"))  # Fails.


[Guido]
> > All in all I prefer the mypy syntax, despite being somewhat more 
> > verbose and requiring an import, with one caveat: I agree that it 
> > would be nicer if the mypy abstract collection types were the same 
> > objects as the ABCs exported by collections.abc. I'm not quite sure 
> > whether we should also change the concrete collection types from 
> > List, Dict, Set, Tuple to list, dict, set, tuple;

We can start with typing.List, Dict, etc., and later on consider using 
builtins.

I worry that if we use builtins, people will declare x:list[int] not 
because they *need* a list of int, but because it saves typing over 

from typing import Sequence, Integer
def func(x:Sequence[Integer]):

So even though I suggested earlier that the builtins grow appropriate 
__getitem__ methods, on second thoughts I would be very cautious about 
introducing that.


> > the concrete types 
> > are so ubiquitous that I worry that there may be working code out 
> > there that somehow relies on the type objects themselves not being 
> > subscriptable.

[Andrew] 
> I won't belabor the point, but again: I don't think we need a generic 
> list type object, and without it, this entire problem--your only 
> remaining problem that isn't a mere stylistic choice--vanishes.

I don't understand. If there's no list typing object, how do you declare 
a variable must be a list and nothing but a list? Or that it returns a 
list?


[Guido]
> > A mostly unrelated issue: there are two different uses of tuples, 
> > and we need a notation for both. One is a tuple of fixed length with 
> > heterogeneous, specific types for the elements; for example 
> > Tuple[int, float]. But I think we also need a way to indicate that a 
> > function expects (or returns) a variable-length tuple with a 
> > homogeneous element type.

Throwing this idea out to be shot down: use some sort of slice notation.

Tuple[int, float, str]  # Like (23, 1.5, "spam")

Tuple[::int, float, str]  # Like (1, 2, 3, 4) or (1.5,) or ("x", "y")

That is, if the argument to __getitem__ is a slice (None, None, T), T is 
either a type or a tuple of types. Any other kind of slice is reserved 
for the future, or an error.

> > Perhaps we should call this type 
> > frozenlist, analogous to frozenset (and it seems there's a proposal 
> > for frozendict making the rounds as well).

[Andrew]
> Even if you drop the idea for [str] and {int: str}, which I agree 
> seems unavoidable, I think it may still make sense for (int, str) to 
> mean a heterogeneous iterable.

That makes no sense to me. It looks like a tuple, not a generic iterable 
object. Your interpretation has the same problems I discussed above for 
[str] notation: it is an arbitrary choice whether (...) means Iterable, 
Sequence or ImmutableSequence, and it doesn't fit nicely with other 
common uses of parens. See below.


> Python already has target lists, argument lists, parameter lists, and 
> expression lists that all have the same syntax as tuples or a superset 
> thereof, but don't define tuples. In (a, b) = zip(c, d), neither (a, 
> b) nor (c, d) is a tuple, and I don't think anyone is confused by 
> that. 

Ha , you've obviously stopped reading the "Multi-line with statement" 
thread on Python-Dev :-)


> So, why can't def foo(spam: (int, str)) mean that spam is an 
> iterable of an int and a str, in exactly the same way that the 
> assignment statement means that a and b are assigned the result of 
> unpacking the iterable returned by zip when called with c and d?

But a, b = zip(c, d) requires that there be exactly two elements, not 
some unspecified number.

To me, spam:(int, str) has a natural interpretation that spam can be 
either an int or a str, not an Iterable or Sequence or even a tuple.

> And this leaves Tuple[str] or tuple[str] free to mean a homogenous 
> tuple (although, again, I don't think we even want or need that...).

We do. Consider the isinstance() function. Here's the signature 
according to its docstring:

isinstance(object, class-or-type-or-tuple) -> bool

The second argument can be a single type, or a tuple of an arbitrary 
number of types. I'd write it with annotations like:

def isinstance(object:Any, 
               class_or_type_or_tuple:(Type, Tuple[::Type])
               )->Bool:

assuming (a,b) means "either a or b" and Tuple[::a] means a homogenous 
tuple of a. (With shorter argument names, it even fits on one line.)

And, here's issubclass:

def issubclass(C:Type, D:(Type, Tuple[::Type]))->Bool:



-- 
Steven


More information about the Python-ideas mailing list