Skip to content

Commit 09d693f

Browse files
authored
REF: de-duplicate methods across nullable EA subclasses (#45380)
1 parent ae5b2b7 commit 09d693f

File tree

5 files changed

+119
-191
lines changed

5 files changed

+119
-191
lines changed

pandas/core/arrays/boolean.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -324,15 +324,6 @@ def __init__(self, values: np.ndarray, mask: np.ndarray, copy: bool = False):
324324
def dtype(self) -> BooleanDtype:
325325
return self._dtype
326326

327-
@classmethod
328-
def _from_sequence(
329-
cls, scalars, *, dtype: Dtype | None = None, copy: bool = False
330-
) -> BooleanArray:
331-
if dtype:
332-
assert dtype == "boolean"
333-
values, mask = coerce_to_array(scalars, copy=copy)
334-
return BooleanArray(values, mask)
335-
336327
@classmethod
337328
def _from_sequence_of_strings(
338329
cls,
@@ -361,8 +352,13 @@ def map_string(s):
361352

362353
_HANDLED_TYPES = (np.ndarray, numbers.Number, bool, np.bool_)
363354

364-
def _coerce_to_array(self, value) -> tuple[np.ndarray, np.ndarray]:
365-
return coerce_to_array(value)
355+
@classmethod
356+
def _coerce_to_array(
357+
cls, value, *, dtype: DtypeObj, copy: bool = False
358+
) -> tuple[np.ndarray, np.ndarray]:
359+
if dtype:
360+
assert dtype == "boolean"
361+
return coerce_to_array(value, copy=copy)
366362

367363
@overload
368364
def astype(self, dtype: npt.DTypeLike, copy: bool = ...) -> np.ndarray:

pandas/core/arrays/floating.py

Lines changed: 6 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,26 @@
11
from __future__ import annotations
22

3-
from typing import overload
4-
53
import numpy as np
64

75
from pandas._libs import (
86
lib,
97
missing as libmissing,
108
)
11-
from pandas._typing import (
12-
ArrayLike,
13-
AstypeArg,
14-
DtypeObj,
15-
npt,
16-
)
9+
from pandas._typing import DtypeObj
1710
from pandas.util._decorators import cache_readonly
1811

19-
from pandas.core.dtypes.astype import astype_nansafe
2012
from pandas.core.dtypes.common import (
2113
is_bool_dtype,
22-
is_datetime64_dtype,
2314
is_float_dtype,
2415
is_integer_dtype,
2516
is_object_dtype,
26-
pandas_dtype,
27-
)
28-
from pandas.core.dtypes.dtypes import (
29-
ExtensionDtype,
30-
register_extension_dtype,
3117
)
18+
from pandas.core.dtypes.dtypes import register_extension_dtype
3219

33-
from pandas.core.arrays import ExtensionArray
3420
from pandas.core.arrays.numeric import (
3521
NumericArray,
3622
NumericDtype,
3723
)
38-
from pandas.core.tools.numeric import to_numeric
3924

4025

4126
class FloatingDtype(NumericDtype):
@@ -260,79 +245,10 @@ def __init__(self, values: np.ndarray, mask: np.ndarray, copy: bool = False):
260245
super().__init__(values, mask, copy=copy)
261246

262247
@classmethod
263-
def _from_sequence(
264-
cls, scalars, *, dtype=None, copy: bool = False
265-
) -> FloatingArray:
266-
values, mask = coerce_to_array(scalars, dtype=dtype, copy=copy)
267-
return FloatingArray(values, mask)
268-
269-
@classmethod
270-
def _from_sequence_of_strings(
271-
cls, strings, *, dtype=None, copy: bool = False
272-
) -> FloatingArray:
273-
scalars = to_numeric(strings, errors="raise")
274-
return cls._from_sequence(scalars, dtype=dtype, copy=copy)
275-
276-
def _coerce_to_array(self, value) -> tuple[np.ndarray, np.ndarray]:
277-
return coerce_to_array(value, dtype=self.dtype)
278-
279-
@overload
280-
def astype(self, dtype: npt.DTypeLike, copy: bool = ...) -> np.ndarray:
281-
...
282-
283-
@overload
284-
def astype(self, dtype: ExtensionDtype, copy: bool = ...) -> ExtensionArray:
285-
...
286-
287-
@overload
288-
def astype(self, dtype: AstypeArg, copy: bool = ...) -> ArrayLike:
289-
...
290-
291-
def astype(self, dtype: AstypeArg, copy: bool = True) -> ArrayLike:
292-
"""
293-
Cast to a NumPy array or ExtensionArray with 'dtype'.
294-
295-
Parameters
296-
----------
297-
dtype : str or dtype
298-
Typecode or data-type to which the array is cast.
299-
copy : bool, default True
300-
Whether to copy the data, even if not necessary. If False,
301-
a copy is made only if the old dtype does not match the
302-
new dtype.
303-
304-
Returns
305-
-------
306-
ndarray or ExtensionArray
307-
NumPy ndarray, or BooleanArray, IntegerArray or FloatingArray with
308-
'dtype' for its dtype.
309-
310-
Raises
311-
------
312-
TypeError
313-
if incompatible type with an FloatingDtype, equivalent of same_kind
314-
casting
315-
"""
316-
dtype = pandas_dtype(dtype)
317-
318-
if isinstance(dtype, ExtensionDtype):
319-
return super().astype(dtype, copy=copy)
320-
321-
# coerce
322-
if is_float_dtype(dtype):
323-
# In astype, we consider dtype=float to also mean na_value=np.nan
324-
kwargs = {"na_value": np.nan}
325-
elif is_datetime64_dtype(dtype):
326-
# error: Dict entry 0 has incompatible type "str": "datetime64"; expected
327-
# "str": "float"
328-
kwargs = {"na_value": np.datetime64("NaT")} # type: ignore[dict-item]
329-
else:
330-
kwargs = {}
331-
332-
# error: Argument 2 to "to_numpy" of "BaseMaskedArray" has incompatible
333-
# type "**Dict[str, float]"; expected "bool"
334-
data = self.to_numpy(dtype=dtype, **kwargs) # type: ignore[arg-type]
335-
return astype_nansafe(data, dtype, copy=False)
248+
def _coerce_to_array(
249+
cls, value, *, dtype: DtypeObj, copy: bool = False
250+
) -> tuple[np.ndarray, np.ndarray]:
251+
return coerce_to_array(value, dtype=dtype, copy=copy)
336252

337253
def _values_for_argsort(self) -> np.ndarray:
338254
return self._data

pandas/core/arrays/integer.py

Lines changed: 6 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,28 @@
11
from __future__ import annotations
22

3-
from typing import overload
4-
53
import numpy as np
64

75
from pandas._libs import (
86
lib,
97
missing as libmissing,
108
)
11-
from pandas._typing import (
12-
ArrayLike,
13-
AstypeArg,
14-
Dtype,
15-
DtypeObj,
16-
npt,
17-
)
9+
from pandas._typing import DtypeObj
1810
from pandas.util._decorators import cache_readonly
1911

20-
from pandas.core.dtypes.base import (
21-
ExtensionDtype,
22-
register_extension_dtype,
23-
)
12+
from pandas.core.dtypes.base import register_extension_dtype
2413
from pandas.core.dtypes.common import (
2514
is_bool_dtype,
26-
is_datetime64_dtype,
2715
is_float_dtype,
2816
is_integer_dtype,
2917
is_object_dtype,
3018
is_string_dtype,
31-
pandas_dtype,
3219
)
3320

34-
from pandas.core.arrays import ExtensionArray
3521
from pandas.core.arrays.masked import BaseMaskedDtype
3622
from pandas.core.arrays.numeric import (
3723
NumericArray,
3824
NumericDtype,
3925
)
40-
from pandas.core.tools.numeric import to_numeric
4126

4227

4328
class _IntegerDtype(NumericDtype):
@@ -319,75 +304,10 @@ def __init__(self, values: np.ndarray, mask: np.ndarray, copy: bool = False):
319304
super().__init__(values, mask, copy=copy)
320305

321306
@classmethod
322-
def _from_sequence(
323-
cls, scalars, *, dtype: Dtype | None = None, copy: bool = False
324-
) -> IntegerArray:
325-
values, mask = coerce_to_array(scalars, dtype=dtype, copy=copy)
326-
return IntegerArray(values, mask)
327-
328-
@classmethod
329-
def _from_sequence_of_strings(
330-
cls, strings, *, dtype: Dtype | None = None, copy: bool = False
331-
) -> IntegerArray:
332-
scalars = to_numeric(strings, errors="raise")
333-
return cls._from_sequence(scalars, dtype=dtype, copy=copy)
334-
335-
def _coerce_to_array(self, value) -> tuple[np.ndarray, np.ndarray]:
336-
return coerce_to_array(value, dtype=self.dtype)
337-
338-
@overload
339-
def astype(self, dtype: npt.DTypeLike, copy: bool = ...) -> np.ndarray:
340-
...
341-
342-
@overload
343-
def astype(self, dtype: ExtensionDtype, copy: bool = ...) -> ExtensionArray:
344-
...
345-
346-
@overload
347-
def astype(self, dtype: AstypeArg, copy: bool = ...) -> ArrayLike:
348-
...
349-
350-
def astype(self, dtype: AstypeArg, copy: bool = True) -> ArrayLike:
351-
"""
352-
Cast to a NumPy array or ExtensionArray with 'dtype'.
353-
354-
Parameters
355-
----------
356-
dtype : str or dtype
357-
Typecode or data-type to which the array is cast.
358-
copy : bool, default True
359-
Whether to copy the data, even if not necessary. If False,
360-
a copy is made only if the old dtype does not match the
361-
new dtype.
362-
363-
Returns
364-
-------
365-
ndarray or ExtensionArray
366-
NumPy ndarray, BooleanArray or IntegerArray with 'dtype' for its dtype.
367-
368-
Raises
369-
------
370-
TypeError
371-
if incompatible type with an IntegerDtype, equivalent of same_kind
372-
casting
373-
"""
374-
dtype = pandas_dtype(dtype)
375-
376-
if isinstance(dtype, ExtensionDtype):
377-
return super().astype(dtype, copy=copy)
378-
379-
na_value: float | np.datetime64 | lib.NoDefault
380-
381-
# coerce
382-
if is_float_dtype(dtype):
383-
# In astype, we consider dtype=float to also mean na_value=np.nan
384-
na_value = np.nan
385-
elif is_datetime64_dtype(dtype):
386-
na_value = np.datetime64("NaT")
387-
else:
388-
na_value = lib.no_default
389-
390-
return self.to_numpy(dtype=dtype, na_value=na_value, copy=False)
307+
def _coerce_to_array(
308+
cls, value, *, dtype: DtypeObj, copy: bool = False
309+
) -> tuple[np.ndarray, np.ndarray]:
310+
return coerce_to_array(value, dtype=dtype, copy=copy)
391311

392312
def _values_for_argsort(self) -> np.ndarray:
393313
"""

pandas/core/arrays/masked.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from pandas._typing import (
2121
ArrayLike,
2222
AstypeArg,
23+
DtypeObj,
2324
NpDtype,
2425
PositionalIndexer,
2526
Scalar,
@@ -160,6 +161,13 @@ def __init__(self, values: np.ndarray, mask: np.ndarray, copy: bool = False):
160161
self._data = values
161162
self._mask = mask
162163

164+
@classmethod
165+
def _from_sequence(
166+
cls: type[BaseMaskedArrayT], scalars, *, dtype=None, copy: bool = False
167+
) -> BaseMaskedArrayT:
168+
values, mask = cls._coerce_to_array(scalars, dtype=dtype, copy=copy)
169+
return cls(values, mask)
170+
163171
@property
164172
def dtype(self) -> BaseMaskedDtype:
165173
raise AbstractMethodError(self)
@@ -219,14 +227,17 @@ def fillna(
219227
new_values = self.copy()
220228
return new_values
221229

222-
def _coerce_to_array(self, values) -> tuple[np.ndarray, np.ndarray]:
223-
raise AbstractMethodError(self)
230+
@classmethod
231+
def _coerce_to_array(
232+
cls, values, *, dtype: DtypeObj, copy: bool = False
233+
) -> tuple[np.ndarray, np.ndarray]:
234+
raise AbstractMethodError(cls)
224235

225236
def __setitem__(self, key, value) -> None:
226237
_is_scalar = is_scalar(value)
227238
if _is_scalar:
228239
value = [value]
229-
value, mask = self._coerce_to_array(value)
240+
value, mask = self._coerce_to_array(value, dtype=self.dtype)
230241

231242
if _is_scalar:
232243
value = value[0]
@@ -806,7 +817,10 @@ def _quantile(
806817
except TypeError:
807818
# GH#42626: not able to safely cast Int64
808819
# for floating point output
809-
out = np.asarray(res, dtype=np.float64)
820+
# error: Incompatible types in assignment (expression has type
821+
# "ndarray[Any, dtype[floating[_64Bit]]]", variable has type
822+
# "BaseMaskedArrayT")
823+
out = np.asarray(res, dtype=np.float64) # type: ignore[assignment]
810824
return out
811825

812826
def _reduce(self, name: str, *, skipna: bool = True, **kwargs):

0 commit comments

Comments
 (0)