On Sat, Aug 16, 2014 at 11:02:01PM -0700, Andrew Barnert wrote:
On Aug 16, 2014, at 22:03, Guido van Rossum <guido@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