builtins.slice: more precise __new__ overloads and defaults for StopT and StepT. (#13008)

This commit is contained in:
Randolf Scholz
2025-02-28 13:33:07 +01:00
committed by GitHub
parent cadaaadfcb
commit be17dc0ac4
2 changed files with 281 additions and 14 deletions
@@ -0,0 +1,251 @@
"""
Assuming X, Y and Z are types other than None, the following rules apply to the slice type:
- The type hint `slice` should be compatible with all slices, including:
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)`. (⟿ `slice[?, ?, ?]`)
- The type hint `slice[T]` should be compatible with:
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)` (⟿ `slice[?, ?, ?]`)
- `slice(t)`, `slice(None, t)` and `slice(None, t, None)`. (⟿ `slice[?, T, ?]`)
- `slice(t, None)` and `slice(t, None, None)`. (⟿ `slice[T, ?, ?]`)
- `slice(t, t)` and `slice(t, t, None)`. (⟿ `slice[T, T, ?]`)
- The type hint `slice[X, Y]` should be compatible with:
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)` (⟿ `slice[?, ?, ?]`)
- `slice(y)`, `slice(None, y)` and `slice(None, y, None)`. (⟿ `slice[?, Y, ?]`)
- `slice(x, None)` and `slice(x, None, None)` (⟿ `slice[X, ?, ?]`)
- `slice(x, y)` and `slice(x, y, None)`. (⟿ `slice[X, Y, ?]`)
- The type hint `slice[X, Y, Z]` should be compatible with:
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)`. (⟿ `slice[?, ?, ?]`)
- `slice(y)`, `slice(None, y)` and `slice(None, y, None)`. (⟿ `slice[?, Y, ?]`)
- `slice(x, None)` and `slice(x, None, None)` (⟿ `slice[X, ?, ?]`)
- `slice(x, y)` and `slice(x, y, None)`. (⟿ `slice[X, Y, ?]`)
- `slice(None, None, z)` (⟿ `slice[?, ?, Z]`)
- `slice(None, y, z)` (⟿ `slice[?, Y, Z]`)
- `slice(x, None, z)` (⟿ `slice[X, ?, Z]`)
- `slice(x, y, z)` (⟿ `slice[X, Y, Z]`)
Consistency criterion: Assuming now X, Y, Z can potentially be None, the following rules apply:
- `slice(x)` must be compatible with `slice[None, X, None]`, even if X is None.
- `slice(x, y)` must be compatible with `slice[X,Y,None]`, even if X is None or Y is None.
- `slice(x, y, z)` must be compatible with `slice[X, Y, Z]`, even if X, Y, or Z are `None`.
"""
from __future__ import annotations
from datetime import date, datetime as DT, timedelta as TD
from typing import Any, SupportsIndex, cast
from typing_extensions import assert_type
# region Tests for slice constructor overloads -----------------------------------------
assert_type(slice(None), "slice[Any, Any, Any]")
assert_type(slice(1234), "slice[Any, int, Any]")
assert_type(slice(None, None), "slice[Any, Any, Any]")
assert_type(slice(None, 5678), "slice[Any, int, Any]")
assert_type(slice(1234, None), "slice[int, Any, Any]")
assert_type(slice(1234, 5678), "slice[int, int, Any]")
assert_type(slice(None, None, None), "slice[Any, Any, Any]")
assert_type(slice(None, 5678, None), "slice[Any, int, Any]")
assert_type(slice(1234, None, None), "slice[int, Any, Any]")
assert_type(slice(1234, 5678, None), "slice[int, int, Any]")
assert_type(slice(1234, 5678, 9012), "slice[int, int, int]")
# endregion Tests for slice constructor overloads --------------------------------------
# region Test parameter defaults for slice constructor ---------------------------------
# Note: need to cast, because pyright specializes regardless of type annotations
slc1 = cast("slice[SupportsIndex | None]", slice(1))
slc2 = cast("slice[int | None, int | None]", slice(1, 2))
fake_key_val = cast("slice[str, int]", slice("1", 2))
assert_type(slc1, "slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None]")
assert_type(slc2, "slice[int | None, int | None, int | None]")
assert_type(fake_key_val, "slice[str, int, str | int]")
# endregion Test parameter defaults for slice constructor ------------------------------
# region Tests for slice properties ----------------------------------------------------
# Note: if an argument is not None, we should get precisely the same type back
assert_type(slice(1234).stop, int)
assert_type(slice(1234, None).start, int)
assert_type(slice(None, 5678).stop, int)
assert_type(slice(1234, None, None).start, int)
assert_type(slice(None, 5678, None).stop, int)
assert_type(slice(None, None, 9012).step, int)
# endregion Tests for slice properties -------------------------------------------------
# region Test for slice assignments ----------------------------------------------------
# exhaustively test all possible assignments: miss (X), None (N), int (I), and str (S)
rXNX: slice = slice(None)
rXIX: slice = slice(1234)
rXSX: slice = slice("70")
rNNX: slice = slice(None, None)
rINX: slice = slice(1234, None)
rSNX: slice = slice("70", None)
rNIX: slice = slice(None, 5678)
rIIX: slice = slice(1234, 5678)
rSIX: slice = slice("70", 9012)
rNSX: slice = slice(None, "71")
rISX: slice = slice(1234, "71")
rSSX: slice = slice("70", "71")
rNNN: slice = slice(None, None, None)
rINN: slice = slice(1234, None, None)
rSNN: slice = slice("70", None, None)
rNIN: slice = slice(None, 5678, None)
rIIN: slice = slice(1234, 5678, None)
rSIN: slice = slice("70", 5678, None)
rNSN: slice = slice(None, "71", None)
rISN: slice = slice(1234, "71", None)
rSSN: slice = slice("70", "71", None)
rNNI: slice = slice(None, None, 9012)
rINI: slice = slice(1234, None, 9012)
rSNI: slice = slice("70", None, 9012)
rNII: slice = slice(None, 5678, 9012)
rIII: slice = slice(1234, 5678, 9012)
rSII: slice = slice("70", 5678, 9012)
rNSI: slice = slice(None, "71", 9012)
rISI: slice = slice(1234, "71", 9012)
rSSI: slice = slice("70", "71", 9012)
rNNS: slice = slice(None, None, "1d")
rINS: slice = slice(1234, None, "1d")
rSNS: slice = slice("70", None, "1d")
rNIS: slice = slice(None, 5678, "1d")
rIIS: slice = slice(1234, 5678, "1d")
rSIS: slice = slice("70", 5678, "1d")
rNSS: slice = slice(None, "71", "1d")
rISS: slice = slice(1234, "71", "1d")
rSSS: slice = slice("70", "71", "1d")
# endregion Test for slice assignments -------------------------------------------------
# region Tests for slice[T] assignments ------------------------------------------------
sXNX: "slice[int]" = slice(None)
sXIX: "slice[int]" = slice(1234)
sNNX: "slice[int]" = slice(None, None)
sNIX: "slice[int]" = slice(None, 5678)
sINX: "slice[int]" = slice(1234, None)
sIIX: "slice[int]" = slice(1234, 5678)
sNNN: "slice[int]" = slice(None, None, None)
sNIN: "slice[int]" = slice(None, 5678, None)
sNNS: "slice[int]" = slice(None, None, 9012)
sINN: "slice[int]" = slice(1234, None, None)
sINS: "slice[int]" = slice(1234, None, 9012)
sIIN: "slice[int]" = slice(1234, 5678, None)
sIIS: "slice[int]" = slice(1234, 5678, 9012)
# endregion Tests for slice[T] assignments ---------------------------------------------
# region Tests for slice[X, Y] assignments ---------------------------------------------
# Note: start=int is illegal and hence we add an explicit "type: ignore" comment.
tXNX: "slice[None, int]" = slice(None) # since slice(None) is slice[Any, Any, Any]
tXIX: "slice[None, int]" = slice(1234)
tNNX: "slice[None, int]" = slice(None, None)
tNIX: "slice[None, int]" = slice(None, 5678)
tINX: "slice[None, int]" = slice(1234, None) # type: ignore
tIIX: "slice[None, int]" = slice(1234, 5678) # type: ignore
tNNN: "slice[None, int]" = slice(None, None, None)
tNIN: "slice[None, int]" = slice(None, 5678, None)
tINN: "slice[None, int]" = slice(1234, None, None) # type: ignore
tIIN: "slice[None, int]" = slice(1234, 5678, None) # type: ignore
tNNS: "slice[None, int]" = slice(None, None, 9012)
tINS: "slice[None, int]" = slice(None, 5678, 9012)
tNIS: "slice[None, int]" = slice(1234, None, 9012) # type: ignore
tIIS: "slice[None, int]" = slice(1234, 5678, 9012) # type: ignore
# endregion Tests for slice[X, Y] assignments ------------------------------------------
# region Tests for slice[X, Y, Z] assignments ------------------------------------------
uXNX: "slice[int, int, int]" = slice(None)
uXIX: "slice[int, int, int]" = slice(1234)
uNNX: "slice[int, int, int]" = slice(None, None)
uNIX: "slice[int, int, int]" = slice(None, 5678)
uINX: "slice[int, int, int]" = slice(1234, None)
uIIX: "slice[int, int, int]" = slice(1234, 5678)
uNNN: "slice[int, int, int]" = slice(None, None, None)
uNNI: "slice[int, int, int]" = slice(None, None, 9012)
uNIN: "slice[int, int, int]" = slice(None, 5678, None)
uNII: "slice[int, int, int]" = slice(None, 5678, 9012)
uINN: "slice[int, int, int]" = slice(1234, None, None)
uINI: "slice[int, int, int]" = slice(1234, None, 9012)
uIIN: "slice[int, int, int]" = slice(1234, 5678, None)
uIII: "slice[int, int, int]" = slice(1234, 5678, 9012)
# endregion Tests for slice[X, Y, Z] assignments ---------------------------------------
# region Test for slice consistency criterion ------------------------------------------
year = date(2021, 1, 1)
vXNX: "slice[None, None, None]" = slice(None)
vXIX: "slice[None, date, None]" = slice(year)
vNNX: "slice[None, None, None]" = slice(None, None)
vNIX: "slice[None, date, None]" = slice(None, year)
vINX: "slice[date, None, None]" = slice(year, None)
vIIX: "slice[date, date, None]" = slice(year, year)
vNNN: "slice[None, None, None]" = slice(None, None, None)
vNIN: "slice[None, date, None]" = slice(None, year, None)
vINN: "slice[date, None, None]" = slice(year, None, None)
vIIN: "slice[date, date, None]" = slice(year, year, None)
vNNI: "slice[None, None, str]" = slice(None, None, "1d")
vNII: "slice[None, date, str]" = slice(None, year, "1d")
vINI: "slice[date, None, str]" = slice(year, None, "1d")
vIII: "slice[date, date, str]" = slice(year, year, "1d")
# endregion Test for slice consistency criterion ---------------------------------------
# region Integration tests for slices with datetimes -----------------------------------
class TimeSeries: # similar to pandas.Series with datetime index
def __getitem__(self, key: "slice[DT | str | None, DT | str | None]") -> Any:
"""Subsample the time series at the given dates."""
...
class TimeSeriesInterpolator: # similar to pandas.Series with datetime index
def __getitem__(self, key: "slice[DT, DT, TD | None]") -> Any:
"""Subsample the time series at the given dates."""
...
# tests slices as an argument
start = DT(1970, 1, 1)
stop = DT(1971, 1, 10)
step = TD(days=1)
# see: https://pandas.pydata.org/docs/user_guide/timeseries.html#partial-string-indexing
# FIXME: https://github.com/python/mypy/issues/2410 (use literal slices)
series = TimeSeries()
_ = series[slice(None, "1970-01-10")]
_ = series[slice("1970-01-01", None)]
_ = series[slice("1970-01-01", "1971-01-10")]
_ = series[slice(None, stop)]
_ = series[slice(start, None)]
_ = series[slice(start, stop)]
_ = series[slice(None)]
model = TimeSeriesInterpolator()
_ = model[slice(start, stop)]
_ = model[slice(start, stop, step)]
_ = model[slice(start, stop, None)]
# test slices as a return type
def foo(flag: bool, value: DT) -> "slice[DT, None] | slice[None, DT]":
if flag:
return slice(value, None) # slice[DT, DT|Any, Any] incompatible
else:
return slice(None, value) # slice[DT|Any, DT, Any] incompatible
# endregion Integration tests for slices with datetimes --------------------------------
+30 -14
View File
@@ -10,7 +10,6 @@ from _typeshed import (
ConvertibleToFloat,
ConvertibleToInt,
FileDescriptorOrPath,
MaybeNone,
OpenBinaryMode,
OpenBinaryModeReading,
OpenBinaryModeUpdating,
@@ -95,9 +94,14 @@ _SupportsAnextT = TypeVar("_SupportsAnextT", bound=SupportsAnext[Any], covariant
_AwaitableT = TypeVar("_AwaitableT", bound=Awaitable[Any])
_AwaitableT_co = TypeVar("_AwaitableT_co", bound=Awaitable[Any], covariant=True)
_P = ParamSpec("_P")
_StartT = TypeVar("_StartT", covariant=True, default=Any)
_StopT = TypeVar("_StopT", covariant=True, default=Any)
_StepT = TypeVar("_StepT", covariant=True, default=Any)
# Type variables for slice
_StartT_co = TypeVar("_StartT_co", covariant=True, default=Any) # slice -> slice[Any, Any, Any]
_StopT_co = TypeVar("_StopT_co", covariant=True, default=_StartT_co) # slice[A] -> slice[A, A, A]
# NOTE: step could differ from start and stop, (e.g. datetime/timedelta)l
# the default (start|stop) is chosen to cater to the most common case of int/index slices.
# FIXME: https://github.com/python/typing/issues/213 (replace step=start|stop with step=start&stop)
_StepT_co = TypeVar("_StepT_co", covariant=True, default=_StartT_co | _StopT_co) # slice[A,B] -> slice[A, B, A|B]
class object:
__doc__: str | None
@@ -940,23 +944,35 @@ class bool(int):
def __invert__(self) -> int: ...
@final
class slice(Generic[_StartT, _StopT, _StepT]):
class slice(Generic[_StartT_co, _StopT_co, _StepT_co]):
@property
def start(self) -> _StartT: ...
def start(self) -> _StartT_co: ...
@property
def step(self) -> _StepT: ...
def step(self) -> _StepT_co: ...
@property
def stop(self) -> _StopT: ...
def stop(self) -> _StopT_co: ...
# Note: __new__ overloads map `None` to `Any`, since users expect slice(x, None)
# to be compatible with slice(None, x).
# generic slice --------------------------------------------------------------------
@overload
def __new__(cls, stop: int | None, /) -> slice[int | MaybeNone, int | MaybeNone, int | MaybeNone]: ...
@overload
def __new__(
cls, start: int | None, stop: int | None, step: int | None = None, /
) -> slice[int | MaybeNone, int | MaybeNone, int | MaybeNone]: ...
def __new__(cls, start: None, stop: None = None, step: None = None, /) -> slice[Any, Any, Any]: ...
# unary overloads ------------------------------------------------------------------
@overload
def __new__(cls, stop: _T2, /) -> slice[Any, _T2, Any]: ...
# binary overloads -----------------------------------------------------------------
@overload
def __new__(cls, start: _T1, stop: _T2, /) -> slice[_T1, _T2, Any]: ...
def __new__(cls, start: _T1, stop: None, step: None = None, /) -> slice[_T1, Any, Any]: ...
@overload
def __new__(cls, start: None, stop: _T2, step: None = None, /) -> slice[Any, _T2, Any]: ...
@overload
def __new__(cls, start: _T1, stop: _T2, step: None = None, /) -> slice[_T1, _T2, Any]: ...
# ternary overloads ----------------------------------------------------------------
@overload
def __new__(cls, start: None, stop: None, step: _T3, /) -> slice[Any, Any, _T3]: ...
@overload
def __new__(cls, start: _T1, stop: None, step: _T3, /) -> slice[_T1, Any, _T3]: ...
@overload
def __new__(cls, start: None, stop: _T2, step: _T3, /) -> slice[Any, _T2, _T3]: ...
@overload
def __new__(cls, start: _T1, stop: _T2, step: _T3, /) -> slice[_T1, _T2, _T3]: ...
def __eq__(self, value: object, /) -> bool: ...