Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions python/pyarrow/array.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,19 @@ cdef PandasOptions _convert_pandas_options(dict options):
return result


def _compute_binary_op(func_name, left, right):
"""
Helper for arithmetic/bitwise dunder methods.

Only use for ops that can't raise ArrowTypeError as it
subclasses TypeError, so will get swallowed.
"""
try:
return _pc().call_function(func_name, [left, right])
except TypeError:
return NotImplemented


cdef class Array(_PandasConvertible):
"""
The base class for all Arrow arrays.
Expand Down Expand Up @@ -2440,47 +2453,47 @@ cdef class Array(_PandasConvertible):

def __add__(self, object other):
self._assert_cpu()
return _pc().call_function('add_checked', [self, other])
return _compute_binary_op('add_checked', self, other)

def __truediv__(self, object other):
self._assert_cpu()
return _pc().call_function('divide_checked', [self, other])
return _compute_binary_op('divide_checked', self, other)

def __mul__(self, object other):
self._assert_cpu()
return _pc().call_function('multiply_checked', [self, other])
return _compute_binary_op('multiply_checked', self, other)

def __neg__(self):
self._assert_cpu()
return _pc().call_function('negate_checked', [self])

def __pow__(self, object other):
self._assert_cpu()
return _pc().call_function('power_checked', [self, other])
return _compute_binary_op('power_checked', self, other)

def __sub__(self, object other):
self._assert_cpu()
return _pc().call_function('subtract_checked', [self, other])
return _compute_binary_op('subtract_checked', self, other)

def __and__(self, object other):
self._assert_cpu()
return _pc().call_function('bit_wise_and', [self, other])
return _compute_binary_op('bit_wise_and', self, other)

def __or__(self, object other):
self._assert_cpu()
return _pc().call_function('bit_wise_or', [self, other])
return _compute_binary_op('bit_wise_or', self, other)

def __xor__(self, object other):
self._assert_cpu()
return _pc().call_function('bit_wise_xor', [self, other])
return _compute_binary_op('bit_wise_xor', self, other)

def __lshift__(self, object other):
self._assert_cpu()
return _pc().call_function('shift_left_checked', [self, other])
return _compute_binary_op('shift_left_checked', self, other)

def __rshift__(self, object other):
self._assert_cpu()
return _pc().call_function('shift_right_checked', [self, other])
return _compute_binary_op('shift_right_checked', self, other)


cdef _array_like_to_pandas(obj, options, types_mapper):
Expand Down
20 changes: 10 additions & 10 deletions python/pyarrow/scalar.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -199,37 +199,37 @@ 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 _compute_binary_op('add_checked', self, other)

def __truediv__(self, object other):
return _pc().call_function('divide_checked', [self, other])
return _compute_binary_op('divide_checked', self, other)

def __mul__(self, object other):
return _pc().call_function('multiply_checked', [self, other])
return _compute_binary_op('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 _compute_binary_op('power_checked', self, other)

def __sub__(self, object other):
return _pc().call_function('subtract_checked', [self, other])
return _compute_binary_op('subtract_checked', self, other)

def __and__(self, object other):
return _pc().call_function('bit_wise_and', [self, other])
return _compute_binary_op('bit_wise_and', self, other)

def __or__(self, object other):
return _pc().call_function('bit_wise_or', [self, other])
return _compute_binary_op('bit_wise_or', self, other)

def __xor__(self, object other):
return _pc().call_function('bit_wise_xor', [self, other])
return _compute_binary_op('bit_wise_xor', self, other)

def __lshift__(self, object other):
return _pc().call_function('shift_left_checked', [self, other])
return _compute_binary_op('shift_left_checked', self, other)

def __rshift__(self, object other):
return _pc().call_function('shift_right_checked', [self, other])
return _compute_binary_op('shift_right_checked', self, other)


_NULL = NA = None
Expand Down
34 changes: 34 additions & 0 deletions python/pyarrow/tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import hypothesis as h
import hypothesis.strategies as st
import itertools
import operator
import pytest
import struct
import subprocess
Expand Down Expand Up @@ -4612,3 +4613,36 @@ def test_dictionary_uint64_index_to_pandas():
result = arr.to_pandas()
assert list(result.cat.categories) == ["a", "b"]
assert result.cat.codes.tolist() == [0, 1, -1, 0]


@pytest.mark.parametrize("op", [
operator.add,
operator.sub,
operator.mul,
operator.truediv,
operator.pow,
operator.and_,
operator.or_,
operator.xor,
operator.lshift,
operator.rshift,
])
def test_arithmetic_dunders_unknown_types(op):
# GH-49826
class MyObj:
def __radd__(self, other):
return "reflected"

__rsub__ = __rmul__ = __rtruediv__ = __rpow__ = __radd__
__rand__ = __ror__ = __rxor__ = __rlshift__ = __rrshift__ = __radd__

assert op(pa.array([1, 2, 3]), MyObj()) == "reflected"

with pytest.raises(TypeError, match="unsupported operand type"):
op(pa.array([1, 2, 3]), object())


def test_arithmetic_dunder_raises_arrow_invalid():
# GH-49826
with pytest.raises(pa.ArrowInvalid, match="divide by zero"):
pa.array([1, 2, 3]) / pa.scalar(0)
34 changes: 34 additions & 0 deletions python/pyarrow/tests/test_scalars.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import datetime
import decimal
import operator
import pytest
import weakref
from collections.abc import Sequence, Mapping
Expand Down Expand Up @@ -1051,3 +1052,36 @@ def test_dunders_checked_overflow():
scl ** scl
with pytest.raises(pa.ArrowInvalid, match=error_match):
scl * scl


@pytest.mark.parametrize("op", [
operator.add,
operator.sub,
operator.mul,
operator.truediv,
operator.pow,
operator.and_,
operator.or_,
operator.xor,
operator.lshift,
operator.rshift,
])
def test_arithmetic_dunders_unknown_types(op):
# GH-49826
class MyObj:
def __radd__(self, other):
return "reflected"

__rsub__ = __rmul__ = __rtruediv__ = __rpow__ = __radd__
__rand__ = __ror__ = __rxor__ = __rlshift__ = __rrshift__ = __radd__

assert op(pa.scalar(5), MyObj()) == "reflected"

with pytest.raises(TypeError, match="unsupported operand type"):
op(pa.scalar(1), object())


def test_arithmetic_dunder_raises_arrow_invalid():
# GH-49826
with pytest.raises(pa.ArrowInvalid, match="divide by zero"):
pa.scalar(1) / pa.scalar(0)