Skip to content

Commit 6cf48fc

Browse files
committed
fix(comment): #1397 (review)
1 parent c8e0105 commit 6cf48fc

File tree

4 files changed

+442
-0
lines changed

4 files changed

+442
-0
lines changed

pandas-stubs/core/indexes/base.pyi

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,16 @@ class Index(IndexOpsMixin[S1]):
781781
),
782782
) -> Index[Timedelta]: ...
783783
@overload
784+
def __mul__(
785+
self: Index[_str],
786+
other: np_ndarray_float | np_ndarray_complex | np_ndarray_dt | np_ndarray_td,
787+
) -> Never: ...
788+
@overload
789+
def __mul__(
790+
self: Index[_str],
791+
other: int | Sequence[int] | np_ndarray_bool | np_ndarray_anyint | Index[T_INT],
792+
) -> Index[_str]: ...
793+
@overload
784794
def __mul__(self: Index[T_INT], other: bool | Sequence[bool]) -> Index[T_INT]: ...
785795
@overload
786796
def __mul__(self: Index[float], other: int | Sequence[int]) -> Index[float]: ...
@@ -854,6 +864,16 @@ class Index(IndexOpsMixin[S1]):
854864
),
855865
) -> Index[Timedelta]: ...
856866
@overload
867+
def __rmul__(
868+
self: Index[_str],
869+
other: np_ndarray_float | np_ndarray_complex | np_ndarray_dt | np_ndarray_td,
870+
) -> Never: ...
871+
@overload
872+
def __rmul__(
873+
self: Index[_str],
874+
other: int | Sequence[int] | np_ndarray_bool | np_ndarray_anyint | Index[T_INT],
875+
) -> Index[_str]: ...
876+
@overload
857877
def __rmul__(self: Index[T_INT], other: bool | Sequence[bool]) -> Index[T_INT]: ...
858878
@overload
859879
def __rmul__(self: Index[float], other: int | Sequence[int]) -> Index[float]: ...

pandas-stubs/core/series.pyi

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2583,6 +2583,23 @@ class Series(IndexOpsMixin[S1], NDFrame):
25832583
),
25842584
) -> Series[Timedelta]: ...
25852585
@overload
2586+
def __mul__(
2587+
self: Series[_str],
2588+
other: np_ndarray_float | np_ndarray_complex | np_ndarray_dt | np_ndarray_td,
2589+
) -> Never: ...
2590+
@overload
2591+
def __mul__(
2592+
self: Series[_str],
2593+
other: (
2594+
int
2595+
| Sequence[int]
2596+
| np_ndarray_bool
2597+
| np_ndarray_anyint
2598+
| Index[T_INT]
2599+
| Series[T_INT]
2600+
),
2601+
) -> Series[_str]: ...
2602+
@overload
25862603
def __mul__(self: Series[T_INT], other: bool | Sequence[bool]) -> Series[T_INT]: ...
25872604
@overload
25882605
def __mul__(self: Series[float], other: int | Sequence[int]) -> Series[float]: ...
@@ -2675,6 +2692,21 @@ class Series(IndexOpsMixin[S1], NDFrame):
26752692
axis: AxisIndex | None = 0,
26762693
) -> Series[Timedelta]: ...
26772694
@overload
2695+
def mul(
2696+
self: Series[_str],
2697+
other: (
2698+
int
2699+
| Sequence[int]
2700+
| np_ndarray_bool
2701+
| np_ndarray_anyint
2702+
| Index[T_INT]
2703+
| Series[T_INT]
2704+
),
2705+
level: Level | None = None,
2706+
fill_value: float | None = None,
2707+
axis: int = 0,
2708+
) -> Series[_str]: ...
2709+
@overload
26782710
def mul(
26792711
self: Series[T_INT],
26802712
other: bool | Sequence[bool],
@@ -2800,6 +2832,23 @@ class Series(IndexOpsMixin[S1], NDFrame):
28002832
),
28012833
) -> Series[Timedelta]: ...
28022834
@overload
2835+
def __rmul__(
2836+
self: Series[_str],
2837+
other: np_ndarray_float | np_ndarray_complex | np_ndarray_dt | np_ndarray_td,
2838+
) -> Never: ...
2839+
@overload
2840+
def __rmul__(
2841+
self: Series[_str],
2842+
other: (
2843+
int
2844+
| Sequence[int]
2845+
| np_ndarray_bool
2846+
| np_ndarray_anyint
2847+
| Index[T_INT]
2848+
| Series[T_INT]
2849+
),
2850+
) -> Series[_str]: ...
2851+
@overload
28032852
def __rmul__(
28042853
self: Series[T_INT], other: bool | Sequence[bool]
28052854
) -> Series[T_INT]: ...
@@ -2894,6 +2943,21 @@ class Series(IndexOpsMixin[S1], NDFrame):
28942943
axis: AxisIndex | None = 0,
28952944
) -> Series[Timedelta]: ...
28962945
@overload
2946+
def rmul(
2947+
self: Series[_str],
2948+
other: (
2949+
int
2950+
| Sequence[int]
2951+
| np_ndarray_bool
2952+
| np_ndarray_anyint
2953+
| Index[T_INT]
2954+
| Series[T_INT]
2955+
),
2956+
level: Level | None = None,
2957+
fill_value: float | None = None,
2958+
axis: int = 0,
2959+
) -> Series[_str]: ...
2960+
@overload
28972961
def rmul(
28982962
self: Series[T_INT],
28992963
other: bool | Sequence[bool],
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from datetime import (
2+
datetime,
3+
timedelta,
4+
)
5+
from typing import Any
6+
7+
import numpy as np
8+
from numpy import typing as npt # noqa: F401
9+
import pandas as pd
10+
import pytest
11+
from typing_extensions import (
12+
Never,
13+
assert_type,
14+
)
15+
16+
from tests import (
17+
TYPE_CHECKING_INVALID_USAGE,
18+
check,
19+
)
20+
21+
22+
@pytest.fixture
23+
def left() -> "pd.Index[str]":
24+
"""left operand"""
25+
lo = pd.Index(["1", "2", "3"])
26+
return check(assert_type(lo, "pd.Index[str]"), pd.Index, str)
27+
28+
29+
def test_mul_py_scalar(left: "pd.Index[str]") -> None:
30+
"""Test pd.Index[str] * Python native scalars"""
31+
b, i, f, c = True, 1, 1.0, 1j
32+
s, d = datetime(2025, 9, 27), timedelta(seconds=1)
33+
34+
check(assert_type(left * b, "pd.Index[str]"), pd.Index, str)
35+
check(assert_type(left * i, "pd.Index[str]"), pd.Index, str)
36+
if TYPE_CHECKING_INVALID_USAGE:
37+
_03 = left * f # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
38+
_04 = left * c # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
39+
_05 = left * s # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
40+
_06 = left * d # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
41+
42+
check(assert_type(b * left, "pd.Index[str]"), pd.Index, str)
43+
check(assert_type(i * left, "pd.Index[str]"), pd.Index, str)
44+
if TYPE_CHECKING_INVALID_USAGE:
45+
_13 = f * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
46+
_14 = c * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
47+
_15 = s * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
48+
_16 = d * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
49+
50+
51+
def test_mul_py_sequence(left: "pd.Index[str]") -> None:
52+
"""Test pd.Index[str] * Python native sequences"""
53+
b, i, f, c = [True, False, True], [2, 3, 5], [1.0, 2.0, 3.0], [1j, 1j, 4j]
54+
s = [datetime(2025, 9, d) for d in (27, 28, 29)]
55+
d = [timedelta(seconds=s + 1) for s in range(3)]
56+
57+
check(assert_type(left * b, "pd.Index[str]"), pd.Index, str)
58+
check(assert_type(left * i, "pd.Index[str]"), pd.Index, str)
59+
if TYPE_CHECKING_INVALID_USAGE:
60+
_03 = left * f # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
61+
_04 = left * c # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
62+
_05 = left * s # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
63+
_06 = left * d # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
64+
65+
check(assert_type(b * left, "pd.Index[str]"), pd.Index, str)
66+
check(assert_type(i * left, "pd.Index[str]"), pd.Index, str)
67+
if TYPE_CHECKING_INVALID_USAGE:
68+
_13 = f * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
69+
_14 = c * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
70+
_15 = s * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
71+
_16 = d * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
72+
73+
74+
def test_mul_numpy_array(left: "pd.Index[str]") -> None:
75+
"""Test pd.Index[str] * numpy arrays"""
76+
b = np.array([True, False, True], np.bool_)
77+
i = np.array([2, 3, 5], np.int64)
78+
f = np.array([1.0, 2.0, 3.0], np.float64)
79+
c = np.array([1.1j, 2.2j, 4.1j], np.complex128)
80+
s = np.array([np.datetime64(f"2025-09-{d}") for d in (27, 28, 29)], np.datetime64)
81+
d = np.array([np.timedelta64(s + 1, "s") for s in range(3)], np.timedelta64)
82+
83+
check(assert_type(left * b, "pd.Index[str]"), pd.Index, str)
84+
check(assert_type(left * i, "pd.Index[str]"), pd.Index, str)
85+
if TYPE_CHECKING_INVALID_USAGE:
86+
assert_type(left * f, Never)
87+
assert_type(left * c, Never)
88+
assert_type(left * s, Never)
89+
assert_type(left * d, Never)
90+
91+
# `numpy` typing gives the corresponding `ndarray`s in the static type
92+
# checking, where our `__rmul__` cannot override. At runtime, they return
93+
# `Index` with the correct element type.
94+
check(assert_type(b * left, "npt.NDArray[np.bool_]"), pd.Index, str)
95+
check(assert_type(i * left, "npt.NDArray[np.int64]"), pd.Index, str)
96+
if TYPE_CHECKING_INVALID_USAGE:
97+
assert_type(f * left, "npt.NDArray[np.float64]")
98+
assert_type(c * left, "npt.NDArray[np.complex128]")
99+
assert_type(s * left, Any)
100+
assert_type(d * left, "npt.NDArray[np.timedelta64]")
101+
102+
103+
def test_mul_pd_index(left: "pd.Index[str]") -> None:
104+
"""Test pd.Index[str] * pandas Indexes"""
105+
b = pd.Index([True, False, True])
106+
i = pd.Index([2, 3, 5])
107+
f = pd.Index([1.0, 2.0, 3.0])
108+
c = pd.Index([1.1j, 2.2j, 4.1j])
109+
s = pd.Index([datetime(2025, 9, d) for d in (27, 28, 29)])
110+
d = pd.Index([timedelta(seconds=s + 1) for s in range(3)])
111+
112+
check(assert_type(left * b, "pd.Index[str]"), pd.Index, str)
113+
check(assert_type(left * i, "pd.Index[str]"), pd.Index, str)
114+
if TYPE_CHECKING_INVALID_USAGE:
115+
_03 = left * f # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
116+
_04 = left * c # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
117+
_05 = left * s # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
118+
_06 = left * d # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
119+
120+
check(assert_type(b * left, "pd.Index[str]"), pd.Index, str)
121+
check(assert_type(i * left, "pd.Index[str]"), pd.Index, str)
122+
if TYPE_CHECKING_INVALID_USAGE:
123+
_13 = f * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
124+
_14 = c * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
125+
_15 = s * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]
126+
_16 = d * left # type: ignore[operator] # pyright: ignore[reportOperatorIssue]

0 commit comments

Comments
 (0)