From 459b8aef50d6aeb18c552222dc04aa1dc7525cc3 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Tue, 21 Apr 2026 16:13:11 -0700 Subject: [PATCH] GH-49826: [Python] Scalar arithmetic dunders return NotImplemented on unknown operand types pyarrow 24.0.0 added Scalar.__add__/__sub__/__mul__/__truediv__/__pow__ (and bitwise ops) that unconditionally call pc.call_function. For operand types compute doesn't recognize, call_function raises TypeError -- Python's data model only triggers reflected-operator fallback when the forward operator returns NotImplemented, not when it raises. Any user class that used to handle `pyarrow.Scalar + obj` via __radd__ was therefore broken (GH-49826). Route every binary dunder through a small _binop_or_notimplemented helper that catches the TypeError from _pack_compute_args and returns NotImplemented instead. Unary __neg__/__abs__ are unaffected. Closes GH-49826. Signed-off-by: SAY-5 --- python/pyarrow/scalar.pxi | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/python/pyarrow/scalar.pxi b/python/pyarrow/scalar.pxi index a6377b2bb707..7d58d1bdb71c 100644 --- a/python/pyarrow/scalar.pxi +++ b/python/pyarrow/scalar.pxi @@ -199,37 +199,48 @@ cdef class Scalar(_Weakrefable): return _pc().call_function('abs_checked', [self]) def __add__(self, object other): - return _pc().call_function('add_checked', [self, other]) + return _binop_or_notimplemented('add_checked', self, other) def __truediv__(self, object other): - return _pc().call_function('divide_checked', [self, other]) + return _binop_or_notimplemented('divide_checked', self, other) def __mul__(self, object other): - return _pc().call_function('multiply_checked', [self, other]) + return _binop_or_notimplemented('multiply_checked', self, other) def __neg__(self): return _pc().call_function('negate_checked', [self]) def __pow__(self, object other): - return _pc().call_function('power_checked', [self, other]) + return _binop_or_notimplemented('power_checked', self, other) def __sub__(self, object other): - return _pc().call_function('subtract_checked', [self, other]) + return _binop_or_notimplemented('subtract_checked', self, other) def __and__(self, object other): - return _pc().call_function('bit_wise_and', [self, other]) + return _binop_or_notimplemented('bit_wise_and', self, other) def __or__(self, object other): - return _pc().call_function('bit_wise_or', [self, other]) + return _binop_or_notimplemented('bit_wise_or', self, other) def __xor__(self, object other): - return _pc().call_function('bit_wise_xor', [self, other]) + return _binop_or_notimplemented('bit_wise_xor', self, other) def __lshift__(self, object other): - return _pc().call_function('shift_left_checked', [self, other]) + return _binop_or_notimplemented('shift_left_checked', self, other) def __rshift__(self, object other): - return _pc().call_function('shift_right_checked', [self, other]) + return _binop_or_notimplemented('shift_right_checked', self, other) + + +def _binop_or_notimplemented(op_name, left, right): + # Scalar arithmetic dunders must return NotImplemented for argument types + # pyarrow.compute does not recognize so Python's reflected-operator + # fallback (__radd__ etc.) kicks in on user-defined classes. + # See GH-49826. + try: + return _pc().call_function(op_name, [left, right]) + except TypeError: + return NotImplemented _NULL = NA = None