Skip to content

Commit 0a84cd0

Browse files
committed
Merge branch 'main' into hotfix/cmp0xff/gh718-drop-tss
2 parents 8a01e12 + 669a258 commit 0a84cd0

25 files changed

+841
-474
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
@@ -63,6 +63,43 @@ The type `Series[Timestamp]` is the result of creating a series from `pd.to_date
6363
the type `TimedeltaSeries` is the result of subtracting two `Series[Timestamp]` as well as
6464
the result of `pd.to_timedelta()`.
6565

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

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

pandas-stubs/_libs/tslibs/offsets.pyi

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,7 @@ class BaseOffset:
8989
@property
9090
def nanos(self) -> int: ...
9191

92-
class SingleConstructorOffset(BaseOffset):
93-
def __reduce__(self): ...
92+
class SingleConstructorOffset(BaseOffset): ...
9493

9594
class Tick(SingleConstructorOffset):
9695
def __init__(self, n: int = ..., normalize: bool = ...) -> None: ...

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
@@ -308,7 +308,7 @@ class Timedelta(timedelta):
308308
@overload
309309
def __eq__(self, other: TimedeltaSeries | Series[pd.Timedelta]) -> Series[bool]: ... # type: ignore[overload-overlap]
310310
@overload
311-
def __eq__(self, other: TimedeltaIndex) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
311+
def __eq__(self, other: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
312312
@overload
313313
def __eq__( # type: ignore[overload-overlap]
314314
self, other: np_ndarray[ShapeT, np.timedelta64]
@@ -321,7 +321,7 @@ class Timedelta(timedelta):
321321
@overload
322322
def __ne__(self, other: TimedeltaSeries | Series[pd.Timedelta]) -> Series[bool]: ... # type: ignore[overload-overlap]
323323
@overload
324-
def __ne__(self, other: TimedeltaIndex) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
324+
def __ne__(self, other: Index) -> np_1darray[np.bool]: ... # type: ignore[overload-overlap]
325325
@overload
326326
def __ne__( # type: ignore[overload-overlap]
327327
self, other: np_ndarray[ShapeT, np.timedelta64]

pandas-stubs/_libs/tslibs/timestamps.pyi

Lines changed: 1 addition & 5 deletions
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,
@@ -246,10 +246,6 @@ class Timestamp(datetime, SupportsIndex):
246246
@overload
247247
def __sub__(self, other: TimedeltaSeries) -> Series[Timestamp]: ...
248248
@overload
249-
def __sub__(self, other: Series[Never]) -> Series: ...
250-
@overload
251-
def __sub__(self, other: Series[Timestamp]) -> Series[Timedelta]: ...
252-
@overload
253249
def __sub__(
254250
self, other: np_ndarray[ShapeT, np.timedelta64]
255251
) -> np_ndarray[ShapeT, np.datetime64]: ...

pandas-stubs/_typing.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ IndexingInt: TypeAlias = (
893893
)
894894

895895
# AxesData is used for data for Index
896-
AxesData: TypeAlias = Mapping[S3, Any] | Axes | KeysView
896+
AxesData: TypeAlias = Mapping[S3, Any] | Axes | KeysView[S3]
897897

898898
# Any plain Python or numpy function
899899
Function: TypeAlias = np.ufunc | Callable[..., Any]

pandas-stubs/core/arraylike.pyi

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ class OpsMixin:
2121
# Arithmetic Methods
2222
def __add__(self, other: Any) -> Self: ...
2323
def __radd__(self, other: Any) -> Self: ...
24-
def __sub__(self, other: Any) -> Self: ...
25-
def __rsub__(self, other: Any) -> Self: ...
2624
def __mul__(self, other: Any) -> Self: ...
2725
def __rmul__(self, other: Any) -> Self: ...
2826
# Handled by subclasses that specify only the valid values

pandas-stubs/core/frame.pyi

Lines changed: 14 additions & 18 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
@@ -545,10 +542,16 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
545542
@classmethod
546543
def from_records(
547544
cls,
548-
data,
549-
index=...,
550-
exclude: SequenceNotStr[str] | None = None,
551-
columns: SequenceNotStr[str] | None = None,
545+
data: (
546+
np_2darray
547+
| Sequence[SequenceNotStr]
548+
| Sequence[Mapping[str, Any]]
549+
| Mapping[str, Any]
550+
| Mapping[str, SequenceNotStr[Any]]
551+
),
552+
index: str | SequenceNotStr[Hashable] | None = None,
553+
columns: ListLike | None = None,
554+
exclude: ListLike | None = None,
552555
coerce_float: bool = False,
553556
nrows: int | None = None,
554557
) -> Self: ...
@@ -1755,9 +1758,7 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
17551758
@property
17561759
def iloc(self) -> _iLocIndexerFrame[Self]: ...
17571760
@property
1758-
# mypy complains if we use Index[Any] instead of UnknownIndex here, even though
1759-
# the latter is aliased to the former ¯\_(ツ)_/¯.
1760-
def index(self) -> UnknownIndex: ...
1761+
def index(self) -> Index: ...
17611762
@index.setter
17621763
def index(self, idx: Index) -> None: ...
17631764
@property
@@ -2402,20 +2403,15 @@ class DataFrame(NDFrame, OpsMixin, _GetItemHack):
24022403
numeric_only: _bool = False,
24032404
**kwargs: Any,
24042405
) -> Series: ...
2406+
def __sub__(self, other: Any) -> Self: ...
2407+
def __rsub__(self, other: Any) -> Self: ...
24052408
def sub(
24062409
self,
24072410
other: num | ListLike | DataFrame,
24082411
axis: Axis | None = ...,
24092412
level: Level | None = ...,
24102413
fill_value: float | None = None,
24112414
) -> Self: ...
2412-
def subtract(
2413-
self,
2414-
other: num | ListLike | DataFrame,
2415-
axis: Axis | None = ...,
2416-
level: Level | None = ...,
2417-
fill_value: float | None = None,
2418-
) -> Self: ...
24192415
def sum(
24202416
self,
24212417
axis: Axis = 0,

pandas-stubs/core/indexes/base.pyi

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ from typing import (
1515
ClassVar,
1616
Generic,
1717
Literal,
18-
TypeAlias,
1918
final,
2019
overload,
2120
type_check_only,
@@ -50,6 +49,7 @@ from pandas._typing import (
5049
ArrayLike,
5150
AxesData,
5251
DropKeep,
52+
Dtype,
5353
DtypeArg,
5454
DTypeLike,
5555
DtypeObj,
@@ -89,7 +89,6 @@ class Index(IndexOpsMixin[S1]):
8989
copy: bool = ...,
9090
name: Hashable = ...,
9191
tupleize_cols: bool = ...,
92-
**kwargs,
9392
) -> Index[int]: ...
9493
@overload
9594
def __new__(
@@ -100,7 +99,6 @@ class Index(IndexOpsMixin[S1]):
10099
copy: bool = ...,
101100
name: Hashable = ...,
102101
tupleize_cols: bool = ...,
103-
**kwargs,
104102
) -> Index[int]: ...
105103
@overload
106104
def __new__(
@@ -111,7 +109,6 @@ class Index(IndexOpsMixin[S1]):
111109
copy: bool = ...,
112110
name: Hashable = ...,
113111
tupleize_cols: bool = ...,
114-
**kwargs,
115112
) -> Index[float]: ...
116113
@overload
117114
def __new__(
@@ -122,7 +119,6 @@ class Index(IndexOpsMixin[S1]):
122119
copy: bool = ...,
123120
name: Hashable = ...,
124121
tupleize_cols: bool = ...,
125-
**kwargs,
126122
) -> Index[float]: ...
127123
@overload
128124
def __new__(
@@ -137,7 +133,6 @@ class Index(IndexOpsMixin[S1]):
137133
copy: bool = ...,
138134
name: Hashable = ...,
139135
tupleize_cols: bool = ...,
140-
**kwargs,
141136
) -> Index[complex]: ...
142137
@overload
143138
def __new__(
@@ -148,7 +143,6 @@ class Index(IndexOpsMixin[S1]):
148143
copy: bool = ...,
149144
name: Hashable = ...,
150145
tupleize_cols: bool = ...,
151-
**kwargs,
152146
) -> Index[complex]: ...
153147
# special overloads with dedicated Index-subclasses
154148
@overload
@@ -160,7 +154,6 @@ class Index(IndexOpsMixin[S1]):
160154
copy: bool = ...,
161155
name: Hashable = ...,
162156
tupleize_cols: bool = ...,
163-
**kwargs,
164157
) -> DatetimeIndex: ...
165158
@overload
166159
def __new__(
@@ -171,7 +164,6 @@ class Index(IndexOpsMixin[S1]):
171164
copy: bool = ...,
172165
name: Hashable = ...,
173166
tupleize_cols: bool = ...,
174-
**kwargs,
175167
) -> DatetimeIndex: ...
176168
@overload
177169
def __new__(
@@ -182,7 +174,6 @@ class Index(IndexOpsMixin[S1]):
182174
copy: bool = ...,
183175
name: Hashable = ...,
184176
tupleize_cols: bool = ...,
185-
**kwargs,
186177
) -> PeriodIndex: ...
187178
@overload
188179
def __new__(
@@ -193,7 +184,6 @@ class Index(IndexOpsMixin[S1]):
193184
copy: bool = ...,
194185
name: Hashable = ...,
195186
tupleize_cols: bool = ...,
196-
**kwargs,
197187
) -> PeriodIndex: ...
198188
@overload
199189
def __new__(
@@ -204,7 +194,6 @@ class Index(IndexOpsMixin[S1]):
204194
copy: bool = ...,
205195
name: Hashable = ...,
206196
tupleize_cols: bool = ...,
207-
**kwargs,
208197
) -> TimedeltaIndex: ...
209198
@overload
210199
def __new__(
@@ -215,7 +204,6 @@ class Index(IndexOpsMixin[S1]):
215204
copy: bool = ...,
216205
name: Hashable = ...,
217206
tupleize_cols: bool = ...,
218-
**kwargs,
219207
) -> TimedeltaIndex: ...
220208
@overload
221209
def __new__(
@@ -226,7 +214,6 @@ class Index(IndexOpsMixin[S1]):
226214
copy: bool = ...,
227215
name: Hashable = ...,
228216
tupleize_cols: bool = ...,
229-
**kwargs,
230217
) -> IntervalIndex[Interval[_OrderableT]]: ...
231218
@overload
232219
def __new__(
@@ -237,7 +224,6 @@ class Index(IndexOpsMixin[S1]):
237224
copy: bool = ...,
238225
name: Hashable = ...,
239226
tupleize_cols: bool = ...,
240-
**kwargs,
241227
) -> IntervalIndex[Interval[Any]]: ...
242228
# generic overloads
243229
@overload
@@ -249,7 +235,6 @@ class Index(IndexOpsMixin[S1]):
249235
copy: bool = ...,
250236
name: Hashable = ...,
251237
tupleize_cols: bool = ...,
252-
**kwargs,
253238
) -> Self: ...
254239
@overload
255240
def __new__(
@@ -260,19 +245,17 @@ class Index(IndexOpsMixin[S1]):
260245
copy: bool = ...,
261246
name: Hashable = ...,
262247
tupleize_cols: bool = ...,
263-
**kwargs,
264248
) -> Self: ...
265249
# fallback overload
266250
@overload
267251
def __new__(
268252
cls,
269253
data: AxesData,
270254
*,
271-
dtype=...,
255+
dtype: Dtype = ...,
272256
copy: bool = ...,
273257
name: Hashable = ...,
274258
tupleize_cols: bool = ...,
275-
**kwargs,
276259
) -> Self: ...
277260
@property
278261
def str(
@@ -476,6 +459,8 @@ class Index(IndexOpsMixin[S1]):
476459
def __lt__(self, other: Self | S1) -> np_1darray[np.bool]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
477460
def __gt__(self, other: Self | S1) -> np_1darray[np.bool]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]
478461
# overwrite inherited methods from OpsMixin
462+
def __sub__(self, other: Any) -> Self: ...
463+
def __rsub__(self, other: Any) -> Self: ...
479464
@overload
480465
def __mul__(
481466
self: Index[int] | Index[float], other: timedelta
@@ -524,8 +509,6 @@ class Index(IndexOpsMixin[S1]):
524509
) -> Self: ...
525510
def infer_objects(self, copy: bool = True) -> Self: ...
526511

527-
UnknownIndex: TypeAlias = Index[Any]
528-
529512
@type_check_only
530513
class _IndexSubclassBase(Index[S1], Generic[S1, GenericT_co]):
531514
@overload

0 commit comments

Comments
 (0)