Skip to content

Commit 83683e5

Browse files
committed
Merge remote-tracking branch 'upstream/main' into natype-arithemtic
2 parents 8c1eced + 928df7b commit 83683e5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2584
-670
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repos:
1111
hooks:
1212
- id: isort
1313
- repo: https://github.com/astral-sh/ruff-pre-commit
14-
rev: v0.12.3
14+
rev: v0.12.10
1515
hooks:
1616
- id: ruff-check
1717
args: [

docs/philosophy.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,43 @@ The type `TimestampSeries` is the result of creating a series from `pd.to_dateti
6161
the type `TimedeltaSeries` is the result of subtracting two `TimestampSeries` as well as
6262
the result of `pd.to_timedelta()`.
6363

64+
### Generic Series have restricted arithmetic
65+
66+
Consider the following Series from a DataFrame:
67+
68+
```python
69+
import pandas as pd
70+
from typing_extensions import reveal_type
71+
from typing import TYPE_CHECKING, cast
72+
73+
if TYPE_CHECKING:
74+
from pandas.core.series import TimestampSeries # noqa: F401
75+
76+
77+
frame = pd.DataFrame({"timestamp": [pd.Timestamp(2025, 8, 26)], "tag": ["one"], "value": [1.0]})
78+
values = frame["value"]
79+
reveal_type(values) # type checker: Series[Any], runtime: Series
80+
new_values = values + 2
81+
82+
timestamps = frame["timestamp"]
83+
reveal_type(timestamps) # type checker: Series[Any], runtime: Series
84+
reveal_type(timestamps - pd.Timestamp(2025, 7, 12)) # type checker: Unknown and error, runtime: Series
85+
reveal_type(cast("TimestampSeries", timestamps) - pd.Timestamp(2025, 7, 12)) # type checker: TimedeltaSeries, runtime: Series
86+
87+
tags = frame["tag"]
88+
reveal_type("suffix" + tags) # type checker: Never, runtime: Series
89+
```
90+
91+
Since they are taken from a DataFrame, all three of them, `values`, `timestamps`
92+
and `tags`, are recognized by type checkers as `Series[Any]`. The code snippet
93+
runs fine at runtime. In the stub for type checking, however, we restrict
94+
generic Series to perform arithmetic operations only with numeric types, and
95+
give `Series[Any]` for the results. For `Timedelta`, `Timestamp`, `str`, etc.,
96+
arithmetic is restricted to `Series[Any]` and the result is either undefined,
97+
showing `Unknown` and errors, or `Never`. Users are encouraged to cast such
98+
generic Series to ones with concrete types, so that type checkers can provide
99+
meaningful results.
100+
64101
### Interval is Generic
65102

66103
A pandas `Interval` can be a time interval, an interval of integers, or an interval of

pandas-stubs/_libs/tslibs/period.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class Period(PeriodMixin):
102102
@overload
103103
def __eq__(self, other: Period) -> bool: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
104104
@overload
105-
def __eq__(self, other: PeriodIndex) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
105+
def __eq__(self, other: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
106106
@overload
107107
def __eq__(self, other: PeriodSeries) -> Series[bool]: ... # type: ignore[overload-overlap]
108108
@overload
@@ -154,7 +154,7 @@ class Period(PeriodMixin):
154154
@overload
155155
def __ne__(self, other: Period) -> bool: ... # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
156156
@overload
157-
def __ne__(self, other: PeriodIndex) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
157+
def __ne__(self, other: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
158158
@overload
159159
def __ne__(self, other: PeriodSeries) -> Series[bool]: ... # type: ignore[overload-overlap]
160160
@overload

pandas-stubs/_libs/tslibs/timedeltas.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ class Timedelta(timedelta):
311311
@overload
312312
def __eq__(self, other: TimedeltaSeries | Series[pd.Timedelta]) -> Series[bool]: ... # type: ignore[overload-overlap]
313313
@overload
314-
def __eq__(self, other: TimedeltaIndex) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
314+
def __eq__(self, other: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
315315
@overload
316316
def __eq__( # type: ignore[overload-overlap]
317317
self, other: np_ndarray[ShapeT, np.timedelta64]
@@ -324,7 +324,7 @@ class Timedelta(timedelta):
324324
@overload
325325
def __ne__(self, other: TimedeltaSeries | Series[pd.Timedelta]) -> Series[bool]: ... # type: ignore[overload-overlap]
326326
@overload
327-
def __ne__(self, other: TimedeltaIndex) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
327+
def __ne__(self, other: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
328328
@overload
329329
def __ne__( # type: ignore[overload-overlap]
330330
self, other: np_ndarray[ShapeT, np.timedelta64]

pandas-stubs/_libs/tslibs/timestamps.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ from typing import (
1919
import numpy as np
2020
from pandas import (
2121
DatetimeIndex,
22-
Index,
2322
TimedeltaIndex,
2423
)
24+
from pandas.core.indexes.base import Index
2525
from pandas.core.series import (
2626
Series,
2727
TimedeltaSeries,

pandas-stubs/_typing.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,8 @@ MaskType: TypeAlias = Series[bool] | np_ndarray_bool | list[bool]
845845

846846
# Scratch types for generics
847847

848+
T_INT = TypeVar("T_INT", bound=int)
849+
T_COMPLEX = TypeVar("T_COMPLEX", bound=complex)
848850
SeriesDType: TypeAlias = (
849851
str
850852
| bytes

pandas-stubs/_version.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ from typing import Literal
22

33
version_json: str = ...
44

5-
_stub_version: Literal["2.3.0.250703"]
5+
_stub_version: Literal["2.3.2.250827"]

pandas-stubs/core/arraylike.pyi

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ class OpsMixin:
1919
def __rxor__(self, other: Any) -> Self: ...
2020
# -------------------------------------------------------------
2121
# Arithmetic Methods
22-
def __add__(self, other: Any) -> Self: ...
23-
def __radd__(self, other: Any) -> Self: ...
24-
def __sub__(self, other: Any) -> Self: ...
25-
def __rsub__(self, other: Any) -> Self: ...
2622
def __mul__(self, other: Any) -> Self: ...
2723
def __rmul__(self, other: Any) -> Self: ...
2824
# Handled by subclasses that specify only the valid values

pandas-stubs/core/base.pyi

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from collections.abc import (
22
Hashable,
33
Iterator,
4+
Sequence,
45
)
56
from typing import (
67
Any,
78
Generic,
89
Literal,
10+
TypeAlias,
911
final,
1012
overload,
1113
)
@@ -22,18 +24,26 @@ from typing_extensions import Self
2224

2325
from pandas._typing import (
2426
S1,
27+
ArrayLike,
2528
AxisIndex,
2629
DropKeep,
2730
DTypeLike,
2831
GenericT,
2932
GenericT_co,
3033
NDFrameT,
3134
Scalar,
35+
SequenceNotStr,
3236
SupportsDType,
3337
np_1darray,
38+
np_ndarray_anyint,
39+
np_ndarray_bool,
40+
np_ndarray_complex,
41+
np_ndarray_float,
3442
)
3543
from pandas.util._decorators import cache_readonly
3644

45+
_ListLike: TypeAlias = ArrayLike | dict[str, np.ndarray] | SequenceNotStr[S1]
46+
3747
class NoNewAttributesMixin:
3848
def __setattr__(self, key: str, value: Any) -> None: ...
3949

@@ -51,7 +61,7 @@ class IndexOpsMixin(OpsMixin, Generic[S1, GenericT_co]):
5161
@property
5262
def T(self) -> Self: ...
5363
@property
54-
def shape(self) -> tuple: ...
64+
def shape(self) -> tuple[int, ...]: ...
5565
@property
5666
def ndim(self) -> int: ...
5767
def item(self) -> S1: ...
@@ -67,41 +77,45 @@ class IndexOpsMixin(OpsMixin, Generic[S1, GenericT_co]):
6777
dtype: None = None,
6878
copy: bool = False,
6979
na_value: Scalar = ...,
70-
**kwargs,
80+
**kwargs: Any,
7181
) -> np_1darray[GenericT_co]: ...
7282
@overload
7383
def to_numpy(
7484
self,
7585
dtype: np.dtype[GenericT] | SupportsDType[GenericT] | type[GenericT],
7686
copy: bool = False,
7787
na_value: Scalar = ...,
78-
**kwargs,
88+
**kwargs: Any,
7989
) -> np_1darray[GenericT]: ...
8090
@overload
8191
def to_numpy(
8292
self,
8393
dtype: DTypeLike,
8494
copy: bool = False,
8595
na_value: Scalar = ...,
86-
**kwargs,
96+
**kwargs: Any,
8797
) -> np_1darray: ...
8898
@property
8999
def empty(self) -> bool: ...
90-
def max(self, axis=..., skipna: bool = ..., **kwargs): ...
91-
def min(self, axis=..., skipna: bool = ..., **kwargs): ...
100+
def max(
101+
self, axis: AxisIndex | None = ..., skipna: bool = ..., **kwargs: Any
102+
) -> S1: ...
103+
def min(
104+
self, axis: AxisIndex | None = ..., skipna: bool = ..., **kwargs: Any
105+
) -> S1: ...
92106
def argmax(
93107
self,
94108
axis: AxisIndex | None = ...,
95109
skipna: bool = True,
96-
*args,
97-
**kwargs,
110+
*args: Any,
111+
**kwargs: Any,
98112
) -> np.int64: ...
99113
def argmin(
100114
self,
101115
axis: AxisIndex | None = ...,
102116
skipna: bool = True,
103-
*args,
104-
**kwargs,
117+
*args: Any,
118+
**kwargs: Any,
105119
) -> np.int64: ...
106120
def tolist(self) -> list[S1]: ...
107121
def to_list(self) -> list[S1]: ...
@@ -114,7 +128,7 @@ class IndexOpsMixin(OpsMixin, Generic[S1, GenericT_co]):
114128
normalize: Literal[False] = ...,
115129
sort: bool = ...,
116130
ascending: bool = ...,
117-
bins=...,
131+
bins: int | None = ...,
118132
dropna: bool = ...,
119133
) -> Series[int]: ...
120134
@overload
@@ -123,7 +137,7 @@ class IndexOpsMixin(OpsMixin, Generic[S1, GenericT_co]):
123137
normalize: Literal[True],
124138
sort: bool = ...,
125139
ascending: bool = ...,
126-
bins=...,
140+
bins: int | None = ...,
127141
dropna: bool = ...,
128142
) -> Series[float]: ...
129143
def nunique(self, dropna: bool = True) -> int: ...
@@ -136,7 +150,29 @@ class IndexOpsMixin(OpsMixin, Generic[S1, GenericT_co]):
136150
def factorize(
137151
self, sort: bool = False, use_na_sentinel: bool = True
138152
) -> tuple[np_1darray, np_1darray | Index | Categorical]: ...
153+
@overload
154+
def searchsorted(
155+
self,
156+
value: _ListLike,
157+
side: Literal["left", "right"] = ...,
158+
sorter: _ListLike | None = ...,
159+
) -> np_1darray[np.intp]: ...
160+
@overload
139161
def searchsorted(
140-
self, value, side: Literal["left", "right"] = ..., sorter=...
141-
) -> int | list[int]: ...
162+
self,
163+
value: Scalar,
164+
side: Literal["left", "right"] = ...,
165+
sorter: _ListLike | None = ...,
166+
) -> np.intp: ...
142167
def drop_duplicates(self, *, keep: DropKeep = ...) -> Self: ...
168+
169+
NumListLike: TypeAlias = (
170+
ExtensionArray
171+
| np_ndarray_bool
172+
| np_ndarray_anyint
173+
| np_ndarray_float
174+
| np_ndarray_complex
175+
| dict[str, np.ndarray]
176+
| Sequence[complex]
177+
| IndexOpsMixin[complex]
178+
)

pandas-stubs/core/frame.pyi

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ from pandas.core.arraylike import OpsMixin
3939
from pandas.core.generic import NDFrame
4040
from pandas.core.groupby.generic import DataFrameGroupBy
4141
from pandas.core.indexers import BaseIndexer
42-
from pandas.core.indexes.base import (
43-
Index,
44-
UnknownIndex,
45-
)
42+
from pandas.core.indexes.base import Index
4643
from pandas.core.indexes.category import CategoricalIndex
4744
from pandas.core.indexes.datetimes import DatetimeIndex
4845
from pandas.core.indexes.interval import IntervalIndex
@@ -1761,9 +1758,7 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
17611758
@property
17621759
def iloc(self) -> _iLocIndexerFrame[Self]: ...
17631760
@property
1764-
# mypy complains if we use Index[Any] instead of UnknownIndex here, even though
1765-
# the latter is aliased to the former ¯\_(ツ)_/¯.
1766-
def index(self) -> UnknownIndex: ...
1761+
def index(self) -> Index: ...
17671762
@index.setter
17681763
def index(self, idx: Index) -> None: ...
17691764
@property
@@ -1777,13 +1772,38 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
17771772
# methods
17781773
@final
17791774
def abs(self) -> Self: ...
1775+
def __add__(self, other: Any) -> Self: ...
17801776
def add(
17811777
self,
17821778
other: num | ListLike | DataFrame,
17831779
axis: Axis | None = "columns",
17841780
level: Level | None = None,
17851781
fill_value: float | None = None,
17861782
) -> Self: ...
1783+
def __radd__(self, other: Any) -> Self: ...
1784+
def radd(
1785+
self,
1786+
other,
1787+
axis: Axis = "columns",
1788+
level: Level | None = None,
1789+
fill_value: float | None = None,
1790+
) -> Self: ...
1791+
def __sub__(self, other: Any) -> Self: ...
1792+
def sub(
1793+
self,
1794+
other: num | ListLike | DataFrame,
1795+
axis: Axis | None = ...,
1796+
level: Level | None = ...,
1797+
fill_value: float | None = None,
1798+
) -> Self: ...
1799+
def __rsub__(self, other: Any) -> Self: ...
1800+
def rsub(
1801+
self,
1802+
other,
1803+
axis: Axis = ...,
1804+
level: Level | None = ...,
1805+
fill_value: float | None = None,
1806+
) -> Self: ...
17871807
@final
17881808
def add_prefix(self, prefix: _str, axis: Axis | None = None) -> Self: ...
17891809
@final
@@ -2227,13 +2247,6 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
22272247
min_count: int = 0,
22282248
**kwargs: Any,
22292249
) -> Series: ...
2230-
def radd(
2231-
self,
2232-
other,
2233-
axis: Axis = "columns",
2234-
level: Level | None = None,
2235-
fill_value: float | None = None,
2236-
) -> Self: ...
22372250
@final
22382251
def rank(
22392252
self,
@@ -2356,13 +2369,6 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
23562369
level: Level | None = ...,
23572370
fill_value: float | None = None,
23582371
) -> Self: ...
2359-
def rsub(
2360-
self,
2361-
other,
2362-
axis: Axis = ...,
2363-
level: Level | None = ...,
2364-
fill_value: float | None = None,
2365-
) -> Self: ...
23662372
def rtruediv(
23672373
self,
23682374
other,
@@ -2408,20 +2414,6 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
24082414
numeric_only: _bool = False,
24092415
**kwargs: Any,
24102416
) -> Series: ...
2411-
def sub(
2412-
self,
2413-
other: num | ListLike | DataFrame,
2414-
axis: Axis | None = ...,
2415-
level: Level | None = ...,
2416-
fill_value: float | None = None,
2417-
) -> Self: ...
2418-
def subtract(
2419-
self,
2420-
other: num | ListLike | DataFrame,
2421-
axis: Axis | None = ...,
2422-
level: Level | None = ...,
2423-
fill_value: float | None = None,
2424-
) -> Self: ...
24252417
def sum(
24262418
self,
24272419
axis: Axis = 0,

0 commit comments

Comments
 (0)