[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