@@ -1079,8 +1079,8 @@ based on iterating over all attributes.
10791079 type InitFnType[T] = typing.Member[
10801080 Literal["__init__"],
10811081 Callable[
1082- [
1083- typing.Param[Literal["self"], Self ],
1082+ typing.Params [
1083+ typing.Param[Literal["self"], T ],
10841084 *[
10851085 typing.Param[
10861086 p.name,
@@ -1117,7 +1117,7 @@ based on iterating over all attributes.
11171117 # Add the computed __init__ function
11181118 InitFnType[T],
11191119 ]:
1120- pass
1120+ raise NotImplementedError
11211121
11221122Or to create a base class (a la Pydantic) that does.
11231123
@@ -1130,86 +1130,40 @@ Or to create a base class (a la Pydantic) that does.
11301130 # Add the computed __init__ function
11311131 InitFnType[T],
11321132 ]:
1133- super().__init_subclass__()
1134-
1135-
1136- NumPy-style broadcasting
1137- ------------------------
1138-
1139- One of the motivations for the introduction of ``TypeVarTuple `` in
1140- :pep: `646 ` is to represent the shapes of multi-dimensional
1141- arrays, such as::
1142-
1143- x: Array[float, L[480], L[640]] = Array()
1144-
1145- The example in that PEP shows how ``TypeVarTuple `` can be used to
1146- make sure that both sides of an arithmetic operation have matching
1147- shapes. Most multi-dimensional array libraries, however, also support
1148- `broadcasting <#broadcasting _>`__, which allows the mixing of differently
1149- shaped data. With this PEP, we can define a ``Broadcast[A, B] `` type
1150- alias, and then use it as a return type::
1151-
1152- class Array[DType, *Shape]:
1153- def __add__[*Shape2](
1154- self,
1155- other: Array[DType, *Shape2]
1156- ) -> Array[DType, *Broadcast[tuple[*Shape], tuple[*Shape2]]]:
1157- raise BaseException
1158-
1159- (The somewhat clunky syntax of wrapping the ``TypeVarTuple `` in
1160- another ``tuple `` is because typecheckers currently disallow having
1161- two ``TypeVarTuple `` arguments. A possible improvement would be to
1162- allow writing the bare (non-starred or ``Unpack ``-ed) variable name to
1163- mean its interpretation as a tuple.)
1133+ pass
11641134
1165- We can then do::
11661135
1167- a1: Array[float, L[4], L[1]]
1168- a2: Array[float, L[3]]
1169- a1 + a2 # Array[builtins.float, Literal[4], Literal[3]]
1136+ .. _pep827-zip-impl :
11701137
1171- b1: Array[float, int, int]
1172- b2: Array[float, int]
1173- b1 + b2 # Array[builtins.float, int, int]
1174-
1175- err1: Array[float, L[4], L[2]]
1176- err2: Array[float, L[3]]
1177- # err1 + err2 # E: Broadcast mismatch: Literal[2], Literal[3]
1178-
1179-
1180- Note that this is meant to be an example of the expressiveness of type
1181- manipulation, and not any kind of final proposal about the typing of
1182- tensor types.
1138+ zip-like functions
1139+ ------------------
11831140
1184- .. _pep827-numpy-impl :
1185-
1186- Implementation
1187- ''''''''''''''
1141+ Using type iteration and ``GetArg ``, we can give a proper type to ``zip ``.
11881142
11891143::
11901144
1191- class Array[DType, *Shape]:
1192- def __add__[*Shape2](
1193- self, other: Array[DType, *Shape2]
1194- ) -> Array[DType, *Broadcast[tuple[*Shape], tuple[*Shape2]]]:
1195- raise BaseException
1145+ type ElemOf[T] = typing.GetArg[T, Iterable, Literal[0]]
1146+
1147+ def zip[*Ts](
1148+ *args: *Ts, strict: bool = False
1149+ ) -> Iterator[tuple[*[ElemOf[t] for t in typing.Iter[tuple[*Ts]]]]]:
1150+ return builtins.zip(*args, strict=strict) # type: ignore[call-overload]
11961151
1197- ``MergeOne `` is the core of the broadcasting operation. If the two types
1198- are equivalent, we take the first, and if either of the types is
1199- ``Literal[1] `` then we take the other.
1152+ Using the ``Slice `` operator and type alias recursion, we can
1153+ also give a more precise type for zipping together heterogeneous tuples.
12001154
1201- On a mismatch, we use the `` RaiseError `` operator to produce an error
1202- message identifying the two types.
1155+ For example, zipping `` tuple[int, str] `` and `` tuple[str, bool] ``
1156+ should produce `` tuple[tuple[int, float], tuple[str, bool]] ``
12031157
12041158::
12051159
1206- type MergeOne[T, S] = (
1207- T
1208- if typing.IsEquivalent[T, S] or typing.IsEquivalent[S, Literal[1]]
1209- else S
1210- if typing.IsEquivalent[T, Literal[1]]
1211- else typing.RaiseError[Literal["Broadcast mismatch"], T, S]
1212- )
1160+ def zip_pairs[*Ts, *Us] (
1161+ a: tuple[*Ts], b: tuple[*Us]
1162+ ) -> Zip[tuple[*Ts], tuple[*Us]]:
1163+ return cast(
1164+ Zip[tuple[*Ts], tuple[*Us]],
1165+ tuple(zip(a, b, strict=True)),
1166+ )
12131167
12141168 type DropLast[T] = typing.Slice[T, Literal[0], Literal[-1]]
12151169 type Last[T] = typing.GetArg[T, tuple, Literal[-1]]
@@ -1218,20 +1172,18 @@ message identifying the two types.
12181172 # recursions when T is not a tuple.
12191173 type Empty[T] = typing.IsAssignable[typing.Length[T], Literal[0]]
12201174
1221- Broadcast recursively walks down the input tuples applying ``MergeOne ``
1222- until one of them is empty.
1175+ Zip recursively walks down the input tuples until one or both of them
1176+ is empty. If the lengths don't match (because only one is empty),
1177+ raise an error.
12231178
12241179::
12251180
1226- type Broadcast[T, S] = (
1227- S
1228- if typing.Bool[Empty[T]]
1229- else T
1230- if typing.Bool[Empty[S]]
1231- else tuple[
1232- *Broadcast[DropLast[T], DropLast[S]],
1233- MergeOne[Last[T], Last[S]],
1234- ]
1181+ type Zip[T, S] = (
1182+ tuple[()]
1183+ if typing.Bool[Empty[T]] and typing.Bool[Empty[S]]
1184+ else typing.RaiseError[Literal["Zip length mismatch"], T, S]
1185+ if typing.Bool[Empty[T]] or typing.Bool[Empty[S]]
1186+ else tuple[*Zip[DropLast[T], DropLast[S]], tuple[Last[T], Last[S]]]
12351187 )
12361188
12371189
0 commit comments