1
1
from __future__ import annotations
2
2
3
3
from datetime import timedelta
4
+ import operator
4
5
from typing import (
5
6
TYPE_CHECKING ,
6
7
Iterator ,
65
66
from pandas .core .arrays import datetimelike as dtl
66
67
from pandas .core .arrays ._ranges import generate_regular_range
67
68
import pandas .core .common as com
68
- from pandas .core .construction import extract_array
69
+ from pandas .core .ops import roperator
69
70
from pandas .core .ops .common import unpack_zerodim_and_defer
70
71
71
72
if TYPE_CHECKING :
@@ -492,10 +493,11 @@ def __mul__(self, other) -> TimedeltaArray:
492
493
493
494
__rmul__ = __mul__
494
495
495
- @unpack_zerodim_and_defer ("__truediv__" )
496
- def __truediv__ (self , other ):
497
- # timedelta / X is well-defined for timedelta-like or numeric X
498
-
496
+ def _scalar_divlike_op (self , other , op ):
497
+ """
498
+ Shared logic for __truediv__, __rtruediv__, __floordiv__, __rfloordiv__
499
+ with scalar 'other'.
500
+ """
499
501
if isinstance (other , self ._recognized_scalars ):
500
502
other = Timedelta (other )
501
503
# mypy assumes that __new__ returns an instance of the class
@@ -507,31 +509,86 @@ def __truediv__(self, other):
507
509
return result
508
510
509
511
# otherwise, dispatch to Timedelta implementation
510
- return self ._ndarray / other
512
+ return op ( self ._ndarray , other )
511
513
512
- elif lib .is_scalar (other ):
513
- # assume it is numeric
514
- result = self ._ndarray / other
514
+ else :
515
+ # caller is responsible for checking lib.is_scalar(other)
516
+ # assume other is numeric, otherwise numpy will raise
517
+
518
+ if op in [roperator .rtruediv , roperator .rfloordiv ]:
519
+ raise TypeError (
520
+ f"Cannot divide { type (other ).__name__ } by { type (self ).__name__ } "
521
+ )
522
+
523
+ result = op (self ._ndarray , other )
515
524
freq = None
525
+
516
526
if self .freq is not None :
517
- # Tick division is not implemented, so operate on Timedelta
518
- freq = self .freq .delta / other
519
- freq = to_offset (freq )
527
+ # Note: freq gets division, not floor-division, even if op
528
+ # is floordiv.
529
+ freq = self .freq / other
530
+
531
+ # TODO: 2022-12-24 test_ufunc_coercions, test_tdi_ops_attributes
532
+ # get here for truediv, no tests for floordiv
533
+
534
+ if op is operator .floordiv :
535
+ if freq .nanos == 0 and self .freq .nanos != 0 :
536
+ # e.g. if self.freq is Nano(1) then dividing by 2
537
+ # rounds down to zero
538
+ # TODO: 2022-12-24 should implement the same check
539
+ # for truediv case
540
+ freq = None
541
+
520
542
return type (self )._simple_new (result , dtype = result .dtype , freq = freq )
521
543
544
+ def _cast_divlike_op (self , other ):
522
545
if not hasattr (other , "dtype" ):
523
546
# e.g. list, tuple
524
547
other = np .array (other )
525
548
526
549
if len (other ) != len (self ):
527
550
raise ValueError ("Cannot divide vectors with unequal lengths" )
551
+ return other
528
552
529
- if is_timedelta64_dtype (other .dtype ):
530
- # let numpy handle it
531
- return self ._ndarray / other
553
+ def _vector_divlike_op (self , other , op ) -> np .ndarray | TimedeltaArray :
554
+ """
555
+ Shared logic for __truediv__, __floordiv__, and their reversed versions
556
+ with timedelta64-dtype ndarray other.
557
+ """
558
+ # Let numpy handle it
559
+ result = op (self ._ndarray , np .asarray (other ))
532
560
533
- elif is_object_dtype (other .dtype ):
534
- other = extract_array (other , extract_numpy = True )
561
+ if (is_integer_dtype (other .dtype ) or is_float_dtype (other .dtype )) and op in [
562
+ operator .truediv ,
563
+ operator .floordiv ,
564
+ ]:
565
+ return type (self )._simple_new (result , dtype = result .dtype )
566
+
567
+ if op in [operator .floordiv , roperator .rfloordiv ]:
568
+ mask = self .isna () | isna (other )
569
+ if mask .any ():
570
+ result = result .astype (np .float64 )
571
+ np .putmask (result , mask , np .nan )
572
+
573
+ return result
574
+
575
+ @unpack_zerodim_and_defer ("__truediv__" )
576
+ def __truediv__ (self , other ):
577
+ # timedelta / X is well-defined for timedelta-like or numeric X
578
+ op = operator .truediv
579
+ if is_scalar (other ):
580
+ return self ._scalar_divlike_op (other , op )
581
+
582
+ other = self ._cast_divlike_op (other )
583
+ if (
584
+ is_timedelta64_dtype (other .dtype )
585
+ or is_integer_dtype (other .dtype )
586
+ or is_float_dtype (other .dtype )
587
+ ):
588
+ return self ._vector_divlike_op (other , op )
589
+
590
+ if is_object_dtype (other .dtype ):
591
+ other = np .asarray (other )
535
592
if self .ndim > 1 :
536
593
res_cols = [left / right for left , right in zip (self , other )]
537
594
res_cols2 = [x .reshape (1 , - 1 ) for x in res_cols ]
@@ -542,40 +599,18 @@ def __truediv__(self, other):
542
599
return result
543
600
544
601
else :
545
- result = self ._ndarray / other
546
- return type (self )._simple_new (result , dtype = result .dtype )
602
+ return NotImplemented
547
603
548
604
@unpack_zerodim_and_defer ("__rtruediv__" )
549
605
def __rtruediv__ (self , other ):
550
606
# X / timedelta is defined only for timedelta-like X
551
- if isinstance (other , self ._recognized_scalars ):
552
- other = Timedelta (other )
553
- # mypy assumes that __new__ returns an instance of the class
554
- # github.com/python/mypy/issues/1020
555
- if cast ("Timedelta | NaTType" , other ) is NaT :
556
- # specifically timedelta64-NaT
557
- result = np .empty (self .shape , dtype = np .float64 )
558
- result .fill (np .nan )
559
- return result
560
-
561
- # otherwise, dispatch to Timedelta implementation
562
- return other / self ._ndarray
563
-
564
- elif lib .is_scalar (other ):
565
- raise TypeError (
566
- f"Cannot divide { type (other ).__name__ } by { type (self ).__name__ } "
567
- )
568
-
569
- if not hasattr (other , "dtype" ):
570
- # e.g. list, tuple
571
- other = np .array (other )
572
-
573
- if len (other ) != len (self ):
574
- raise ValueError ("Cannot divide vectors with unequal lengths" )
607
+ op = roperator .rtruediv
608
+ if is_scalar (other ):
609
+ return self ._scalar_divlike_op (other , op )
575
610
611
+ other = self ._cast_divlike_op (other )
576
612
if is_timedelta64_dtype (other .dtype ):
577
- # let numpy handle it
578
- return other / self ._ndarray
613
+ return self ._vector_divlike_op (other , op )
579
614
580
615
elif is_object_dtype (other .dtype ):
581
616
# Note: unlike in __truediv__, we do not _need_ to do type
@@ -585,60 +620,24 @@ def __rtruediv__(self, other):
585
620
return np .array (result_list )
586
621
587
622
else :
588
- raise TypeError (
589
- f"Cannot divide { other .dtype } data by { type (self ).__name__ } "
590
- )
623
+ return NotImplemented
591
624
592
625
@unpack_zerodim_and_defer ("__floordiv__" )
593
626
def __floordiv__ (self , other ):
594
-
627
+ op = operator . floordiv
595
628
if is_scalar (other ):
596
- if isinstance (other , self ._recognized_scalars ):
597
- other = Timedelta (other )
598
- # mypy assumes that __new__ returns an instance of the class
599
- # github.com/python/mypy/issues/1020
600
- if cast ("Timedelta | NaTType" , other ) is NaT :
601
- # treat this specifically as timedelta-NaT
602
- result = np .empty (self .shape , dtype = np .float64 )
603
- result .fill (np .nan )
604
- return result
605
-
606
- # dispatch to Timedelta implementation
607
- return other .__rfloordiv__ (self ._ndarray )
608
-
609
- # at this point we should only have numeric scalars; anything
610
- # else will raise
611
- result = self ._ndarray // other
612
- freq = None
613
- if self .freq is not None :
614
- # Note: freq gets division, not floor-division
615
- freq = self .freq / other
616
- if freq .nanos == 0 and self .freq .nanos != 0 :
617
- # e.g. if self.freq is Nano(1) then dividing by 2
618
- # rounds down to zero
619
- freq = None
620
- return type (self )(result , freq = freq )
621
-
622
- if not hasattr (other , "dtype" ):
623
- # list, tuple
624
- other = np .array (other )
625
- if len (other ) != len (self ):
626
- raise ValueError ("Cannot divide with unequal lengths" )
629
+ return self ._scalar_divlike_op (other , op )
627
630
628
- if is_timedelta64_dtype (other .dtype ):
629
- other = type (self )(other )
630
-
631
- # numpy timedelta64 does not natively support floordiv, so operate
632
- # on the i8 values
633
- result = self .asi8 // other .asi8
634
- mask = self ._isnan | other ._isnan
635
- if mask .any ():
636
- result = result .astype (np .float64 )
637
- np .putmask (result , mask , np .nan )
638
- return result
631
+ other = self ._cast_divlike_op (other )
632
+ if (
633
+ is_timedelta64_dtype (other .dtype )
634
+ or is_integer_dtype (other .dtype )
635
+ or is_float_dtype (other .dtype )
636
+ ):
637
+ return self ._vector_divlike_op (other , op )
639
638
640
639
elif is_object_dtype (other .dtype ):
641
- other = extract_array (other , extract_numpy = True )
640
+ other = np . asarray (other )
642
641
if self .ndim > 1 :
643
642
res_cols = [left // right for left , right in zip (self , other )]
644
643
res_cols2 = [x .reshape (1 , - 1 ) for x in res_cols ]
@@ -649,61 +648,26 @@ def __floordiv__(self, other):
649
648
assert result .dtype == object
650
649
return result
651
650
652
- elif is_integer_dtype (other .dtype ) or is_float_dtype (other .dtype ):
653
- result = self ._ndarray // other
654
- return type (self )(result )
655
-
656
651
else :
657
- dtype = getattr (other , "dtype" , type (other ).__name__ )
658
- raise TypeError (f"Cannot divide { dtype } by { type (self ).__name__ } " )
652
+ return NotImplemented
659
653
660
654
@unpack_zerodim_and_defer ("__rfloordiv__" )
661
655
def __rfloordiv__ (self , other ):
662
-
656
+ op = roperator . rfloordiv
663
657
if is_scalar (other ):
664
- if isinstance (other , self ._recognized_scalars ):
665
- other = Timedelta (other )
666
- # mypy assumes that __new__ returns an instance of the class
667
- # github.com/python/mypy/issues/1020
668
- if cast ("Timedelta | NaTType" , other ) is NaT :
669
- # treat this specifically as timedelta-NaT
670
- result = np .empty (self .shape , dtype = np .float64 )
671
- result .fill (np .nan )
672
- return result
673
-
674
- # dispatch to Timedelta implementation
675
- return other .__floordiv__ (self ._ndarray )
676
-
677
- raise TypeError (
678
- f"Cannot divide { type (other ).__name__ } by { type (self ).__name__ } "
679
- )
680
-
681
- if not hasattr (other , "dtype" ):
682
- # list, tuple
683
- other = np .array (other )
684
-
685
- if len (other ) != len (self ):
686
- raise ValueError ("Cannot divide with unequal lengths" )
658
+ return self ._scalar_divlike_op (other , op )
687
659
660
+ other = self ._cast_divlike_op (other )
688
661
if is_timedelta64_dtype (other .dtype ):
689
- other = type (self )(other )
690
- # numpy timedelta64 does not natively support floordiv, so operate
691
- # on the i8 values
692
- result = other .asi8 // self .asi8
693
- mask = self ._isnan | other ._isnan
694
- if mask .any ():
695
- result = result .astype (np .float64 )
696
- np .putmask (result , mask , np .nan )
697
- return result
662
+ return self ._vector_divlike_op (other , op )
698
663
699
664
elif is_object_dtype (other .dtype ):
700
665
result_list = [other [n ] // self [n ] for n in range (len (self ))]
701
666
result = np .array (result_list )
702
667
return result
703
668
704
669
else :
705
- dtype = getattr (other , "dtype" , type (other ).__name__ )
706
- raise TypeError (f"Cannot divide { dtype } by { type (self ).__name__ } " )
670
+ return NotImplemented
707
671
708
672
@unpack_zerodim_and_defer ("__mod__" )
709
673
def __mod__ (self , other ):
0 commit comments