diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 02e866204..c04587ea9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: hooks: - id: isort - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.3 + rev: v0.12.10 hooks: - id: ruff-check args: [ diff --git a/docs/philosophy.md b/docs/philosophy.md index 853c516e1..31b8eac60 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -61,6 +61,43 @@ The type `TimestampSeries` is the result of creating a series from `pd.to_dateti the type `TimedeltaSeries` is the result of subtracting two `TimestampSeries` as well as the result of `pd.to_timedelta()`. +### Generic Series have restricted arithmetic + +Consider the following Series from a DataFrame: + +```python +import pandas as pd +from typing_extensions import reveal_type +from typing import TYPE_CHECKING, cast + +if TYPE_CHECKING: + from pandas.core.series import TimestampSeries # noqa: F401 + + +frame = pd.DataFrame({"timestamp": [pd.Timestamp(2025, 8, 26)], "tag": ["one"], "value": [1.0]}) +values = frame["value"] +reveal_type(values) # type checker: Series[Any], runtime: Series +new_values = values + 2 + +timestamps = frame["timestamp"] +reveal_type(timestamps) # type checker: Series[Any], runtime: Series +reveal_type(timestamps - pd.Timestamp(2025, 7, 12)) # type checker: Unknown and error, runtime: Series +reveal_type(cast("TimestampSeries", timestamps) - pd.Timestamp(2025, 7, 12)) # type checker: TimedeltaSeries, runtime: Series + +tags = frame["tag"] +reveal_type("suffix" + tags) # type checker: Never, runtime: Series +``` + +Since they are taken from a DataFrame, all three of them, `values`, `timestamps` +and `tags`, are recognized by type checkers as `Series[Any]`. The code snippet +runs fine at runtime. In the stub for type checking, however, we restrict +generic Series to perform arithmetic operations only with numeric types, and +give `Series[Any]` for the results. For `Timedelta`, `Timestamp`, `str`, etc., +arithmetic is restricted to `Series[Any]` and the result is either undefined, +showing `Unknown` and errors, or `Never`. Users are encouraged to cast such +generic Series to ones with concrete types, so that type checkers can provide +meaningful results. + ### Interval is Generic A pandas `Interval` can be a time interval, an interval of integers, or an interval of diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi index f5bea7bfc..52f6a110c 100644 --- a/pandas-stubs/core/series.pyi +++ b/pandas-stubs/core/series.pyi @@ -186,7 +186,6 @@ from pandas._typing import ( np_ndarray_anyint, np_ndarray_bool, np_ndarray_complex, - np_ndarray_dt, np_ndarray_float, np_ndarray_str, np_ndarray_td, @@ -261,9 +260,20 @@ class _LocIndexerSeries(_LocIndexer, Generic[S1]): value: S1 | ArrayLike | Series[S1] | None, ) -> None: ... -_ListLike: TypeAlias = ( +_ListLike: TypeAlias = ArrayLike | dict[_str, np.ndarray] | SequenceNotStr[S1] +_ListLikeS1: TypeAlias = ( ArrayLike | dict[_str, np.ndarray] | Sequence[S1] | IndexOpsMixin[S1] ) +_NumListLike: TypeAlias = ( + ExtensionArray + | np_ndarray_bool + | np_ndarray_anyint + | np_ndarray_float + | np_ndarray_complex + | dict[_str, np.ndarray] + | Sequence[complex] + | IndexOpsMixin[complex] +) class Series(IndexOpsMixin[S1], NDFrame): # Define __index__ because mypy thinks Series follows protocol `SupportsIndex` https://github.com/pandas-dev/pandas-stubs/pull/1332#discussion_r2285648790 @@ -419,7 +429,9 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __new__( cls, - data: S1 | _ListLike[S1] | dict[HashableT1, S1] | KeysView[S1] | ValuesView[S1], + data: ( + S1 | _ListLikeS1[S1] | dict[HashableT1, S1] | KeysView[S1] | ValuesView[S1] + ), index: AxesData | None = ..., dtype: Dtype = ..., name: Hashable = ..., @@ -1619,7 +1631,9 @@ class Series(IndexOpsMixin[S1], NDFrame): # just failed to generate these so I couldn't match # them up. @overload - def __add__(self: Series[Never], other: Scalar | _ListLike | Series) -> Series: ... + def __add__(self: Series[Never], other: _str) -> Never: ... + @overload + def __add__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... @overload def __add__(self, other: Series[Never]) -> Series: ... @overload @@ -1697,7 +1711,15 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def add( self: Series[Never], - other: Scalar | _ListLike | Series, + other: _str, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Never: ... + @overload + def add( + self: Series[Never], + other: complex | _ListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -1840,7 +1862,11 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series[_str]: ... @overload # type: ignore[override] - def __radd__(self: Series[Never], other: Scalar | _ListLike) -> Series: ... + def __radd__(self: Series[Never], other: _str) -> Never: ... + @overload + def __radd__( + self: Series[Never], other: complex | _ListLike | Series + ) -> Series: ... @overload def __radd__( self: Series[bool], @@ -1912,7 +1938,23 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def radd( self: Series[Never], - other: Scalar | _ListLike | Series, + other: _str, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Never: ... + @overload + def radd( + self: Series[Never], + other: complex | _ListLike | Series, + level: Level | None = None, + fill_value: float | None = None, + axis: int = 0, + ) -> Series: ... + @overload + def radd( + self: Series[S1], + other: Series[Never], level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2051,7 +2093,9 @@ class Series(IndexOpsMixin[S1], NDFrame): self, other: S1 | _ListLike | Series[S1] | datetime | timedelta | date ) -> Series[_bool]: ... @overload - def __mul__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... + def __mul__( + self: Series[Never], other: complex | _NumListLike | Series + ) -> Series: ... @overload def __mul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload @@ -2246,7 +2290,7 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> TimedeltaSeries: ... @overload def __rmul__( - self: Series[Never], other: complex | _ListLike | Series + self: Series[Never], other: complex | _NumListLike | Series ) -> Series: ... @overload def __rmul__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @@ -2475,12 +2519,11 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def __rxor__(self, other: int | np_ndarray_anyint | Series[int]) -> Series[int]: ... @overload - def __sub__( - self: Series[Never], - other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, - ) -> TimedeltaSeries: ... + def __sub__(self: Series[Never], other: TimestampSeries) -> Never: ... @overload - def __sub__(self: Series[Never], other: complex | _ListLike | Series) -> Series: ... + def __sub__( + self: Series[Never], other: complex | _NumListLike | Series + ) -> Series: ... @overload def __sub__(self, other: Series[Never]) -> Series: ... # type: ignore[overload-overlap] @overload @@ -2571,15 +2614,15 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def sub( self: Series[Never], - other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, + other: TimestampSeries, level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> TimedeltaSeries: ... + ) -> Never: ... @overload def sub( self: Series[Never], - other: complex | _ListLike | Series, + other: complex | _NumListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2705,13 +2748,10 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> TimedeltaSeries: ... @overload - def __rsub__( # type: ignore[misc] - self: Series[Never], - other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, - ) -> TimedeltaSeries: ... + def __rsub__(self: Series[Never], other: TimestampSeries) -> Never: ... # type: ignore[misc] @overload def __rsub__( - self: Series[Never], other: complex | _ListLike | Series + self: Series[Never], other: complex | _NumListLike | Series ) -> Series: ... @overload def __rsub__(self, other: Series[Never]) -> Series: ... @@ -2781,15 +2821,15 @@ class Series(IndexOpsMixin[S1], NDFrame): @overload def rsub( self: Series[Never], - other: datetime | np.datetime64 | np_ndarray_dt | TimestampSeries, + other: TimestampSeries, level: Level | None = None, fill_value: float | None = None, axis: int = 0, - ) -> TimedeltaSeries: ... + ) -> Never: ... @overload def rsub( self: Series[Never], - other: complex | _ListLike | Series, + other: complex | _NumListLike | Series, level: Level | None = None, fill_value: float | None = None, axis: int = 0, @@ -2887,8 +2927,8 @@ class Series(IndexOpsMixin[S1], NDFrame): axis: int = 0, ) -> Series[complex]: ... @overload - def __truediv__( - self: Series[Never], other: complex | _ListLike | Series + def __truediv__( # type:ignore[overload-overlap] + self: Series[Never], other: complex | _NumListLike | Series ) -> Series: ... @overload def __truediv__(self, other: Series[Never]) -> Series: ... @@ -3083,8 +3123,8 @@ class Series(IndexOpsMixin[S1], NDFrame): ) -> Series: ... div = truediv @overload - def __rtruediv__( - self: Series[Never], other: complex | _ListLike | Series + def __rtruediv__( # type:ignore[overload-overlap] + self: Series[Never], other: complex | _NumListLike | Series ) -> Series: ... @overload def __rtruediv__(self, other: Series[Never]) -> Series: ... diff --git a/pyproject.toml b/pyproject.toml index 25e7d8d34..5772f1408 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,12 +35,12 @@ types-pytz = ">= 2022.1.1" numpy = ">= 1.23.5" [tool.poetry.group.dev.dependencies] -mypy = "1.17.0" +mypy = "1.17.1" pandas = "2.3.1" pyarrow = ">=10.0.1" pytest = ">=7.1.2" pyright = ">=1.1.404" -ty = "^0.0.1a8" +ty = "^0.0.1a9" pyrefly = "^0.21.0" poethepoet = ">=0.16.5" loguru = ">=0.6.0" diff --git a/tests/series/arithmetic/str/test_add.py b/tests/series/arithmetic/str/test_add.py index 2eaffd59a..a58fe663a 100644 --- a/tests/series/arithmetic/str/test_add.py +++ b/tests/series/arithmetic/str/test_add.py @@ -18,7 +18,7 @@ def test_add_py_scalar() -> None: - """Testpd.Series[str]+ Python native 'scalar's""" + """Test pd.Series[str] + Python native 'scalar's""" i = 4 r0 = "right" @@ -35,12 +35,12 @@ def test_add_py_scalar() -> None: check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) if TYPE_CHECKING_INVALID_USAGE: - left.radd(i) # type: ignore[call-overload] # pyright: ignore[reportArgumentType] + left.radd(i) # type: ignore[call-overload] # pyright: ignore[reportArgumentType, reportCallIssue] check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) def test_add_py_sequence() -> None: - """Testpd.Series[str]+ Python native sequence""" + """Test pd.Series[str] + Python native sequence""" i = [3, 5, 8] r0 = ["a", "bc", "def"] r1 = tuple(r0) @@ -61,13 +61,13 @@ def test_add_py_sequence() -> None: check(assert_type(left.add(r1), "pd.Series[str]"), pd.Series, str) if TYPE_CHECKING_INVALID_USAGE: - left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) check(assert_type(left.radd(r1), "pd.Series[str]"), pd.Series, str) def test_add_numpy_array() -> None: - """Testpd.Series[str]+ numpy array""" + """Test pd.Series[str] + numpy array""" i = np.array([3, 5, 8], np.int64) r0 = np.array(["a", "bc", "def"], np.str_) @@ -96,12 +96,12 @@ def test_add_numpy_array() -> None: check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) if TYPE_CHECKING_INVALID_USAGE: - left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType, reportCallIssue] check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) def test_add_pd_series() -> None: - """Testpd.Series[str]+ pandas series""" + """Test pd.Series[str] + pandas series""" i = pd.Series([3, 5, 8]) r0 = pd.Series(["a", "bc", "def"]) @@ -118,5 +118,5 @@ def test_add_pd_series() -> None: check(assert_type(left.add(r0), "pd.Series[str]"), pd.Series, str) if TYPE_CHECKING_INVALID_USAGE: - left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + left.radd(i) # type: ignore[arg-type] # pyright: ignore[reportArgumentType, reportCallIssue] check(assert_type(left.radd(r0), "pd.Series[str]"), pd.Series, str) diff --git a/tests/series/arithmetic/test_add.py b/tests/series/arithmetic/test_add.py index e4d68c94f..a739ddae3 100644 --- a/tests/series/arithmetic/test_add.py +++ b/tests/series/arithmetic/test_add.py @@ -1,126 +1,154 @@ import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd -from typing_extensions import assert_type +from typing_extensions import ( + Never, + assert_type, +) -from tests import check +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) -left = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +# left operands +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] +left_str = pd.DataFrame({"a": ["1", "2", "3_"]})["a"] -def test_add_py_scalar() -> None: - """Test pd.Series[Any] + Python native scalars""" +def test_add_i_py_scalar() -> None: + """Test pd.Series[Any] (int) + Python native scalars""" b, i, f, c = True, 1, 1.0, 1j - check(assert_type(left + b, pd.Series), pd.Series) - check(assert_type(left + i, pd.Series), pd.Series) - check(assert_type(left + f, pd.Series), pd.Series) - check(assert_type(left + c, pd.Series), pd.Series) + check(assert_type(left_i + b, pd.Series), pd.Series) + check(assert_type(left_i + i, pd.Series), pd.Series) + check(assert_type(left_i + f, pd.Series), pd.Series) + check(assert_type(left_i + c, pd.Series), pd.Series) - check(assert_type(b + left, pd.Series), pd.Series) - check(assert_type(i + left, pd.Series), pd.Series) - check(assert_type(f + left, pd.Series), pd.Series) - check(assert_type(c + left, pd.Series), pd.Series) + check(assert_type(b + left_i, pd.Series), pd.Series) + check(assert_type(i + left_i, pd.Series), pd.Series) + check(assert_type(f + left_i, pd.Series), pd.Series) + check(assert_type(c + left_i, pd.Series), pd.Series) - check(assert_type(left.add(b), pd.Series), pd.Series) - check(assert_type(left.add(i), pd.Series), pd.Series) - check(assert_type(left.add(f), pd.Series), pd.Series) - check(assert_type(left.add(c), pd.Series), pd.Series) + check(assert_type(left_i.add(b), pd.Series), pd.Series) + check(assert_type(left_i.add(i), pd.Series), pd.Series) + check(assert_type(left_i.add(f), pd.Series), pd.Series) + check(assert_type(left_i.add(c), pd.Series), pd.Series) - check(assert_type(left.radd(b), pd.Series), pd.Series) - check(assert_type(left.radd(i), pd.Series), pd.Series) - check(assert_type(left.radd(f), pd.Series), pd.Series) - check(assert_type(left.radd(c), pd.Series), pd.Series) + check(assert_type(left_i.radd(b), pd.Series), pd.Series) + check(assert_type(left_i.radd(i), pd.Series), pd.Series) + check(assert_type(left_i.radd(f), pd.Series), pd.Series) + check(assert_type(left_i.radd(c), pd.Series), pd.Series) -def test_add_py_sequence() -> None: - """Test pd.Series[Any] + Python native sequence""" +def test_add_i_py_sequence() -> None: + """Test pd.Series[Any] (int) + Python native sequence""" b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] - check(assert_type(left + b, pd.Series), pd.Series) - check(assert_type(left + i, pd.Series), pd.Series) - check(assert_type(left + f, pd.Series), pd.Series) - check(assert_type(left + c, pd.Series), pd.Series) + check(assert_type(left_i + b, pd.Series), pd.Series) + check(assert_type(left_i + i, pd.Series), pd.Series) + check(assert_type(left_i + f, pd.Series), pd.Series) + check(assert_type(left_i + c, pd.Series), pd.Series) - check(assert_type(b + left, pd.Series), pd.Series) - check(assert_type(i + left, pd.Series), pd.Series) - check(assert_type(f + left, pd.Series), pd.Series) - check(assert_type(c + left, pd.Series), pd.Series) + check(assert_type(b + left_i, pd.Series), pd.Series) + check(assert_type(i + left_i, pd.Series), pd.Series) + check(assert_type(f + left_i, pd.Series), pd.Series) + check(assert_type(c + left_i, pd.Series), pd.Series) - check(assert_type(left.add(b), pd.Series), pd.Series) - check(assert_type(left.add(i), pd.Series), pd.Series) - check(assert_type(left.add(f), pd.Series), pd.Series) - check(assert_type(left.add(c), pd.Series), pd.Series) + check(assert_type(left_i.add(b), pd.Series), pd.Series) + check(assert_type(left_i.add(i), pd.Series), pd.Series) + check(assert_type(left_i.add(f), pd.Series), pd.Series) + check(assert_type(left_i.add(c), pd.Series), pd.Series) - check(assert_type(left.radd(b), pd.Series), pd.Series) - check(assert_type(left.radd(i), pd.Series), pd.Series) - check(assert_type(left.radd(f), pd.Series), pd.Series) - check(assert_type(left.radd(c), pd.Series), pd.Series) + check(assert_type(left_i.radd(b), pd.Series), pd.Series) + check(assert_type(left_i.radd(i), pd.Series), pd.Series) + check(assert_type(left_i.radd(f), pd.Series), pd.Series) + check(assert_type(left_i.radd(c), pd.Series), pd.Series) -def test_add_numpy_array() -> None: - """Test pd.Series[Any] + numpy array""" +def test_add_i_numpy_array() -> None: + """Test pd.Series[Any] (int) + numpy array""" b = np.array([True, False, True], np.bool_) i = np.array([2, 3, 5], np.int64) f = np.array([1.0, 2.0, 3.0], np.float64) c = np.array([1.1j, 2.2j, 4.1j], np.complex128) - check(assert_type(left + b, pd.Series), pd.Series) - check(assert_type(left + i, pd.Series), pd.Series) - check(assert_type(left + f, pd.Series), pd.Series) - check(assert_type(left + c, pd.Series), pd.Series) + check(assert_type(left_i + b, pd.Series), pd.Series) + check(assert_type(left_i + i, pd.Series), pd.Series) + check(assert_type(left_i + f, pd.Series), pd.Series) + check(assert_type(left_i + c, pd.Series), pd.Series) # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__radd__` cannot override. At runtime, they return # `Series`s. # `mypy` thinks the return types are `Any`, which is a bug. check( - assert_type(b + left, "npt.NDArray[np.bool_]"), pd.Series # type: ignore[assert-type] + assert_type(b + left_i, "npt.NDArray[np.bool_]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(i + left, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] + assert_type(i + left_i, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(f + left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(f + left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(c + left, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] + assert_type(c + left_i, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] ) - check(assert_type(left.add(b), pd.Series), pd.Series) - check(assert_type(left.add(i), pd.Series), pd.Series) - check(assert_type(left.add(f), pd.Series), pd.Series) - check(assert_type(left.add(c), pd.Series), pd.Series) + check(assert_type(left_i.add(b), pd.Series), pd.Series) + check(assert_type(left_i.add(i), pd.Series), pd.Series) + check(assert_type(left_i.add(f), pd.Series), pd.Series) + check(assert_type(left_i.add(c), pd.Series), pd.Series) - check(assert_type(left.radd(b), pd.Series), pd.Series) - check(assert_type(left.radd(i), pd.Series), pd.Series) - check(assert_type(left.radd(f), pd.Series), pd.Series) - check(assert_type(left.radd(c), pd.Series), pd.Series) + check(assert_type(left_i.radd(b), pd.Series), pd.Series) + check(assert_type(left_i.radd(i), pd.Series), pd.Series) + check(assert_type(left_i.radd(f), pd.Series), pd.Series) + check(assert_type(left_i.radd(c), pd.Series), pd.Series) -def test_add_pd_series() -> None: - """Test pd.Series[Any] + pandas series""" +def test_add_i_pd_series() -> None: + """Test pd.Series[Any] (int) + pandas series""" + a = pd.DataFrame({"a": [1, 2, 3]})["a"] b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) - check(assert_type(left + b, pd.Series), pd.Series) - check(assert_type(left + i, pd.Series), pd.Series) - check(assert_type(left + f, pd.Series), pd.Series) - check(assert_type(left + c, pd.Series), pd.Series) - - check(assert_type(b + left, pd.Series), pd.Series) - check(assert_type(i + left, pd.Series), pd.Series) - check(assert_type(f + left, pd.Series), pd.Series) - check(assert_type(c + left, pd.Series), pd.Series) - - check(assert_type(left.add(b), pd.Series), pd.Series) - check(assert_type(left.add(i), pd.Series), pd.Series) - check(assert_type(left.add(f), pd.Series), pd.Series) - check(assert_type(left.add(c), pd.Series), pd.Series) - - check(assert_type(left.radd(b), pd.Series), pd.Series) - check(assert_type(left.radd(i), pd.Series), pd.Series) - check(assert_type(left.radd(f), pd.Series), pd.Series) - check(assert_type(left.radd(c), pd.Series), pd.Series) + check(assert_type(left_i + a, pd.Series), pd.Series) + check(assert_type(left_i + b, pd.Series), pd.Series) + check(assert_type(left_i + i, pd.Series), pd.Series) + check(assert_type(left_i + f, pd.Series), pd.Series) + check(assert_type(left_i + c, pd.Series), pd.Series) + + check(assert_type(a + left_i, pd.Series), pd.Series) + check(assert_type(b + left_i, pd.Series), pd.Series) + check(assert_type(i + left_i, pd.Series), pd.Series) + check(assert_type(f + left_i, pd.Series), pd.Series) + check(assert_type(c + left_i, pd.Series), pd.Series) + + check(assert_type(left_i.add(a), pd.Series), pd.Series) + check(assert_type(left_i.add(b), pd.Series), pd.Series) + check(assert_type(left_i.add(i), pd.Series), pd.Series) + check(assert_type(left_i.add(f), pd.Series), pd.Series) + check(assert_type(left_i.add(c), pd.Series), pd.Series) + + check(assert_type(left_i.radd(a), pd.Series), pd.Series) + check(assert_type(left_i.radd(b), pd.Series), pd.Series) + check(assert_type(left_i.radd(i), pd.Series), pd.Series) + check(assert_type(left_i.radd(f), pd.Series), pd.Series) + check(assert_type(left_i.radd(c), pd.Series), pd.Series) + + +def test_add_i_py_str() -> None: + """Test pd.Series[Any] (int) + Python str""" + s = "abc" + + if TYPE_CHECKING_INVALID_USAGE: + assert_type(left_i + s, Never) + assert_type(s + left_i, Never) + + def _type_checking_enabler_0() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_i.add(s), Never) + + def _type_checking_enabler_1() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_i.radd(s), Never) diff --git a/tests/series/arithmetic/test_mul.py b/tests/series/arithmetic/test_mul.py index ec7ed5f49..e98f14c85 100644 --- a/tests/series/arithmetic/test_mul.py +++ b/tests/series/arithmetic/test_mul.py @@ -3,124 +3,143 @@ import pandas as pd from typing_extensions import assert_type -from tests import check +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) -left = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand def test_mul_py_scalar() -> None: - """Test pd.Series[Any] * Python native scalars""" + """Test pd.Series[Any] (int) * Python native scalars""" b, i, f, c = True, 1, 1.0, 1j - check(assert_type(left * b, pd.Series), pd.Series) - check(assert_type(left * i, pd.Series), pd.Series) - check(assert_type(left * f, pd.Series), pd.Series) - check(assert_type(left * c, pd.Series), pd.Series) + check(assert_type(left_i * b, pd.Series), pd.Series) + check(assert_type(left_i * i, pd.Series), pd.Series) + check(assert_type(left_i * f, pd.Series), pd.Series) + check(assert_type(left_i * c, pd.Series), pd.Series) - check(assert_type(b * left, pd.Series), pd.Series) - check(assert_type(i * left, pd.Series), pd.Series) - check(assert_type(f * left, pd.Series), pd.Series) - check(assert_type(c * left, pd.Series), pd.Series) + check(assert_type(b * left_i, pd.Series), pd.Series) + check(assert_type(i * left_i, pd.Series), pd.Series) + check(assert_type(f * left_i, pd.Series), pd.Series) + check(assert_type(c * left_i, pd.Series), pd.Series) - check(assert_type(left.mul(b), pd.Series), pd.Series) - check(assert_type(left.mul(i), pd.Series), pd.Series) - check(assert_type(left.mul(f), pd.Series), pd.Series) - check(assert_type(left.mul(c), pd.Series), pd.Series) + check(assert_type(left_i.mul(b), pd.Series), pd.Series) + check(assert_type(left_i.mul(i), pd.Series), pd.Series) + check(assert_type(left_i.mul(f), pd.Series), pd.Series) + check(assert_type(left_i.mul(c), pd.Series), pd.Series) - check(assert_type(left.rmul(b), pd.Series), pd.Series) - check(assert_type(left.rmul(i), pd.Series), pd.Series) - check(assert_type(left.rmul(f), pd.Series), pd.Series) - check(assert_type(left.rmul(c), pd.Series), pd.Series) + check(assert_type(left_i.rmul(b), pd.Series), pd.Series) + check(assert_type(left_i.rmul(i), pd.Series), pd.Series) + check(assert_type(left_i.rmul(f), pd.Series), pd.Series) + check(assert_type(left_i.rmul(c), pd.Series), pd.Series) def test_mul_py_sequence() -> None: - """Test pd.Series[Any] * Python native sequence""" + """Test pd.Series[Any] (int) * Python native sequence""" b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] - check(assert_type(left * b, pd.Series), pd.Series) - check(assert_type(left * i, pd.Series), pd.Series) - check(assert_type(left * f, pd.Series), pd.Series) - check(assert_type(left * c, pd.Series), pd.Series) + check(assert_type(left_i * b, pd.Series), pd.Series) + check(assert_type(left_i * i, pd.Series), pd.Series) + check(assert_type(left_i * f, pd.Series), pd.Series) + check(assert_type(left_i * c, pd.Series), pd.Series) - check(assert_type(b * left, pd.Series), pd.Series) - check(assert_type(i * left, pd.Series), pd.Series) - check(assert_type(f * left, pd.Series), pd.Series) - check(assert_type(c * left, pd.Series), pd.Series) + check(assert_type(b * left_i, pd.Series), pd.Series) + check(assert_type(i * left_i, pd.Series), pd.Series) + check(assert_type(f * left_i, pd.Series), pd.Series) + check(assert_type(c * left_i, pd.Series), pd.Series) - check(assert_type(left.mul(b), pd.Series), pd.Series) - check(assert_type(left.mul(i), pd.Series), pd.Series) - check(assert_type(left.mul(f), pd.Series), pd.Series) - check(assert_type(left.mul(c), pd.Series), pd.Series) + check(assert_type(left_i.mul(b), pd.Series), pd.Series) + check(assert_type(left_i.mul(i), pd.Series), pd.Series) + check(assert_type(left_i.mul(f), pd.Series), pd.Series) + check(assert_type(left_i.mul(c), pd.Series), pd.Series) - check(assert_type(left.rmul(b), pd.Series), pd.Series) - check(assert_type(left.rmul(i), pd.Series), pd.Series) - check(assert_type(left.rmul(f), pd.Series), pd.Series) - check(assert_type(left.rmul(c), pd.Series), pd.Series) + check(assert_type(left_i.rmul(b), pd.Series), pd.Series) + check(assert_type(left_i.rmul(i), pd.Series), pd.Series) + check(assert_type(left_i.rmul(f), pd.Series), pd.Series) + check(assert_type(left_i.rmul(c), pd.Series), pd.Series) def test_mul_numpy_array() -> None: - """Test pd.Series[Any] * numpy array""" + """Test pd.Series[Any] (int) * numpy array""" b = np.array([True, False, True], np.bool_) i = np.array([2, 3, 5], np.int64) f = np.array([1.0, 2.0, 3.0], np.float64) c = np.array([1.1j, 2.2j, 4.1j], np.complex128) - check(assert_type(left * b, pd.Series), pd.Series) - check(assert_type(left * i, pd.Series), pd.Series) - check(assert_type(left * f, pd.Series), pd.Series) - check(assert_type(left * c, pd.Series), pd.Series) + check(assert_type(left_i * b, pd.Series), pd.Series) + check(assert_type(left_i * i, pd.Series), pd.Series) + check(assert_type(left_i * f, pd.Series), pd.Series) + check(assert_type(left_i * c, pd.Series), pd.Series) # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rmul__` cannot override. At runtime, they return # `Series`s. # `mypy` thinks the return types are `Any`, which is a bug. check( - assert_type(b * left, "npt.NDArray[np.bool_]"), pd.Series # type: ignore[assert-type] + assert_type(b * left_i, "npt.NDArray[np.bool_]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(i * left, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] + assert_type(i * left_i, "npt.NDArray[np.int64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(f * left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(f * left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(c * left, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] + assert_type(c * left_i, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] ) - check(assert_type(left.mul(b), pd.Series), pd.Series) - check(assert_type(left.mul(i), pd.Series), pd.Series) - check(assert_type(left.mul(f), pd.Series), pd.Series) - check(assert_type(left.mul(c), pd.Series), pd.Series) + check(assert_type(left_i.mul(b), pd.Series), pd.Series) + check(assert_type(left_i.mul(i), pd.Series), pd.Series) + check(assert_type(left_i.mul(f), pd.Series), pd.Series) + check(assert_type(left_i.mul(c), pd.Series), pd.Series) - check(assert_type(left.rmul(b), pd.Series), pd.Series) - check(assert_type(left.rmul(i), pd.Series), pd.Series) - check(assert_type(left.rmul(f), pd.Series), pd.Series) - check(assert_type(left.rmul(c), pd.Series), pd.Series) + check(assert_type(left_i.rmul(b), pd.Series), pd.Series) + check(assert_type(left_i.rmul(i), pd.Series), pd.Series) + check(assert_type(left_i.rmul(f), pd.Series), pd.Series) + check(assert_type(left_i.rmul(c), pd.Series), pd.Series) def test_mul_pd_series() -> None: - """Test pd.Series[Any] * pandas series""" + """Test pd.Series[Any] (int) * pandas series""" + a = pd.DataFrame({"a": [1, 2, 3]})["a"] b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) - check(assert_type(left * b, pd.Series), pd.Series) - check(assert_type(left * i, pd.Series), pd.Series) - check(assert_type(left * f, pd.Series), pd.Series) - check(assert_type(left * c, pd.Series), pd.Series) - - check(assert_type(b * left, pd.Series), pd.Series) - check(assert_type(i * left, pd.Series), pd.Series) - check(assert_type(f * left, pd.Series), pd.Series) - check(assert_type(c * left, pd.Series), pd.Series) - - check(assert_type(left.mul(b), pd.Series), pd.Series) - check(assert_type(left.mul(i), pd.Series), pd.Series) - check(assert_type(left.mul(f), pd.Series), pd.Series) - check(assert_type(left.mul(c), pd.Series), pd.Series) - - check(assert_type(left.rmul(b), pd.Series), pd.Series) - check(assert_type(left.rmul(i), pd.Series), pd.Series) - check(assert_type(left.rmul(f), pd.Series), pd.Series) - check(assert_type(left.rmul(c), pd.Series), pd.Series) + check(assert_type(left_i * a, pd.Series), pd.Series) + check(assert_type(left_i * b, pd.Series), pd.Series) + check(assert_type(left_i * i, pd.Series), pd.Series) + check(assert_type(left_i * f, pd.Series), pd.Series) + check(assert_type(left_i * c, pd.Series), pd.Series) + + check(assert_type(a * left_i, pd.Series), pd.Series) + check(assert_type(b * left_i, pd.Series), pd.Series) + check(assert_type(i * left_i, pd.Series), pd.Series) + check(assert_type(f * left_i, pd.Series), pd.Series) + check(assert_type(c * left_i, pd.Series), pd.Series) + + check(assert_type(left_i.mul(a), pd.Series), pd.Series) + check(assert_type(left_i.mul(b), pd.Series), pd.Series) + check(assert_type(left_i.mul(i), pd.Series), pd.Series) + check(assert_type(left_i.mul(f), pd.Series), pd.Series) + check(assert_type(left_i.mul(c), pd.Series), pd.Series) + + check(assert_type(left_i.rmul(a), pd.Series), pd.Series) + check(assert_type(left_i.rmul(b), pd.Series), pd.Series) + check(assert_type(left_i.rmul(i), pd.Series), pd.Series) + check(assert_type(left_i.rmul(f), pd.Series), pd.Series) + check(assert_type(left_i.rmul(c), pd.Series), pd.Series) + + +def test_mul_str_py_str() -> None: + """Test pd.Series[Any] (int) * Python str""" + s = "abc" + + if TYPE_CHECKING_INVALID_USAGE: + left_i * s # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + s * left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + left_i.mul(s) # type: ignore[type-var] # pyright: ignore[reportArgumentType,reportCallIssue] + left_i.rmul(s) # type: ignore[type-var] # pyright: ignore[reportArgumentType,reportCallIssue] diff --git a/tests/series/arithmetic/test_sub.py b/tests/series/arithmetic/test_sub.py index 636aa242c..fba533f8a 100644 --- a/tests/series/arithmetic/test_sub.py +++ b/tests/series/arithmetic/test_sub.py @@ -2,26 +2,31 @@ datetime, timedelta, ) -from typing import ( - TYPE_CHECKING, - NoReturn, -) +from typing import NoReturn import numpy as np from numpy import typing as npt # noqa: F401 import pandas as pd -from typing_extensions import assert_type +from typing_extensions import ( + Never, + assert_type, +) -from tests import check +from tests import ( + TYPE_CHECKING_INVALID_USAGE, + check, +) -if TYPE_CHECKING: - from pandas.core.series import TimedeltaSeries # noqa: F401 +anchor = datetime(2025, 8, 18) -left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +# left operands +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] +left_ts = pd.DataFrame({"a": [anchor + timedelta(hours=h + 1) for h in range(3)]})["a"] +left_td = pd.DataFrame({"a": [timedelta(hours=h, minutes=1) for h in range(3)]})["a"] -def test_sub_py_scalar() -> None: - """Test pd.Series[Any] - Python native scalars""" +def test_sub_i_py_scalar() -> None: + """Test pd.Series[Any] (int) - Python native scalars""" b, i, f, c = True, 1, 1.0, 1j check(assert_type(left_i - b, pd.Series), pd.Series) @@ -45,8 +50,8 @@ def test_sub_py_scalar() -> None: check(assert_type(left_i.rsub(c), pd.Series), pd.Series) -def test_sub_py_sequence() -> None: - """Test pd.Series[Any] - Python native sequence""" +def test_sub_i_py_sequence() -> None: + """Test pd.Series[Any] (int) - Python native sequence""" b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] check(assert_type(left_i - b, pd.Series), pd.Series) @@ -70,8 +75,8 @@ def test_sub_py_sequence() -> None: check(assert_type(left_i.rsub(c), pd.Series), pd.Series) -def test_sub_numpy_array() -> None: - """Test pd.Series[Any] - numpy array""" +def test_sub_i_numpy_array() -> None: + """Test pd.Series[Any] (int) - numpy array""" b = np.array([True, False, True], np.bool_) i = np.array([2, 3, 5], np.int64) f = np.array([1.0, 2.0, 3.0], np.float64) @@ -108,85 +113,139 @@ def test_sub_numpy_array() -> None: check(assert_type(left_i.rsub(c), pd.Series), pd.Series) -def test_sub_pd_series() -> None: - """Test pd.Series[Any] - pandas series""" +def test_sub_i_pd_series() -> None: + """Test pd.Series[Any] (int) - pandas series""" + a = pd.DataFrame({"a": [1, 2, 3]})["a"] b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) + check(assert_type(left_i - a, pd.Series), pd.Series) check(assert_type(left_i - b, pd.Series), pd.Series) check(assert_type(left_i - i, pd.Series), pd.Series) check(assert_type(left_i - f, pd.Series), pd.Series) check(assert_type(left_i - c, pd.Series), pd.Series) + check(assert_type(a - left_i, pd.Series), pd.Series) check(assert_type(b - left_i, pd.Series), pd.Series) check(assert_type(i - left_i, pd.Series), pd.Series) check(assert_type(f - left_i, pd.Series), pd.Series) check(assert_type(c - left_i, pd.Series), pd.Series) + check(assert_type(left_i.sub(a), pd.Series), pd.Series) check(assert_type(left_i.sub(b), pd.Series), pd.Series) check(assert_type(left_i.sub(i), pd.Series), pd.Series) check(assert_type(left_i.sub(f), pd.Series), pd.Series) check(assert_type(left_i.sub(c), pd.Series), pd.Series) + check(assert_type(left_i.rsub(a), pd.Series), pd.Series) check(assert_type(left_i.rsub(b), pd.Series), pd.Series) check(assert_type(left_i.rsub(i), pd.Series), pd.Series) check(assert_type(left_i.rsub(f), pd.Series), pd.Series) check(assert_type(left_i.rsub(c), pd.Series), pd.Series) -anchor = datetime(2025, 8, 18) -left_ts = pd.DataFrame({"a": [anchor + timedelta(hours=h + 1) for h in range(3)]})["a"] - - -def test_sub_py_datetime() -> None: - """Test pd.Series[Any] - Python native datetime""" +def test_sub_ts_py_datetime() -> None: + """Test pd.Series[Any] (Timestamp | Timedelta) - Python native datetime""" s = anchor + a = [s + timedelta(minutes=m) for m in range(3)] - check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _0 = left_ts - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _1 = left_ts - a # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _2 = left_td - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _3 = left_td - a # type: ignore[operator] # pyright: ignore[reportOperatorIssue] - check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) + _4 = s - left_ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _5 = a - left_ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _6 = s - left_td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + _7 = a - left_td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] - check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + left_ts.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_ts.sub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.sub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) + left_ts.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_ts.rsub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.rsub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] -def test_sub_numpy_datetime() -> None: - """Test pd.Series[Any] - numpy datetime(s)""" +def test_sub_ts_numpy_datetime() -> None: + """Test pd.Series[Any] (Timestamp | Timedelta) - numpy datetime(s)""" s = np.datetime64(anchor) a = np.array([s + np.timedelta64(m, "m") for m in range(3)], np.datetime64) - check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + # We would like to have _1, _3, _5 and _7 below as invalid, but numpy.ndarray.__rsub__ overrides our efforts + _0 = left_ts - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + # _1 = left_ts - a + _2 = left_td - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + # _3 = left_td - a - check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) - # `numpy` typing gives the corresponding `ndarray`s in the static type - # checking, where our `__rsub__` cannot override. At runtime, they return - # `Series`s. - check(assert_type(a - left_ts, "npt.NDArray[np.datetime64]"), pd.Series, pd.Timedelta) # type: ignore[assert-type] + _4 = s - left_ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + # _5 = a - left_ts + _6 = s - left_td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + # _7 = a - left_td - check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) + left_ts.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_ts.sub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.sub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] - check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.rsub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) + left_ts.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_ts.rsub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_td.rsub(a) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] -def test_sub_pd_datetime() -> None: - """Test pd.Series[Any] - Pandas datetime(s)""" +def test_sub_ts_pd_datetime() -> None: + """Test pd.Series[Any] (Timestamp | Timedelta) - Pandas datetime(s)""" s = pd.Timestamp(anchor) a = pd.Series([s + pd.Timedelta(minutes=m) for m in range(3)]) - check(assert_type(left_ts - s, "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts - a, "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _0 = left_ts - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + assert_type(left_ts - a, Never) + + _2 = left_td - s # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + assert_type(left_td - a, Never) + + _4 = s - left_ts # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + assert_type(a - left_ts, Never) + + _6 = s - left_td # type: ignore[operator] # pyright: ignore[reportOperatorIssue] + assert_type(a - left_td, Never) + + left_ts.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + + def _type_checking_enabler_0() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_ts.sub(a), Never) + + left_td.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + + def _type_checking_enabler_1() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_td.sub(a), Never) + + left_ts.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + + def _type_checking_enabler_2() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_ts.rsub(a), Never) + + left_td.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + + def _type_checking_enabler_3() -> None: # pyright: ignore[reportUnusedFunction] + assert_type(left_td.rsub(a), Never) - check(assert_type(s - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(a - left_ts, "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.sub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.sub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) +def test_sub_str_py_str() -> None: + """Test pd.Series[Any] (int) - Python str""" + s = "abc" - check(assert_type(left_ts.rsub(s), "TimedeltaSeries"), pd.Series, pd.Timedelta) - check(assert_type(left_ts.rsub(a), "TimedeltaSeries"), pd.Series, pd.Timedelta) + if TYPE_CHECKING_INVALID_USAGE: + _0 = left_i - s # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + _1 = s - left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + left_i.sub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_i.rsub(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] diff --git a/tests/series/arithmetic/test_truediv.py b/tests/series/arithmetic/test_truediv.py index 155a0c0e7..26c0bb8a8 100644 --- a/tests/series/arithmetic/test_truediv.py +++ b/tests/series/arithmetic/test_truediv.py @@ -7,168 +7,176 @@ from tests import ( PD_LTE_23, + TYPE_CHECKING_INVALID_USAGE, check, ) -left = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand +left_i = pd.DataFrame({"a": [1, 2, 3]})["a"] # left operand def test_truediv_py_scalar() -> None: - """Test pd.Series[Any] / Python native scalars""" + """Test pd.Series[Any] (int) / Python native scalars""" b, i, f, c = True, 1, 1.0, 1j - check(assert_type(left / b, pd.Series), pd.Series) - check(assert_type(left / i, pd.Series), pd.Series) - check(assert_type(left / f, pd.Series), pd.Series) - check(assert_type(left / c, pd.Series), pd.Series) + check(assert_type(left_i / b, pd.Series), pd.Series) + check(assert_type(left_i / i, pd.Series), pd.Series) + check(assert_type(left_i / f, pd.Series), pd.Series) + check(assert_type(left_i / c, pd.Series), pd.Series) - check(assert_type(b / left, pd.Series), pd.Series) - check(assert_type(i / left, pd.Series), pd.Series) - check(assert_type(f / left, pd.Series), pd.Series) - check(assert_type(c / left, pd.Series), pd.Series) + check(assert_type(b / left_i, pd.Series), pd.Series) + check(assert_type(i / left_i, pd.Series), pd.Series) + check(assert_type(f / left_i, pd.Series), pd.Series) + check(assert_type(c / left_i, pd.Series), pd.Series) - check(assert_type(left.truediv(b), pd.Series), pd.Series) - check(assert_type(left.truediv(i), pd.Series), pd.Series) - check(assert_type(left.truediv(f), pd.Series), pd.Series) - check(assert_type(left.truediv(c), pd.Series), pd.Series) + check(assert_type(left_i.truediv(b), pd.Series), pd.Series) + check(assert_type(left_i.truediv(i), pd.Series), pd.Series) + check(assert_type(left_i.truediv(f), pd.Series), pd.Series) + check(assert_type(left_i.truediv(c), pd.Series), pd.Series) - check(assert_type(left.div(b), pd.Series), pd.Series) - check(assert_type(left.div(i), pd.Series), pd.Series) - check(assert_type(left.div(f), pd.Series), pd.Series) - check(assert_type(left.div(c), pd.Series), pd.Series) + check(assert_type(left_i.div(b), pd.Series), pd.Series) + check(assert_type(left_i.div(i), pd.Series), pd.Series) + check(assert_type(left_i.div(f), pd.Series), pd.Series) + check(assert_type(left_i.div(c), pd.Series), pd.Series) - check(assert_type(left.rtruediv(b), pd.Series), pd.Series) - check(assert_type(left.rtruediv(i), pd.Series), pd.Series) - check(assert_type(left.rtruediv(f), pd.Series), pd.Series) - check(assert_type(left.rtruediv(c), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(b), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(i), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(f), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(c), pd.Series), pd.Series) - check(assert_type(left.rdiv(b), pd.Series), pd.Series) - check(assert_type(left.rdiv(i), pd.Series), pd.Series) - check(assert_type(left.rdiv(f), pd.Series), pd.Series) - check(assert_type(left.rdiv(c), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(b), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(i), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(f), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(c), pd.Series), pd.Series) def test_truediv_py_sequence() -> None: - """Test pd.Series[Any] / Python native sequence""" + """Test pd.Series[Any] (int) / Python native sequence""" b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j] - check(assert_type(left / b, pd.Series), pd.Series) - check(assert_type(left / i, pd.Series), pd.Series) - check(assert_type(left / f, pd.Series), pd.Series) - check(assert_type(left / c, pd.Series), pd.Series) + check(assert_type(left_i / b, pd.Series), pd.Series) + check(assert_type(left_i / i, pd.Series), pd.Series) + check(assert_type(left_i / f, pd.Series), pd.Series) + check(assert_type(left_i / c, pd.Series), pd.Series) - check(assert_type(b / left, pd.Series), pd.Series) - check(assert_type(i / left, pd.Series), pd.Series) - check(assert_type(f / left, pd.Series), pd.Series) - check(assert_type(c / left, pd.Series), pd.Series) + check(assert_type(b / left_i, pd.Series), pd.Series) + check(assert_type(i / left_i, pd.Series), pd.Series) + check(assert_type(f / left_i, pd.Series), pd.Series) + check(assert_type(c / left_i, pd.Series), pd.Series) - check(assert_type(left.truediv(b), pd.Series), pd.Series) - check(assert_type(left.truediv(i), pd.Series), pd.Series) - check(assert_type(left.truediv(f), pd.Series), pd.Series) - check(assert_type(left.truediv(c), pd.Series), pd.Series) + check(assert_type(left_i.truediv(b), pd.Series), pd.Series) + check(assert_type(left_i.truediv(i), pd.Series), pd.Series) + check(assert_type(left_i.truediv(f), pd.Series), pd.Series) + check(assert_type(left_i.truediv(c), pd.Series), pd.Series) - check(assert_type(left.div(b), pd.Series), pd.Series) - check(assert_type(left.div(i), pd.Series), pd.Series) - check(assert_type(left.div(f), pd.Series), pd.Series) - check(assert_type(left.div(c), pd.Series), pd.Series) + check(assert_type(left_i.div(b), pd.Series), pd.Series) + check(assert_type(left_i.div(i), pd.Series), pd.Series) + check(assert_type(left_i.div(f), pd.Series), pd.Series) + check(assert_type(left_i.div(c), pd.Series), pd.Series) - check(assert_type(left.rtruediv(b), pd.Series), pd.Series) - check(assert_type(left.rtruediv(i), pd.Series), pd.Series) - check(assert_type(left.rtruediv(f), pd.Series), pd.Series) - check(assert_type(left.rtruediv(c), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(b), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(i), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(f), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(c), pd.Series), pd.Series) - check(assert_type(left.rdiv(b), pd.Series), pd.Series) - check(assert_type(left.rdiv(i), pd.Series), pd.Series) - check(assert_type(left.rdiv(f), pd.Series), pd.Series) - check(assert_type(left.rdiv(c), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(b), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(i), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(f), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(c), pd.Series), pd.Series) def test_truediv_numpy_array() -> None: - """Test pd.Series[Any] / numpy array""" + """Test pd.Series[Any] (int) / numpy array""" b = np.array([True, False, True], np.bool_) i = np.array([2, 3, 5], np.int64) f = np.array([1.0, 2.0, 3.0], np.float64) c = np.array([1.1j, 2.2j, 4.1j], np.complex64) - check(assert_type(left / b, pd.Series), pd.Series) - check(assert_type(left / i, pd.Series), pd.Series) - check(assert_type(left / f, pd.Series), pd.Series) - check(assert_type(left / c, pd.Series), pd.Series) + check(assert_type(left_i / b, pd.Series), pd.Series) + check(assert_type(left_i / i, pd.Series), pd.Series) + check(assert_type(left_i / f, pd.Series), pd.Series) + check(assert_type(left_i / c, pd.Series), pd.Series) # `numpy` typing gives the corresponding `ndarray`s in the static type # checking, where our `__rtruediv__` cannot override. At runtime, they return # `Series`s. # `mypy` thinks the return types are `Any`, which is a bug. check( - assert_type(b / left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(b / left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(i / left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(i / left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(f / left, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] + assert_type(f / left_i, "npt.NDArray[np.float64]"), pd.Series # type: ignore[assert-type] ) check( - assert_type(c / left, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] + assert_type(c / left_i, "npt.NDArray[np.complex128]"), pd.Series # type: ignore[assert-type] ) - check(assert_type(left.truediv(b), pd.Series), pd.Series) - check(assert_type(left.truediv(i), pd.Series), pd.Series) - check(assert_type(left.truediv(f), pd.Series), pd.Series) - check(assert_type(left.truediv(c), pd.Series), pd.Series) + check(assert_type(left_i.truediv(b), pd.Series), pd.Series) + check(assert_type(left_i.truediv(i), pd.Series), pd.Series) + check(assert_type(left_i.truediv(f), pd.Series), pd.Series) + check(assert_type(left_i.truediv(c), pd.Series), pd.Series) - check(assert_type(left.div(b), pd.Series), pd.Series) - check(assert_type(left.div(i), pd.Series), pd.Series) - check(assert_type(left.div(f), pd.Series), pd.Series) - check(assert_type(left.div(c), pd.Series), pd.Series) + check(assert_type(left_i.div(b), pd.Series), pd.Series) + check(assert_type(left_i.div(i), pd.Series), pd.Series) + check(assert_type(left_i.div(f), pd.Series), pd.Series) + check(assert_type(left_i.div(c), pd.Series), pd.Series) - check(assert_type(left.rtruediv(b), pd.Series), pd.Series) - check(assert_type(left.rtruediv(i), pd.Series), pd.Series) - check(assert_type(left.rtruediv(f), pd.Series), pd.Series) - check(assert_type(left.rtruediv(c), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(b), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(i), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(f), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(c), pd.Series), pd.Series) - check(assert_type(left.rdiv(b), pd.Series), pd.Series) - check(assert_type(left.rdiv(i), pd.Series), pd.Series) - check(assert_type(left.rdiv(f), pd.Series), pd.Series) - check(assert_type(left.rdiv(c), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(b), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(i), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(f), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(c), pd.Series), pd.Series) def test_truediv_pd_series() -> None: - """Test pd.Series[Any] / pandas series""" + """Test pd.Series[Any] (int) / pandas series""" + a = pd.DataFrame({"a": [1, 2, 3]})["a"] b = pd.Series([True, False, True]) i = pd.Series([2, 3, 5]) f = pd.Series([1.0, 2.0, 3.0]) c = pd.Series([1.1j, 2.2j, 4.1j]) - check(assert_type(left / b, pd.Series), pd.Series) - check(assert_type(left / i, pd.Series), pd.Series) - check(assert_type(left / f, pd.Series), pd.Series) - check(assert_type(left / c, pd.Series), pd.Series) - - check(assert_type(b / left, pd.Series), pd.Series) - check(assert_type(i / left, pd.Series), pd.Series) - check(assert_type(f / left, pd.Series), pd.Series) - check(assert_type(c / left, pd.Series), pd.Series) - - check(assert_type(left.truediv(b), pd.Series), pd.Series) - check(assert_type(left.truediv(i), pd.Series), pd.Series) - check(assert_type(left.truediv(f), pd.Series), pd.Series) - check(assert_type(left.truediv(c), pd.Series), pd.Series) - - check(assert_type(left.div(b), pd.Series), pd.Series) - check(assert_type(left.div(i), pd.Series), pd.Series) - check(assert_type(left.div(f), pd.Series), pd.Series) - check(assert_type(left.div(c), pd.Series), pd.Series) - - check(assert_type(left.rtruediv(b), pd.Series), pd.Series) - check(assert_type(left.rtruediv(i), pd.Series), pd.Series) - check(assert_type(left.rtruediv(f), pd.Series), pd.Series) - check(assert_type(left.rtruediv(c), pd.Series), pd.Series) - - check(assert_type(left.rdiv(b), pd.Series), pd.Series) - check(assert_type(left.rdiv(i), pd.Series), pd.Series) - check(assert_type(left.rdiv(f), pd.Series), pd.Series) - check(assert_type(left.rdiv(c), pd.Series), pd.Series) + check(assert_type(left_i / a, pd.Series), pd.Series) + check(assert_type(left_i / b, pd.Series), pd.Series) + check(assert_type(left_i / i, pd.Series), pd.Series) + check(assert_type(left_i / f, pd.Series), pd.Series) + check(assert_type(left_i / c, pd.Series), pd.Series) + + check(assert_type(a / left_i, pd.Series), pd.Series) + check(assert_type(b / left_i, pd.Series), pd.Series) + check(assert_type(i / left_i, pd.Series), pd.Series) + check(assert_type(f / left_i, pd.Series), pd.Series) + check(assert_type(c / left_i, pd.Series), pd.Series) + + check(assert_type(left_i.truediv(a), pd.Series), pd.Series) + check(assert_type(left_i.truediv(b), pd.Series), pd.Series) + check(assert_type(left_i.truediv(i), pd.Series), pd.Series) + check(assert_type(left_i.truediv(f), pd.Series), pd.Series) + check(assert_type(left_i.truediv(c), pd.Series), pd.Series) + + check(assert_type(left_i.div(a), pd.Series), pd.Series) + check(assert_type(left_i.div(b), pd.Series), pd.Series) + check(assert_type(left_i.div(i), pd.Series), pd.Series) + check(assert_type(left_i.div(f), pd.Series), pd.Series) + check(assert_type(left_i.div(c), pd.Series), pd.Series) + + check(assert_type(left_i.rtruediv(a), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(b), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(i), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(f), pd.Series), pd.Series) + check(assert_type(left_i.rtruediv(c), pd.Series), pd.Series) + + check(assert_type(left_i.rdiv(a), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(b), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(i), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(f), pd.Series), pd.Series) + check(assert_type(left_i.rdiv(c), pd.Series), pd.Series) def test_truediv_paths(tmp_path: Path) -> None: @@ -204,3 +212,18 @@ def test_truediv_path(tmp_path: Path) -> None: check(assert_type(fnames.rtruediv(tmp_path), pd.Series), pd.Series, Path) check(assert_type(fnames.rdiv(tmp_path), pd.Series), pd.Series, Path) + + +def test_truediv_str_py_str() -> None: + """Test pd.Series[Any] (int) / Python str""" + s = "abc" + + if TYPE_CHECKING_INVALID_USAGE: + _0 = left_i / s # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + _1 = s / left_i # type: ignore[operator] # pyright:ignore[reportOperatorIssue] + + left_i.truediv(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_i.div(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + + left_i.rtruediv(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] + left_i.rdiv(s) # type: ignore[arg-type] # pyright: ignore[reportArgumentType,reportCallIssue] diff --git a/tests/series/test_series.py b/tests/series/test_series.py index af90cde4b..12326fd11 100644 --- a/tests/series/test_series.py +++ b/tests/series/test_series.py @@ -844,17 +844,20 @@ def test_types_element_wise_arithmetic() -> None: def test_types_scalar_arithmetic() -> None: s = pd.Series([0, 1, -10]) - check(assert_type(s + 1, "pd.Series[int]"), pd.Series, np.integer) check(assert_type(s.add(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_sub: pd.Series = s - 1 - res_sub2: pd.Series = s.sub(1, fill_value=0) + check(assert_type(s.sub(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_mul: pd.Series = s * 2 - res_mul2: pd.Series = s.mul(2, fill_value=0) + check(assert_type(s.mul(1, fill_value=0), "pd.Series[int]"), pd.Series, np.integer) - res_div: pd.Series = s / 2 - res_div2: pd.Series = s.div(2, fill_value=0) + check( + assert_type(s.truediv(2, fill_value=0), "pd.Series[float]"), + pd.Series, + np.floating, + ) + check( + assert_type(s.div(2, fill_value=0), "pd.Series[float]"), pd.Series, np.floating + ) res_floordiv: pd.Series = s // 2 res_floordiv2: pd.Series = s.floordiv(2, fill_value=0) @@ -868,14 +871,6 @@ def test_types_scalar_arithmetic() -> None: res_pow3: pd.Series = s.pow(0.5) -def test_types_complex_arithmetic() -> None: - """Test adding complex number to pd.Series[float] GH 103.""" - c = 1 + 1j - s = pd.Series([1.0, 2.0, 3.0]) - x = s + c - y = s - c - - def test_types_groupby() -> None: s = pd.Series([4, 2, 1, 8], index=["a", "b", "a", "b"]) s.groupby(["a", "b", "a", "b"]) @@ -1594,16 +1589,8 @@ def test_series_loc_setitem() -> None: def test_series_min_max_sub_axis() -> None: df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [5, 4, 3, 2, 1]}) - s1 = df.min(axis=1) - s2 = df.max(axis=1) - sa = s1 + s2 - ss = s1 - s2 - sm = s1 * s2 - sd = s1 / s2 - check(assert_type(sa, pd.Series), pd.Series) - check(assert_type(ss, pd.Series), pd.Series) - check(assert_type(sm, pd.Series), pd.Series) - check(assert_type(sd, pd.Series), pd.Series) + check(assert_type(df.min(axis=1), pd.Series), pd.Series) + check(assert_type(df.max(axis=1), pd.Series), pd.Series) def test_series_index_isin() -> None: @@ -1654,12 +1641,6 @@ def test_reset_index() -> None: assert assert_type(s.reset_index(inplace=True, drop=True), None) is None -def test_series_add_str() -> None: - s = pd.Series(["abc", "def"]) - check(assert_type(s + "x", "pd.Series[str]"), pd.Series, str) - check(assert_type("x" + s, "pd.Series[str]"), pd.Series, str) - - def test_series_dtype() -> None: s = pd.Series(["abc", "def"], dtype=str) check(assert_type(s, "pd.Series[str]"), pd.Series, str) diff --git a/tests/test_frame.py b/tests/test_frame.py index a212b441c..31ce7e004 100644 --- a/tests/test_frame.py +++ b/tests/test_frame.py @@ -74,6 +74,7 @@ if TYPE_CHECKING: from pandas.core.frame import _PandasNamedTuple + from pandas.core.series import TimestampSeries else: _PandasNamedTuple: TypeAlias = tuple @@ -4440,7 +4441,9 @@ def test_frame_setitem_na() -> None: df.loc[ind, :] = pd.NA df.iloc[[0, 2], :] = pd.NA - df["x"] = df["y"] + pd.Timedelta(days=3) + # reveal_type(df["y"]) gives Series[Any], so we have to cast to tell the + # type checker what kind of type it is when adding to a Timedelta + df["x"] = cast("TimestampSeries", df["y"]) + pd.Timedelta(days=3) df.loc[ind, :] = pd.NaT df.iloc[[0, 2], :] = pd.NaT