dycw-utilities 0.133.7__py3-none-any.whl → 0.134.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
utilities/iterables.py CHANGED
@@ -22,10 +22,8 @@ from operator import add, itemgetter, or_
22
22
  from typing import (
23
23
  TYPE_CHECKING,
24
24
  Any,
25
- Generic,
26
25
  Literal,
27
26
  TypeGuard,
28
- TypeVar,
29
27
  assert_never,
30
28
  cast,
31
29
  overload,
@@ -43,46 +41,34 @@ from utilities.math import (
43
41
  )
44
42
  from utilities.reprlib import get_repr
45
43
  from utilities.sentinel import Sentinel, sentinel
46
- from utilities.types import Sign, THashable, TSupportsAdd, TSupportsLT
44
+ from utilities.types import SupportsAdd, SupportsLT
47
45
 
48
46
  if TYPE_CHECKING:
49
47
  from types import NoneType
50
48
 
51
- from utilities.types import MaybeIterable, MaybeIterableHashable, StrMapping
52
-
53
-
54
- _K = TypeVar("_K")
55
- _T = TypeVar("_T")
56
- _U = TypeVar("_U")
57
- _V = TypeVar("_V")
58
- _W = TypeVar("_W")
59
- _T1 = TypeVar("_T1")
60
- _T2 = TypeVar("_T2")
61
- _T3 = TypeVar("_T3")
62
- _T4 = TypeVar("_T4")
63
- _T5 = TypeVar("_T5")
49
+ from utilities.types import MaybeIterable, MaybeIterableHashable, Sign, StrMapping
64
50
 
65
51
 
66
52
  ##
67
53
 
68
54
 
69
- def always_iterable(obj: MaybeIterable[_T], /) -> Iterable[_T]:
55
+ def always_iterable[T](obj: MaybeIterable[T], /) -> Iterable[T]:
70
56
  """Typed version of `always_iterable`."""
71
57
  obj = cast("Any", obj)
72
58
  if isinstance(obj, str | bytes):
73
- return cast("list[_T]", [obj])
59
+ return cast("list[T]", [obj])
74
60
  try:
75
- return iter(cast("Iterable[_T]", obj))
61
+ return iter(cast("Iterable[T]", obj))
76
62
  except TypeError:
77
- return cast("list[_T]", [obj])
63
+ return cast("list[T]", [obj])
78
64
 
79
65
 
80
66
  ##
81
67
 
82
68
 
83
- def always_iterable_hashable(
84
- obj: MaybeIterable[_T] | None, /
85
- ) -> MaybeIterableHashable[_T] | None:
69
+ def always_iterable_hashable[T](
70
+ obj: MaybeIterable[T] | None, /
71
+ ) -> MaybeIterableHashable[T] | None:
86
72
  """Ensure an object is always hashable."""
87
73
  return None if obj is None else tuple(always_iterable(obj))
88
74
 
@@ -90,9 +76,9 @@ def always_iterable_hashable(
90
76
  ##
91
77
 
92
78
 
93
- def apply_bijection(
94
- func: Callable[[_T], _U], iterable: Iterable[_T], /
95
- ) -> Mapping[_T, _U]:
79
+ def apply_bijection[T, U](
80
+ func: Callable[[T], U], iterable: Iterable[T], /
81
+ ) -> Mapping[T, U]:
96
82
  """Apply a function bijectively."""
97
83
  keys = list(iterable)
98
84
  try:
@@ -112,21 +98,21 @@ def apply_bijection(
112
98
 
113
99
 
114
100
  @dataclass(kw_only=True, slots=True)
115
- class ApplyBijectionError(Exception, Generic[_T]):
116
- keys: list[_T]
117
- counts: Mapping[_T, int]
101
+ class ApplyBijectionError[T](Exception):
102
+ keys: list[T]
103
+ counts: Mapping[T, int]
118
104
 
119
105
 
120
106
  @dataclass(kw_only=True, slots=True)
121
- class _ApplyBijectionDuplicateKeysError(ApplyBijectionError[_T]):
107
+ class _ApplyBijectionDuplicateKeysError[T](ApplyBijectionError[T]):
122
108
  @override
123
109
  def __str__(self) -> str:
124
110
  return f"Keys {get_repr(self.keys)} must not contain duplicates; got {get_repr(self.counts)}"
125
111
 
126
112
 
127
113
  @dataclass(kw_only=True, slots=True)
128
- class _ApplyBijectionDuplicateValuesError(ApplyBijectionError[_T], Generic[_T, _U]):
129
- values: list[_U]
114
+ class _ApplyBijectionDuplicateValuesError[T, U](ApplyBijectionError[T]):
115
+ values: list[U]
130
116
 
131
117
  @override
132
118
  def __str__(self) -> str:
@@ -136,7 +122,7 @@ class _ApplyBijectionDuplicateValuesError(ApplyBijectionError[_T], Generic[_T, _
136
122
  ##
137
123
 
138
124
 
139
- def apply_to_tuple(func: Callable[..., _T], args: tuple[Any, ...], /) -> _T:
125
+ def apply_to_tuple[T](func: Callable[..., T], args: tuple[Any, ...], /) -> T:
140
126
  """Apply a function to a tuple of args."""
141
127
  return apply_to_varargs(func, *args)
142
128
 
@@ -144,7 +130,7 @@ def apply_to_tuple(func: Callable[..., _T], args: tuple[Any, ...], /) -> _T:
144
130
  ##
145
131
 
146
132
 
147
- def apply_to_varargs(func: Callable[..., _T], *args: Any) -> _T:
133
+ def apply_to_varargs[T](func: Callable[..., T], *args: Any) -> T:
148
134
  """Apply a function to a variable number of arguments."""
149
135
  return func(*args)
150
136
 
@@ -153,17 +139,17 @@ def apply_to_varargs(func: Callable[..., _T], *args: Any) -> _T:
153
139
 
154
140
 
155
141
  @overload
156
- def chain_mappings(
157
- *mappings: Mapping[_K, _V], list: Literal[True]
158
- ) -> Mapping[_K, Sequence[_V]]: ...
142
+ def chain_mappings[K, V](
143
+ *mappings: Mapping[K, V], list: Literal[True]
144
+ ) -> Mapping[K, Sequence[V]]: ...
159
145
  @overload
160
- def chain_mappings(
161
- *mappings: Mapping[_K, _V], list: bool = False
162
- ) -> Mapping[_K, Iterable[_V]]: ...
163
- def chain_mappings(
164
- *mappings: Mapping[_K, _V],
146
+ def chain_mappings[K, V](
147
+ *mappings: Mapping[K, V], list: bool = False
148
+ ) -> Mapping[K, Iterable[V]]: ...
149
+ def chain_mappings[K, V](
150
+ *mappings: Mapping[K, V],
165
151
  list: bool = False, # noqa: A002
166
- ) -> Mapping[_K, Iterable[_V]]:
152
+ ) -> Mapping[K, Iterable[V]]:
167
153
  """Chain the values of a set of mappings."""
168
154
  try:
169
155
  first, *rest = mappings
@@ -176,9 +162,9 @@ def chain_mappings(
176
162
  return reduced
177
163
 
178
164
 
179
- def _chain_mappings_one(
180
- acc: Mapping[_K, Iterable[_V]], el: Mapping[_K, _V], /
181
- ) -> Mapping[_K, Iterable[_V]]:
165
+ def _chain_mappings_one[K, V](
166
+ acc: Mapping[K, Iterable[V]], el: Mapping[K, V], /
167
+ ) -> Mapping[K, Iterable[V]]:
182
168
  """Chain the values of a set of mappings."""
183
169
  out = dict(acc)
184
170
  for key, value in el.items():
@@ -189,7 +175,7 @@ def _chain_mappings_one(
189
175
  ##
190
176
 
191
177
 
192
- def chain_maybe_iterables(*maybe_iterables: MaybeIterable[_T]) -> Iterable[_T]:
178
+ def chain_maybe_iterables[T](*maybe_iterables: MaybeIterable[T]) -> Iterable[T]:
193
179
  """Chain a set of maybe iterables."""
194
180
  iterables = map(always_iterable, maybe_iterables)
195
181
  return chain.from_iterable(iterables)
@@ -198,7 +184,7 @@ def chain_maybe_iterables(*maybe_iterables: MaybeIterable[_T]) -> Iterable[_T]:
198
184
  ##
199
185
 
200
186
 
201
- def chain_nullable(*maybe_iterables: Iterable[_T | None] | None) -> Iterable[_T]:
187
+ def chain_nullable[T](*maybe_iterables: Iterable[T | None] | None) -> Iterable[T]:
202
188
  """Chain a set of values; ignoring nulls."""
203
189
  iterables = (mi for mi in maybe_iterables if mi is not None)
204
190
  values = ((i for i in it if i is not None) for it in iterables)
@@ -217,7 +203,7 @@ def check_bijection(mapping: Mapping[Any, Hashable], /) -> None:
217
203
 
218
204
 
219
205
  @dataclass(kw_only=True, slots=True)
220
- class CheckBijectionError(Exception, Generic[THashable]):
206
+ class CheckBijectionError[THashable](Exception):
221
207
  mapping: Mapping[Any, THashable]
222
208
  counts: Mapping[THashable, int]
223
209
 
@@ -237,7 +223,7 @@ def check_duplicates(iterable: Iterable[Hashable], /) -> None:
237
223
 
238
224
 
239
225
  @dataclass(kw_only=True, slots=True)
240
- class CheckDuplicatesError(Exception, Generic[THashable]):
226
+ class CheckDuplicatesError[THashable](Exception):
241
227
  iterable: Iterable[THashable]
242
228
  counts: Mapping[THashable, int]
243
229
 
@@ -280,10 +266,10 @@ type _CheckIterablesEqualState = Literal["left_longer", "right_longer"]
280
266
 
281
267
 
282
268
  @dataclass(kw_only=True, slots=True)
283
- class CheckIterablesEqualError(Exception, Generic[_T]):
284
- left: list[_T]
285
- right: list[_T]
286
- errors: list[tuple[int, _T, _T]]
269
+ class CheckIterablesEqualError[T](Exception):
270
+ left: list[T]
271
+ right: list[T]
272
+ errors: list[tuple[int, T, T]]
287
273
  state: _CheckIterablesEqualState | None
288
274
 
289
275
  @override
@@ -434,12 +420,12 @@ def check_mappings_equal(left: Mapping[Any, Any], right: Mapping[Any, Any], /) -
434
420
 
435
421
 
436
422
  @dataclass(kw_only=True, slots=True)
437
- class CheckMappingsEqualError(Exception, Generic[_K, _V]):
438
- left: Mapping[_K, _V]
439
- right: Mapping[_K, _V]
440
- left_extra: AbstractSet[_K]
441
- right_extra: AbstractSet[_K]
442
- errors: list[tuple[_K, _V, _V]]
423
+ class CheckMappingsEqualError[K, V](Exception):
424
+ left: Mapping[K, V]
425
+ right: Mapping[K, V]
426
+ left_extra: AbstractSet[K]
427
+ right_extra: AbstractSet[K]
428
+ errors: list[tuple[K, V, V]]
443
429
 
444
430
  @override
445
431
  def __str__(self) -> str:
@@ -484,11 +470,11 @@ def check_sets_equal(left: Iterable[Any], right: Iterable[Any], /) -> None:
484
470
 
485
471
 
486
472
  @dataclass(kw_only=True, slots=True)
487
- class CheckSetsEqualError(Exception, Generic[_T]):
488
- left: AbstractSet[_T]
489
- right: AbstractSet[_T]
490
- left_extra: AbstractSet[_T]
491
- right_extra: AbstractSet[_T]
473
+ class CheckSetsEqualError[T](Exception):
474
+ left: AbstractSet[T]
475
+ right: AbstractSet[T]
476
+ left_extra: AbstractSet[T]
477
+ right_extra: AbstractSet[T]
492
478
 
493
479
  @override
494
480
  def __str__(self) -> str:
@@ -531,11 +517,11 @@ def check_submapping(left: Mapping[Any, Any], right: Mapping[Any, Any], /) -> No
531
517
 
532
518
 
533
519
  @dataclass(kw_only=True, slots=True)
534
- class CheckSubMappingError(Exception, Generic[_K, _V]):
535
- left: Mapping[_K, _V]
536
- right: Mapping[_K, _V]
537
- extra: AbstractSet[_K]
538
- errors: list[tuple[_K, _V, _V]]
520
+ class CheckSubMappingError[K, V](Exception):
521
+ left: Mapping[K, V]
522
+ right: Mapping[K, V]
523
+ extra: AbstractSet[K]
524
+ errors: list[tuple[K, V, V]]
539
525
 
540
526
  @override
541
527
  def __str__(self) -> str:
@@ -570,10 +556,10 @@ def check_subset(left: Iterable[Any], right: Iterable[Any], /) -> None:
570
556
 
571
557
 
572
558
  @dataclass(kw_only=True, slots=True)
573
- class CheckSubSetError(Exception, Generic[_T]):
574
- left: AbstractSet[_T]
575
- right: AbstractSet[_T]
576
- extra: AbstractSet[_T]
559
+ class CheckSubSetError[T](Exception):
560
+ left: AbstractSet[T]
561
+ right: AbstractSet[T]
562
+ extra: AbstractSet[T]
577
563
 
578
564
  @override
579
565
  def __str__(self) -> str:
@@ -602,11 +588,11 @@ def check_supermapping(left: Mapping[Any, Any], right: Mapping[Any, Any], /) ->
602
588
 
603
589
 
604
590
  @dataclass(kw_only=True, slots=True)
605
- class CheckSuperMappingError(Exception, Generic[_K, _V]):
606
- left: Mapping[_K, _V]
607
- right: Mapping[_K, _V]
608
- extra: AbstractSet[_K]
609
- errors: list[tuple[_K, _V, _V]]
591
+ class CheckSuperMappingError[K, V](Exception):
592
+ left: Mapping[K, V]
593
+ right: Mapping[K, V]
594
+ extra: AbstractSet[K]
595
+ errors: list[tuple[K, V, V]]
610
596
 
611
597
  @override
612
598
  def __str__(self) -> str:
@@ -641,10 +627,10 @@ def check_superset(left: Iterable[Any], right: Iterable[Any], /) -> None:
641
627
 
642
628
 
643
629
  @dataclass(kw_only=True, slots=True)
644
- class CheckSuperSetError(Exception, Generic[_T]):
645
- left: AbstractSet[_T]
646
- right: AbstractSet[_T]
647
- extra: AbstractSet[_T]
630
+ class CheckSuperSetError[T](Exception):
631
+ left: AbstractSet[T]
632
+ right: AbstractSet[T]
633
+ extra: AbstractSet[T]
648
634
 
649
635
  @override
650
636
  def __str__(self) -> str:
@@ -693,7 +679,7 @@ class _CheckUniqueModuloCaseDuplicateLowerCaseStringsError(CheckUniqueModuloCase
693
679
  ##
694
680
 
695
681
 
696
- def cmp_nullable(x: TSupportsLT | None, y: TSupportsLT | None, /) -> Sign:
682
+ def cmp_nullable[T: SupportsLT](x: T | None, y: T | None, /) -> Sign:
697
683
  """Compare two nullable objects."""
698
684
  match x, y:
699
685
  case None, None:
@@ -711,7 +697,7 @@ def cmp_nullable(x: TSupportsLT | None, y: TSupportsLT | None, /) -> Sign:
711
697
  ##
712
698
 
713
699
 
714
- def chunked(iterable: Iterable[_T], n: int, /) -> Iterator[Sequence[_T]]:
700
+ def chunked[T](iterable: Iterable[T], n: int, /) -> Iterator[Sequence[T]]:
715
701
  """Break an iterable into lists of length n."""
716
702
  return iter(partial(take, n, iter(iterable)), [])
717
703
 
@@ -769,10 +755,10 @@ class EnsureIterableNotStrError(Exception):
769
755
  ##
770
756
 
771
757
 
772
- def expanding_window(iterable: Iterable[_T], /) -> islice[list[_T]]:
758
+ def expanding_window[T](iterable: Iterable[T], /) -> islice[list[T]]:
773
759
  """Yield an expanding window over an iterable."""
774
760
 
775
- def func(acc: Iterable[_T], el: _T, /) -> list[_T]:
761
+ def func(acc: Iterable[T], el: T, /) -> list[T]:
776
762
  return list(chain(acc, [el]))
777
763
 
778
764
  return islice(accumulate(iterable, func=func, initial=[]), 1, None)
@@ -782,31 +768,31 @@ def expanding_window(iterable: Iterable[_T], /) -> islice[list[_T]]:
782
768
 
783
769
 
784
770
  @overload
785
- def filter_include_and_exclude(
786
- iterable: Iterable[_T],
771
+ def filter_include_and_exclude[T, U](
772
+ iterable: Iterable[T],
787
773
  /,
788
774
  *,
789
- include: MaybeIterable[_U] | None = None,
790
- exclude: MaybeIterable[_U] | None = None,
791
- key: Callable[[_T], _U],
792
- ) -> Iterable[_T]: ...
775
+ include: MaybeIterable[U] | None = None,
776
+ exclude: MaybeIterable[U] | None = None,
777
+ key: Callable[[T], U],
778
+ ) -> Iterable[T]: ...
793
779
  @overload
794
- def filter_include_and_exclude(
795
- iterable: Iterable[_T],
780
+ def filter_include_and_exclude[T](
781
+ iterable: Iterable[T],
796
782
  /,
797
783
  *,
798
- include: MaybeIterable[_T] | None = None,
799
- exclude: MaybeIterable[_T] | None = None,
800
- key: Callable[[_T], Any] | None = None,
801
- ) -> Iterable[_T]: ...
802
- def filter_include_and_exclude(
803
- iterable: Iterable[_T],
784
+ include: MaybeIterable[T] | None = None,
785
+ exclude: MaybeIterable[T] | None = None,
786
+ key: Callable[[T], Any] | None = None,
787
+ ) -> Iterable[T]: ...
788
+ def filter_include_and_exclude[T, U](
789
+ iterable: Iterable[T],
804
790
  /,
805
791
  *,
806
- include: MaybeIterable[_U] | None = None,
807
- exclude: MaybeIterable[_U] | None = None,
808
- key: Callable[[_T], _U] | None = None,
809
- ) -> Iterable[_T]:
792
+ include: MaybeIterable[U] | None = None,
793
+ exclude: MaybeIterable[U] | None = None,
794
+ key: Callable[[T], U] | None = None,
795
+ ) -> Iterable[T]:
810
796
  """Filter an iterable based on an inclusion/exclusion pair."""
811
797
  include, exclude = resolve_include_and_exclude(include=include, exclude=exclude)
812
798
  if include is not None:
@@ -844,16 +830,16 @@ def ungroup_consecutive_integers(
844
830
 
845
831
 
846
832
  @overload
847
- def groupby_lists(
848
- iterable: Iterable[_T], /, *, key: None = None
849
- ) -> Iterator[tuple[_T, list[_T]]]: ...
833
+ def groupby_lists[T](
834
+ iterable: Iterable[T], /, *, key: None = None
835
+ ) -> Iterator[tuple[T, list[T]]]: ...
850
836
  @overload
851
- def groupby_lists(
852
- iterable: Iterable[_T], /, *, key: Callable[[_T], _U]
853
- ) -> Iterator[tuple[_U, list[_T]]]: ...
854
- def groupby_lists(
855
- iterable: Iterable[_T], /, *, key: Callable[[_T], _U] | None = None
856
- ) -> Iterator[tuple[_T, list[_T]]] | Iterator[tuple[_U, list[_T]]]:
837
+ def groupby_lists[T, U](
838
+ iterable: Iterable[T], /, *, key: Callable[[T], U]
839
+ ) -> Iterator[tuple[U, list[T]]]: ...
840
+ def groupby_lists[T, U](
841
+ iterable: Iterable[T], /, *, key: Callable[[T], U] | None = None
842
+ ) -> Iterator[tuple[T, list[T]]] | Iterator[tuple[U, list[T]]]:
857
843
  """Yield consecutive keys and groups (as lists)."""
858
844
  if key is None:
859
845
  for k, group in groupby(iterable):
@@ -866,7 +852,7 @@ def groupby_lists(
866
852
  ##
867
853
 
868
854
 
869
- def hashable_to_iterable(obj: THashable | None, /) -> tuple[THashable, ...] | None:
855
+ def hashable_to_iterable[T: Hashable](obj: T | None, /) -> tuple[T, ...] | None:
870
856
  """Lift a hashable singleton to an iterable of hashables."""
871
857
  return None if obj is None else (obj,)
872
858
 
@@ -902,9 +888,9 @@ def is_iterable_not_str(obj: Any, /) -> TypeGuard[Iterable[Any]]:
902
888
  ##
903
889
 
904
890
 
905
- def map_mapping(
906
- func: Callable[[_V], _W], mapping: Mapping[_K, _V], /
907
- ) -> Mapping[_K, _W]:
891
+ def map_mapping[K, V, W](
892
+ func: Callable[[V], W], mapping: Mapping[K, V], /
893
+ ) -> Mapping[K, W]:
908
894
  """Map a function over the values of a mapping."""
909
895
  return {k: func(v) for k, v in mapping.items()}
910
896
 
@@ -912,7 +898,7 @@ def map_mapping(
912
898
  ##
913
899
 
914
900
 
915
- def merge_mappings(*mappings: Mapping[_K, _V]) -> Mapping[_K, _V]:
901
+ def merge_mappings[K, V](*mappings: Mapping[K, V]) -> Mapping[K, V]:
916
902
  """Merge a set of mappings."""
917
903
  return reduce(or_, map(dict, mappings), {})
918
904
 
@@ -920,7 +906,7 @@ def merge_mappings(*mappings: Mapping[_K, _V]) -> Mapping[_K, _V]:
920
906
  ##
921
907
 
922
908
 
923
- def merge_sets(*iterables: Iterable[_T]) -> AbstractSet[_T]:
909
+ def merge_sets[T](*iterables: Iterable[T]) -> AbstractSet[T]:
924
910
  """Merge a set of sets."""
925
911
  return reduce(or_, map(set, iterables), set())
926
912
 
@@ -967,7 +953,7 @@ class MergeStrMappingsError(Exception):
967
953
  ##
968
954
 
969
955
 
970
- def one(*iterables: Iterable[_T]) -> _T:
956
+ def one[T](*iterables: Iterable[T]) -> T:
971
957
  """Return the unique value in a set of iterables."""
972
958
  it = iter(chain(*iterables))
973
959
  try:
@@ -982,21 +968,21 @@ def one(*iterables: Iterable[_T]) -> _T:
982
968
 
983
969
 
984
970
  @dataclass(kw_only=True, slots=True)
985
- class OneError(Exception, Generic[_T]):
986
- iterables: tuple[Iterable[_T], ...]
971
+ class OneError[T](Exception):
972
+ iterables: tuple[Iterable[T], ...]
987
973
 
988
974
 
989
975
  @dataclass(kw_only=True, slots=True)
990
- class OneEmptyError(OneError[_T]):
976
+ class OneEmptyError[T](OneError[T]):
991
977
  @override
992
978
  def __str__(self) -> str:
993
979
  return f"Iterable(s) {get_repr(self.iterables)} must not be empty"
994
980
 
995
981
 
996
982
  @dataclass(kw_only=True, slots=True)
997
- class OneNonUniqueError(OneError, Generic[_T]):
998
- first: _T
999
- second: _T
983
+ class OneNonUniqueError[T](OneError):
984
+ first: T
985
+ second: T
1000
986
 
1001
987
  @override
1002
988
  def __str__(self) -> str:
@@ -1006,7 +992,7 @@ class OneNonUniqueError(OneError, Generic[_T]):
1006
992
  ##
1007
993
 
1008
994
 
1009
- def one_maybe(*objs: MaybeIterable[_T]) -> _T:
995
+ def one_maybe[T](*objs: MaybeIterable[T]) -> T:
1010
996
  """Return the unique value in a set of values/iterables."""
1011
997
  try:
1012
998
  return one(chain_maybe_iterables(*objs))
@@ -1030,10 +1016,10 @@ class OneMaybeEmptyError(OneMaybeError):
1030
1016
 
1031
1017
 
1032
1018
  @dataclass(kw_only=True, slots=True)
1033
- class OneMaybeNonUniqueError(OneMaybeError, Generic[_T]):
1034
- objs: tuple[MaybeIterable[_T], ...]
1035
- first: _T
1036
- second: _T
1019
+ class OneMaybeNonUniqueError[T](OneMaybeError):
1020
+ objs: tuple[MaybeIterable[T], ...]
1021
+ first: T
1022
+ second: T
1037
1023
 
1038
1024
  @override
1039
1025
  def __str__(self) -> str:
@@ -1133,7 +1119,7 @@ class OneStrNonUniqueError(OneStrError):
1133
1119
  ##
1134
1120
 
1135
1121
 
1136
- def one_unique(*iterables: Iterable[THashable]) -> THashable:
1122
+ def one_unique[T: Hashable](*iterables: Iterable[T]) -> T:
1137
1123
  """Return the set-unique value in a set of iterables."""
1138
1124
  try:
1139
1125
  return one(set(chain(*iterables)))
@@ -1157,7 +1143,7 @@ class OneUniqueEmptyError(OneUniqueError):
1157
1143
 
1158
1144
 
1159
1145
  @dataclass(kw_only=True, slots=True)
1160
- class OneUniqueNonUniqueError(OneUniqueError, Generic[THashable]):
1146
+ class OneUniqueNonUniqueError[THashable](OneUniqueError):
1161
1147
  iterables: tuple[MaybeIterable[THashable], ...]
1162
1148
  first: THashable
1163
1149
  second: THashable
@@ -1170,7 +1156,7 @@ class OneUniqueNonUniqueError(OneUniqueError, Generic[THashable]):
1170
1156
  ##
1171
1157
 
1172
1158
 
1173
- def pairwise_tail(iterable: Iterable[_T], /) -> Iterator[tuple[_T, _T | Sentinel]]:
1159
+ def pairwise_tail[T](iterable: Iterable[T], /) -> Iterator[tuple[T, T | Sentinel]]:
1174
1160
  """Return pairwise elements, with the last paired with the sentinel."""
1175
1161
  return pairwise(chain(iterable, [sentinel]))
1176
1162
 
@@ -1178,11 +1164,11 @@ def pairwise_tail(iterable: Iterable[_T], /) -> Iterator[tuple[_T, _T | Sentinel
1178
1164
  ##
1179
1165
 
1180
1166
 
1181
- def product_dicts(mapping: Mapping[_K, Iterable[_V]], /) -> Iterator[Mapping[_K, _V]]:
1167
+ def product_dicts[K, V](mapping: Mapping[K, Iterable[V]], /) -> Iterator[Mapping[K, V]]:
1182
1168
  """Return the cartesian product of the values in a mapping, as mappings."""
1183
1169
  keys = list(mapping)
1184
1170
  for values in product(*mapping.values()):
1185
- yield cast("Mapping[_K, _V]", dict(zip(keys, values, strict=True)))
1171
+ yield cast("Mapping[K, V]", dict(zip(keys, values, strict=True)))
1186
1172
 
1187
1173
 
1188
1174
  ##
@@ -1239,41 +1225,39 @@ class _RangePartitionsNumError(RangePartitionsError):
1239
1225
 
1240
1226
 
1241
1227
  @overload
1242
- def reduce_mappings(
1243
- func: Callable[[_V, _V], _V], sequence: Iterable[Mapping[_K, _V]], /
1244
- ) -> Mapping[_K, _V]: ...
1228
+ def reduce_mappings[K, V](
1229
+ func: Callable[[V, V], V], sequence: Iterable[Mapping[K, V]], /
1230
+ ) -> Mapping[K, V]: ...
1245
1231
  @overload
1246
- def reduce_mappings(
1247
- func: Callable[[_W, _V], _W],
1248
- sequence: Iterable[Mapping[_K, _V]],
1232
+ def reduce_mappings[K, V, W](
1233
+ func: Callable[[W, V], W],
1234
+ sequence: Iterable[Mapping[K, V]],
1249
1235
  /,
1250
1236
  *,
1251
- initial: _W | Sentinel = sentinel,
1252
- ) -> Mapping[_K, _W]: ...
1253
- def reduce_mappings(
1254
- func: Callable[[_V, _V], _V] | Callable[[_W, _V], _W],
1255
- sequence: Iterable[Mapping[_K, _V]],
1237
+ initial: W | Sentinel = sentinel,
1238
+ ) -> Mapping[K, W]: ...
1239
+ def reduce_mappings[K, V, W](
1240
+ func: Callable[[V, V], V] | Callable[[W, V], W],
1241
+ sequence: Iterable[Mapping[K, V]],
1256
1242
  /,
1257
1243
  *,
1258
- initial: _W | Sentinel = sentinel,
1259
- ) -> Mapping[_K, _V | _W]:
1244
+ initial: W | Sentinel = sentinel,
1245
+ ) -> Mapping[K, V | W]:
1260
1246
  """Reduce a function over the values of a set of mappings."""
1261
1247
  chained = chain_mappings(*sequence)
1262
1248
  if isinstance(initial, Sentinel):
1263
- func2 = cast("Callable[[_V, _V], _V]", func)
1249
+ func2 = cast("Callable[[V, V], V]", func)
1264
1250
  return {k: reduce(func2, v) for k, v in chained.items()}
1265
- func2 = cast("Callable[[_W, _V], _W]", func)
1251
+ func2 = cast("Callable[[W, V], W]", func)
1266
1252
  return {k: reduce(func2, v, initial) for k, v in chained.items()}
1267
1253
 
1268
1254
 
1269
1255
  ##
1270
1256
 
1271
1257
 
1272
- def resolve_include_and_exclude(
1273
- *,
1274
- include: MaybeIterable[_T] | None = None,
1275
- exclude: MaybeIterable[_T] | None = None,
1276
- ) -> tuple[set[_T] | None, set[_T] | None]:
1258
+ def resolve_include_and_exclude[T](
1259
+ *, include: MaybeIterable[T] | None = None, exclude: MaybeIterable[T] | None = None
1260
+ ) -> tuple[set[T] | None, set[T] | None]:
1277
1261
  """Resolve an inclusion/exclusion pair."""
1278
1262
  include_use = include if include is None else set(always_iterable(include))
1279
1263
  exclude_use = exclude if exclude is None else set(always_iterable(exclude))
@@ -1287,9 +1271,9 @@ def resolve_include_and_exclude(
1287
1271
 
1288
1272
 
1289
1273
  @dataclass(kw_only=True, slots=True)
1290
- class ResolveIncludeAndExcludeError(Exception, Generic[_T]):
1291
- include: Iterable[_T]
1292
- exclude: Iterable[_T]
1274
+ class ResolveIncludeAndExcludeError[T](Exception):
1275
+ include: Iterable[T]
1276
+ exclude: Iterable[T]
1293
1277
 
1294
1278
  @override
1295
1279
  def __str__(self) -> str:
@@ -1302,7 +1286,7 @@ class ResolveIncludeAndExcludeError(Exception, Generic[_T]):
1302
1286
  ##
1303
1287
 
1304
1288
 
1305
- def sort_iterable(iterable: Iterable[_T], /) -> list[_T]:
1289
+ def sort_iterable[T](iterable: Iterable[T], /) -> list[T]:
1306
1290
  """Sort an iterable across types."""
1307
1291
  return sorted(iterable, key=cmp_to_key(_sort_iterable_cmp))
1308
1292
 
@@ -1385,7 +1369,9 @@ def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
1385
1369
  ##
1386
1370
 
1387
1371
 
1388
- def sum_mappings(*mappings: Mapping[_K, TSupportsAdd]) -> Mapping[_K, TSupportsAdd]:
1372
+ def sum_mappings[K: Hashable, V: SupportsAdd](
1373
+ *mappings: Mapping[K, V],
1374
+ ) -> Mapping[K, V]:
1389
1375
  """Sum the values of a set of mappings."""
1390
1376
  return reduce_mappings(add, mappings, initial=0)
1391
1377
 
@@ -1393,7 +1379,7 @@ def sum_mappings(*mappings: Mapping[_K, TSupportsAdd]) -> Mapping[_K, TSupportsA
1393
1379
  ##
1394
1380
 
1395
1381
 
1396
- def take(n: int, iterable: Iterable[_T], /) -> Sequence[_T]:
1382
+ def take[T](n: int, iterable: Iterable[T], /) -> Sequence[T]:
1397
1383
  """Return first n items of the iterable as a list."""
1398
1384
  return list(islice(iterable, n))
1399
1385
 
@@ -1402,23 +1388,23 @@ def take(n: int, iterable: Iterable[_T], /) -> Sequence[_T]:
1402
1388
 
1403
1389
 
1404
1390
  @overload
1405
- def transpose(iterable: Iterable[tuple[_T1]], /) -> tuple[list[_T1]]: ...
1391
+ def transpose[T1](iterable: Iterable[tuple[T1]], /) -> tuple[list[T1]]: ...
1406
1392
  @overload
1407
- def transpose(
1408
- iterable: Iterable[tuple[_T1, _T2]], /
1409
- ) -> tuple[list[_T1], list[_T2]]: ...
1393
+ def transpose[T1, T2](
1394
+ iterable: Iterable[tuple[T1, T2]], /
1395
+ ) -> tuple[list[T1], list[T2]]: ...
1410
1396
  @overload
1411
- def transpose(
1412
- iterable: Iterable[tuple[_T1, _T2, _T3]], /
1413
- ) -> tuple[list[_T1], list[_T2], list[_T3]]: ...
1397
+ def transpose[T1, T2, T3](
1398
+ iterable: Iterable[tuple[T1, T2, T3]], /
1399
+ ) -> tuple[list[T1], list[T2], list[T3]]: ...
1414
1400
  @overload
1415
- def transpose(
1416
- iterable: Iterable[tuple[_T1, _T2, _T3, _T4]], /
1417
- ) -> tuple[list[_T1], list[_T2], list[_T3], list[_T4]]: ...
1401
+ def transpose[T1, T2, T3, T4](
1402
+ iterable: Iterable[tuple[T1, T2, T3, T4]], /
1403
+ ) -> tuple[list[T1], list[T2], list[T3], list[T4]]: ...
1418
1404
  @overload
1419
- def transpose(
1420
- iterable: Iterable[tuple[_T1, _T2, _T3, _T4, _T5]], /
1421
- ) -> tuple[list[_T1], list[_T2], list[_T3], list[_T4], list[_T5]]: ...
1405
+ def transpose[T1, T2, T3, T4, T5](
1406
+ iterable: Iterable[tuple[T1, T2, T3, T4, T5]], /
1407
+ ) -> tuple[list[T1], list[T2], list[T3], list[T4], list[T5]]: ...
1422
1408
  def transpose(iterable: Iterable[tuple[Any]]) -> tuple[list[Any], ...]: # pyright: ignore[reportInconsistentOverload]
1423
1409
  """Typed verison of `transpose`."""
1424
1410
  return tuple(map(list, zip(*iterable, strict=True)))
@@ -1427,9 +1413,9 @@ def transpose(iterable: Iterable[tuple[Any]]) -> tuple[list[Any], ...]: # pyrig
1427
1413
  ##
1428
1414
 
1429
1415
 
1430
- def unique_everseen(
1431
- iterable: Iterable[_T], /, *, key: Callable[[_T], Any] | None = None
1432
- ) -> Iterator[_T]:
1416
+ def unique_everseen[T](
1417
+ iterable: Iterable[T], /, *, key: Callable[[T], Any] | None = None
1418
+ ) -> Iterator[T]:
1433
1419
  """Yield unique elements, preserving order."""
1434
1420
  seenset = set()
1435
1421
  seenset_add = seenset.add