22Functions for arithmetic and comparison operations on NumPy arrays and
33ExtensionArrays.
44"""
5- from datetime import timedelta
5+ import datetime
66from functools import partial
77import operator
88from typing import Any
99
1010import numpy as np
1111
1212from pandas ._libs import (
13+ NaT ,
1314 Timedelta ,
1415 Timestamp ,
1516 lib ,
1617 ops as libops ,
1718)
19+ from pandas ._libs .tslibs import BaseOffset
1820from pandas ._typing import (
1921 ArrayLike ,
2022 Shape ,
@@ -154,8 +156,14 @@ def _na_arithmetic_op(left, right, op, is_cmp: bool = False):
154156 ------
155157 TypeError : invalid operation
156158 """
159+ if isinstance (right , str ):
160+ # can never use numexpr
161+ func = op
162+ else :
163+ func = partial (expressions .evaluate , op )
164+
157165 try :
158- result = expressions . evaluate ( op , left , right )
166+ result = func ( left , right )
159167 except TypeError :
160168 if is_object_dtype (left ) or is_object_dtype (right ) and not is_cmp :
161169 # For object dtype, fallback to a masked operation (only operating
@@ -201,8 +209,13 @@ def arithmetic_op(left: ArrayLike, right: Any, op):
201209 # casts integer dtypes to timedelta64 when operating with timedelta64 - GH#22390)
202210 right = _maybe_upcast_for_op (right , left .shape )
203211
204- if should_extension_dispatch (left , right ) or isinstance (right , Timedelta ):
205- # Timedelta is included because numexpr will fail on it, see GH#31457
212+ if (
213+ should_extension_dispatch (left , right )
214+ or isinstance (right , (Timedelta , BaseOffset , Timestamp ))
215+ or right is NaT
216+ ):
217+ # Timedelta/Timestamp and other custom scalars are included in the check
218+ # because numexpr will fail on it, see GH#31457
206219 res_values = op (left , right )
207220 else :
208221 res_values = _na_arithmetic_op (left , right , op )
@@ -246,7 +259,10 @@ def comparison_op(left: ArrayLike, right: Any, op) -> ArrayLike:
246259 "Lengths must match to compare" , lvalues .shape , rvalues .shape
247260 )
248261
249- if should_extension_dispatch (lvalues , rvalues ):
262+ if should_extension_dispatch (lvalues , rvalues ) or (
263+ (isinstance (rvalues , (Timedelta , BaseOffset , Timestamp )) or right is NaT )
264+ and not is_object_dtype (lvalues .dtype )
265+ ):
250266 # Call the method on lvalues
251267 res_values = op (lvalues , rvalues )
252268
@@ -261,7 +277,7 @@ def comparison_op(left: ArrayLike, right: Any, op) -> ArrayLike:
261277 # GH#36377 going through the numexpr path would incorrectly raise
262278 return invalid_comparison (lvalues , rvalues , op )
263279
264- elif is_object_dtype (lvalues .dtype ):
280+ elif is_object_dtype (lvalues .dtype ) or isinstance ( rvalues , str ) :
265281 res_values = comp_method_OBJECT_ARRAY (op , lvalues , rvalues )
266282
267283 else :
@@ -438,11 +454,14 @@ def _maybe_upcast_for_op(obj, shape: Shape):
438454 Be careful to call this *after* determining the `name` attribute to be
439455 attached to the result of the arithmetic operation.
440456 """
441- if type (obj ) is timedelta :
457+ if type (obj ) is datetime . timedelta :
442458 # GH#22390 cast up to Timedelta to rely on Timedelta
443459 # implementation; otherwise operation against numeric-dtype
444460 # raises TypeError
445461 return Timedelta (obj )
462+ elif type (obj ) is datetime .datetime :
463+ # cast up to Timestamp to rely on Timestamp implementation, see Timedelta above
464+ return Timestamp (obj )
446465 elif isinstance (obj , np .datetime64 ):
447466 # GH#28080 numpy casts integer-dtype to datetime64 when doing
448467 # array[int] + datetime64, which we do not allow
0 commit comments