I just realised that a related question is how unparameterized aliases should behave.

The easiest thing would be to have a general rule: "If you omit the type parameter list, you replace `*Ts` with an arbitrary number of `Any`". Then we could do:

DType = TypeVar('DType')
Shape = TypeVarTuple('Shape')

class Array(Generic[DType, *Shape]): ...

Float32Array = Array[np.float32, *Shape]

def takes_float_array_of_any_shape(x: Float32Array): ...

x: Array[np.float32, Height]
takes_float_array_of_any_shape(x)  # Valid
y: Array[np.float32, Height, Width]
takes_float_array_of_any_shape(y)  # Also valid

One complication, though, is that it implies:

IntTuple = Tuple[int, *Ts]

IntTuple  # Behaves like Tuple[int, Any, ...]!?

I'm guessing there was a good reason that heterogeneous tuples of arbitrary length like this weren't supported by PEP 484?

Other options I can think of are:
  • Disallow unparameterized variadic generic aliases in general
    • This doesn't seem great because it would decrease backwards compatibility. Ideally, if a function in new library code takes a `Float32Array` (`== Array[Any, Any, ...]`), we want to let legacy code pass in a plain `Array` (`== Array[Any, ...]`).
    • Also, it would be inconsistent with non-variadic generic aliases, where I think the type variables are just replaced as `Any` when unparameterized?
  • Pull the same trick we did with unparameterized classes: not making `Tuple[int, Any, ...]` valid, but just saying that an unparameterized alias behaves as if it had been written like that.

On Wed, 17 Feb 2021 at 21:06, Matthew Rahtz <mrahtz@google.com> wrote:
Ok, if we're saying that it wouldn't be too abhorrent to treat Tuple differently, I do think supporting both cases is important for Tensor:

def foo(x: Tensor): ...
x: Tensor[Height, Width]

def bar(y: Tensor[Height, Width]): ...
y: Tensor

So I'll go ahead and state in the PEP that it should work both ways (and add some notes on our discussion about this to the Rationale section).

On Mon, 15 Feb 2021 at 04:33, Eric Traut <eric@traut.com> wrote:
I don't think it's feasible to change rules about tuple type compatibility at this point. Existing type stubs (including typeshed stubs) assume the current behavior. It's especially important for function overload matching. If we were to change the rules and make open-ended tuples assignable to fixed-sized tuples, the selected overload would change in some cases.

I also agree with Guido's justification for the current behavior (in the case of tuples). So even if it were feasible to change the behavior at this point, I don't think it would make sense to do so.

I'm not opposed to adopting different rules for (non-tuple) variadic generic classes if we think that the target use cases justify it. Of course, all things being equal, I'd favor consistency. Hopefully that's not considered a "foolish consistency". :)

Eric Traut
Contributor to Pyright & Pylance
Microsoft Corp.
Typing-sig mailing list -- typing-sig@python.org
To unsubscribe send an email to typing-sig-leave@python.org
Member address: mrahtz@google.com