Skip to content

Commit eb62db6

Browse files
committed
PEP 827: Swap out the numpy-like example for zip
1 parent 454541c commit eb62db6

1 file changed

Lines changed: 34 additions & 82 deletions

File tree

peps/pep-0827.rst

Lines changed: 34 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -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

11221122
Or 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

Comments
 (0)