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

Andrew Barnert abarnert at yahoo.com
Sun Aug 17 12:41:17 CEST 2014


On Aug 17, 2014, at 2:41, Steven D'Aprano <steve at pearwood.info> wrote:

> On Sun, Aug 17, 2014 at 01:52:21AM -0700, Andrew Barnert wrote:
>> On Aug 17, 2014, at 0:26, Steven D'Aprano <steve at pearwood.info> wrote:
>> 
>>> On Sat, Aug 16, 2014 at 11:02:01PM -0700, Andrew Barnert wrote:
>>> 
>>>> 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?
>> 
>> You think about it and make sure you really do need a list and nothing 
>> but a list. Most of the time (as in all three of the examples given in 
>> this thread) this is a mistake. If it's not, then you use List. (Or, 
>> if the stdlib doesn't provide that, you have to write one line of 
>> code: List = TypeAlias(list), and then you can use it.)
> 
> Ah, that is the point I missed. You think that the stdlib shouldn't 
> provide a standard typing object for lists, but that people should just 
> create their own if they need it.
> 
> Okay, but I don't understand why you're singling out lists. If you want 
> to propose providing only abstract classes (Sequence, Mapping, etc.) 
> and not concrete classes (list, dict, etc.) by default, that makes 
> some sense to me. But I don't understand including typing.Dict as 
> an alias for dict (say) but not List.

I'm not singling out lists. I referred to tuple in exactly the same way in the same message. I didn't mention dict, frozenset, etc. because I'd already given the full argument a few hundred messages ago and I didn't think anyone wanted to read it again. But the "if" sentence in your paragraph is a good summary of the whole thing.

>> If having list[T] is going to be more of an attractive nuisance than a 
>> useful feature, and it will be especially attractive and nuisanceful 
>> for exactly the same novices who are unlikely to know how to TypeAlias 
>> it themselves, why is it a problem to leave it out?
> 
> There are at least three scenarios:
> 
> (1) Built-ins can be used directly in static type annotations:
> 
>    x:list[dict]
> 
>    This has the advantage of not needing special names, but the 
>    disadvantage of encouraging lazy programmers to specify concrete
>    types when they should be using abstract Sequence[Mapping].
> 
> (2) Built-ins *cannot* be used, you have to import them from typing:
> 
>    from typing import List, Dict
>    x:List[Dict]
> 
>    The advantage is that since you have to do an import anyway,
>    it is not much more effort to Do The Right Thing:
> 
>    from typing import Sequence, Mapping
>    x:Sequence[Mapping]
> 
> (3) And the final scenario, the one which confuses me, but seems 
>    to be what you are suggesting: you can use the built-ins, 
>    *but not list*, and there is no List to import either:
> 
>    from typing import Sequence
>    x:Sequence[dict]
> 
>    I don't understand the advantage of this.

As explained above, I'm suggesting (2), not (3).

> [...]
>>>> 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.
>> 
>> And spam:(int, str) requires that there be exactly two elements (and 
>> that the first be an int and the second a str), not some unspecified 
>> number. How is that any different?
> 
> Ah, that's what I didn't understand. I thought you meant an iterable of 
> either ints or strs, without requiring a fixed number of them.

Right. The "paradigm case" for tuples is as fixed-length, heterogeneous collections, where each index has a specific semantic meaning (and therefore type).

The problem is that, at least in Python, tuples are _also_ used as general-purpose immutable sequences--and, in a few cases, that's specifically enshrined in syntax (e.g., the exception types in an except statement) or builtins (e.g., the arguments to str.__mod__), so we can't just ignore that.

My suggestion is that (int, str) means the first (what you'd call int * str if you wanted your language to look more like type theory than something a normal human would write), so Tuple[int] works exactly like every other generic type: it's a homogenous collection of ints.

>>> 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.
>> 
>> OK, I see the parallel there with exception statements now that you 
>> mention it.
>> 
>> But almost anywhere else in Python, a comma-separated list is a 
>> sequence of values, targets, parameters, etc., not a disjunction. The 
>> obvious way to spell what you want here is "int | str"
> 
> Which mypy spells as Union[ ].
> 
> http://mypy-lang.org/tutorial.html

Yes, but multiple people in this thread have suggested spelling it as int | str, nobody's objected, and both Jukka and Guido have given at least tentative assent.

(I realize there's a whole lot of messages to read through and remember. And I could easily have missed an argument against this syntax just as you missed the suggestions for it.)


More information about the Python-ideas mailing list