-
-
Notifications
You must be signed in to change notification settings - Fork 11.3k
TYP: Fix reverse operators typing for objects with __array__ and __array_ufunc__ #29591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…func__ Fixes numpy#29486 ## Description This PR addresses an issue with static typing of reverse operators in NumPy's `ndarray` class. When using objects that implement both `__array__` and `__array_ufunc__` (like pandas Series) in reverse operations with NumPy arrays, the static typing incorrectly reports the result as an `ndarray` instead of the appropriate type from the left-hand side object. The solution implements a Protocol-based approach that adds overloads to the reverse operators (`__radd__`, `__rsub__`, `__rmul__`, etc.) in the `ndarray` class. These overloads check if the left-hand side operand implements the corresponding forward operator and, if so, delegate to that operator, returning its result type. ## Changes 1. Added a `_CanAdd` Protocol in `numpy/_typing/_callable.pyi` 2. Added new overloads to reverse operators in `numpy/__init__.pyi` that use the Protocol 3. Added necessary imports for the new Protocol and TypeVar ## Testing The changes have been syntax-checked with `python -m compileall` and maintain backward compatibility with existing code. For a detailed explanation of the problem and solution, see ISSUE_29486_SOLUTION.md
This comment has been minimized.
This comment has been minimized.
numpy/__init__.pyi
Outdated
@@ -207,6 +208,7 @@ from typing import ( | |||
SupportsIndex, | |||
TypeAlias, | |||
TypedDict, | |||
TypeVar, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TypeVar
should be imported from typing_extensions
because we use the PEP 696 default
kwarg, which requires Python 3.13+
numpy/_typing/_callable.pyi
Outdated
@@ -42,6 +42,7 @@ from ._scalars import _BoolLike_co, _IntLike_co, _NumberLike_co | |||
|
|||
_T1 = TypeVar("_T1") | |||
_T2 = TypeVar("_T2") | |||
_T = TypeVar("_T") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see how you might want to use _CanAdd
for __radd__
, but it doesn't make much sense to also use that for other reflected binops like __rpow__
Co-authored-by: Joren Hammudoglu <jhammudoglu@gmail.com>
This comment has been minimized.
This comment has been minimized.
Thanks for this PR; it's always good to see interest in improving NumPy's type annotations. But keep in mind that every commit you push notifies us, and that running the CI isn't free. So please run the mypy tests locally first ( Also note that this is a very difficult problem to solve, and I'm still not sure what the right approach is, even though I do this for a living. Don't take this the wrong way, but based on your changes so far, it looks to me like you're not an expert in writing typing stubs. I'm a bit worried that the time that your generously investing in this might go to waste, due to its complexity. So perhaps it would be more efficient (and more fun) to work on a different numpy typing issue? Some options that come to mind are #29595, or annotating one of the untyped |
Diff from mypy_primer, showing the effect of this PR on type check results on a corpus of open source code: static-frame (https://github.com/static-frame/static-frame)
+ static_frame/core/rank.py:106: error: Unused "type: ignore" comment [unused-ignore]
xarray (https://github.com/pydata/xarray)
+ xarray/computation/weighted.py: note: In function "_weighted_quantile":
+ xarray/computation/weighted.py:333: error: Incompatible types in assignment (expression has type "int", variable has type "ndarray[tuple[Any, ...], dtype[Any]]") [assignment]
+ xarray/computation/weighted.py:337: error: Incompatible types in assignment (expression has type "float", variable has type "ndarray[tuple[Any, ...], dtype[Any]]") [assignment]
+ xarray/computation/weighted.py:341: error: Incompatible types in assignment (expression has type "float", variable has type "ndarray[tuple[Any, ...], dtype[Any]]") [assignment]
+ xarray/computation/weighted.py:343: error: Incompatible types in assignment (expression has type "float", variable has type "ndarray[tuple[Any, ...], dtype[Any]]") [assignment]
+ xarray/tests/test_namedarray.py: note: In member "test_real_and_imag" of class "TestNamedArray":
+ xarray/tests/test_namedarray.py:274: error: Incompatible types in assignment (expression has type "ndarray[tuple[int], dtype[float64]]", variable has type "ndarray[Any, dtype[complex128]]") [assignment]
+ xarray/tests/test_namedarray.py: note: In class "TestNamedArray":
+ xarray/tests/test_variable.py: note: In member "test_copy" of class "VariableSubclassobjects":
+ xarray/tests/test_variable.py:555: error: "Never" has no attribute "astype" [attr-defined]
+ xarray/tests/test_variable.py: note: At top level:
+ xarray/tests/test_duck_array_ops.py:892: error: Need type annotation for "expected" [var-annotated]
+ xarray/tests/test_duck_array_ops.py:908: error: Need type annotation for "expected2" [var-annotated]
+ xarray/tests/test_duck_array_ops.py: note: In function "test_datetime_to_numeric_potential_overflow":
+ xarray/tests/test_duck_array_ops.py:980: error: Need type annotation for "expected" [var-annotated]
+ xarray/tests/test_dataset.py: note: In member "test_constructor" of class "TestDataset":
+ xarray/tests/test_dataset.py:475: error: Need type annotation for "x1" [var-annotated]
+ xarray/tests/test_cftimeindex.py: note: In function "test_asi8":
+ xarray/tests/test_cftimeindex.py:1380: error: Need type annotation for "expected" [var-annotated]
+ xarray/tests/test_backends.py: note: In function "test_use_cftime_standard_calendar_default_in_range":
+ xarray/tests/test_backends.py:6639: error: Need type annotation for "decoded_x" [var-annotated]
+ xarray/tests/test_backends.py:6640: error: Need type annotation for "decoded_time" [var-annotated]
+ xarray/tests/test_backends.py: note: In function "test_use_cftime_false_standard_calendar_in_range":
+ xarray/tests/test_backends.py:6732: error: Need type annotation for "decoded_x" [var-annotated]
+ xarray/tests/test_backends.py:6733: error: Need type annotation for "decoded_time" [var-annotated]
arviz (https://github.com/arviz-devs/arviz)
+ arviz/stats/ecdf_utils.py:69: error: "Never" has no attribute "astype" [attr-defined]
+ examples/matplotlib/mpl_plot_hdi.py:15: error: Need type annotation for "y_data" [var-annotated]
+ examples/bokeh/bokeh_plot_hdi.py:12: error: Need type annotation for "y_data" [var-annotated]
optuna (https://github.com/optuna/optuna)
+ optuna/samplers/_tpe/_truncnorm.py:75: error: Incompatible return value type (got "float", expected "ndarray[tuple[Any, ...], dtype[Any]]") [return-value]
+ tests/hypervolume_tests/test_wfg.py:22: error: Need type annotation for "r" [var-annotated]
+ tests/hypervolume_tests/test_wfg.py:35: error: Need type annotation for "r" [var-annotated]
+ tests/hypervolume_tests/test_wfg.py:53: error: Need type annotation for "r" [var-annotated]
+ tests/hypervolume_tests/test_wfg.py:63: error: Need type annotation for "r" [var-annotated]
+ tests/hypervolume_tests/test_wfg.py:73: error: Need type annotation for "r" [var-annotated]
+ tests/hypervolume_tests/test_wfg.py:81: error: Need type annotation for "r" [var-annotated]
+ tests/hypervolume_tests/test_wfg.py:91: error: Need type annotation for "r" [var-annotated]
+ tests/hypervolume_tests/test_wfg.py:101: error: Need type annotation for "s" [var-annotated]
+ optuna/importance/_ped_anova/evaluator.py:43: error: Need type annotation for "loss_values" [var-annotated]
scipy (https://github.com/scipy/scipy)
+ scipy/io/matlab/tests/test_mio.py:46: error: Need type annotation for "theta" [var-annotated]
+ scipy/interpolate/_cubic.py:562: error: Unsupported target for indexed assignment ("Never") [index]
+ scipy/_lib/cobyqa/examples/powell2015.py:49: error: Need type annotation for "bub" [var-annotated]
pandas-stubs (https://github.com/pandas-dev/pandas-stubs)
+ tests/test_plotting.py:201: error: Unsupported left operand type for + ("Never") [operator]
+ tests/series/arithmetic/bool/test_sub.py:87: error: Expression is of type "Any", not "Never" [assert-type]
hydpy (https://github.com/hydpy-dev/hydpy)
+ hydpy/auxs/statstools.py:1053: error: Incompatible return value type (got "float", expected "ndarray[tuple[Any, ...], dtype[float64]]") [return-value]
pandas (https://github.com/pandas-dev/pandas)
+ pandas/core/dtypes/missing.py:590: error: Need type annotation for "taker" [var-annotated]
+ pandas/io/formats/format.py:1603: error: "Never" has no attribute "round" [attr-defined]
+ pandas/io/formats/format.py:1611: error: Need type annotation for "unique_pcts" [var-annotated]
+ pandas/io/formats/format.py:1614: error: Value of type "Never" is not indexable [index]
+ pandas/io/formats/format.py:1616: error: Value of type "Never" is not indexable [index]
+ pandas/core/indexing.py:1889: error: Need type annotation for "taker" [var-annotated]
+ pandas/core/internals/managers.py:2484: error: Need type annotation for "empty_arr" [var-annotated]
+ pandas/core/indexes/base.py:6171: error: Need type annotation for "no_matches" [var-annotated]
+ pandas/core/groupby/generic.py:1033: error: Need type annotation for "idchanges" [var-annotated]
+ pandas/core/reshape/reshape.py:996: error: Need type annotation for "idxs" [var-annotated]
colour (https://github.com/colour-science/colour)
+ colour/algebra/interpolation.py:308: error: Unsupported left operand type for + ("Never") [operator]
+ colour/algebra/interpolation.py:309: error: Unsupported left operand type for + ("Never") [operator]
+ colour/temperature/kang2002.py:177: error: Unsupported left operand type for - ("Never") [operator]
+ colour/temperature/kang2002.py:178: error: Unsupported left operand type for - ("Never") [operator]
+ colour/temperature/kang2002.py:179: error: Unsupported left operand type for - ("Never") [operator]
+ colour/models/yrg.py:119: error: Unsupported left operand type for - ("Never") [operator]
+ colour/models/yrg.py:120: error: Unsupported left operand type for + ("Never") [operator]
+ colour/models/rgb/transfer_functions/log.py:321: error: Redundant cast to "float" [redundant-cast]
+ colour/models/rgb/transfer_functions/dcdm.py:174: error: Need type annotation for "XYZ" [var-annotated]
+ colour/difference/delta_e.py:404: error: Need type annotation for "delta_theta" [var-annotated]
+ colour/contrast/barten1999.py:246: error: Need type annotation for "E" [var-annotated]
+ colour/colorimetry/yellowness.py:280: error: Need type annotation for "WI" [var-annotated]
+ colour/appearance/llab.py:555: error: Need type annotation for "z" [var-annotated]
+ colour/appearance/llab.py:559: error: Need type annotation for "a" [var-annotated]
+ colour/appearance/llab.py:560: error: Need type annotation for "b" [var-annotated]
+ colour/appearance/hunt.py:572: error: Incompatible return value type (got "floating[_16Bit] | floating[_32Bit] | float64", expected "ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]]") [return-value]
+ colour/appearance/hunt.py:603: error: Incompatible return value type (got "floating[_16Bit] | floating[_32Bit] | float64", expected "ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]]") [return-value]
+ colour/appearance/hunt.py:766: error: Incompatible return value type (got "float", expected "ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]]") [return-value]
+ colour/appearance/hunt.py:1187: error: Incompatible types in assignment (expression has type "floating[_16Bit] | float64 | floating[_32Bit]", variable has type "float") [assignment]
+ colour/appearance/hunt.py:1245: error: Incompatible return value type (got "floating[_16Bit] | float64 | floating[_32Bit]", expected "ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]]") [return-value]
+ colour/appearance/hke.py:226: error: Need type annotation for "theta_2" [var-annotated]
+ colour/appearance/hke.py:226: error: Need type annotation for "theta_3" [var-annotated]
+ colour/appearance/hke.py:226: error: Need type annotation for "theta_4" [var-annotated]
+ colour/models/rgb/transfer_functions/sony.py:330: error: Incompatible return value type (got "float", expected "ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]]") [return-value]
+ colour/appearance/rlab.py:280: error: Need type annotation for "LR" [var-annotated]
+ colour/colorimetry/luminance.py:206: error: Unsupported left operand type for - ("Never") [operator]
+ colour/models/hdr_cie_lab.py:228: error: Need type annotation for "a_hdr" [var-annotated]
+ colour/models/hdr_cie_lab.py:229: error: Need type annotation for "b_hdr" [var-annotated]
+ colour/models/cie_luv.py:139: error: Unsupported left operand type for - ("Never") [operator]
+ colour/models/cie_luv.py:140: error: Unsupported left operand type for - ("Never") [operator]
+ colour/models/cie_luv.py:216: error: Need type annotation for "b" [var-annotated]
+ colour/models/cie_lab.py:116: error: Need type annotation for "a" [var-annotated]
+ colour/models/cie_lab.py:117: error: Need type annotation for "b" [var-annotated]
+ colour/colorimetry/illuminants.py:360: error: Unsupported left operand type for + ("Never") [operator]
+ colour/temperature/mccamy1992.py:81: error: Unsupported left operand type for + ("Never") [operator]
+ colour/phenomena/rayleigh.py:222: error: Incompatible return value type (got "floating[_16Bit] | floating[_32Bit] | float64", expected "ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]]") [return-value]
+ colour/phenomena/rayleigh.py:368: error: Unsupported left operand type for + ("Never") [operator]
+ colour/phenomena/rayleigh.py:406: error: Unsupported left operand type for + ("Never") [operator]
+ colour/phenomena/rayleigh.py:480: error: Incompatible return value type (got "float", expected "ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]]") [return-value]
+ colour/appearance/scam.py:270: error: Need type annotation for "z" [var-annotated]
+ colour/appearance/scam.py:426: error: Need type annotation for "z" [var-annotated]
+ colour/appearance/scam.py:440: error: Need type annotation for "I" [var-annotated]
+ colour/appearance/ciecam02.py:625: error: Need type annotation for "N_bb" [var-annotated]
+ colour/appearance/ciecam02.py:625: error: Need type annotation for "N_cb" [var-annotated]
- colour/notation/munsell.py:1955: error: Incompatible types in assignment (expression has type "int", variable has type "ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]]") [assignment]
+ colour/notation/munsell.py:440: error: Need type annotation for "V" [var-annotated]
+ colour/appearance/hellwig2022.py:384: error: Need type annotation for "Q_HK" [var-annotated]
+ colour/appearance/hellwig2022.py:762: error: Need type annotation for "_2_h" [var-annotated]
+ colour/appearance/hellwig2022.py:763: error: Need type annotation for "_3_h" [var-annotated]
+ colour/appearance/hellwig2022.py:764: error: Need type annotation for "_4_h" [var-annotated]
+ colour/appearance/hellwig2022.py:961: error: Need type annotation for "P_p_1" [var-annotated]
+ colour/appearance/ciecam16.py:689: error: Unsupported left operand type for / ("Never") [operator]
|
Fixes #29486
Description
This PR addresses an issue with static typing of reverse operators in NumPy's
ndarray
class. When using objects that implement both__array__
and__array_ufunc__
(like pandas Series) in reverse operations with NumPy arrays, the static typing incorrectly reports the result as anndarray
instead of the appropriate type from the left-hand side object.The solution implements a Protocol-based approach that adds overloads to the reverse operators (
__radd__
,__rsub__
,__rmul__
, etc.) in thendarray
class. These overloads check if the left-hand side operand implements the corresponding forward operator and, if so, delegate to that operator, returning its result type.