@@ -334,7 +334,19 @@ def __sub__(self, other: Self) -> Self:
334334 difference ._base_value = self ._base_value - other ._base_value
335335 return difference
336336
337- def __mul__ (self , percent : Percentage ) -> Self :
337+ @overload
338+ def __mul__ (self , scalar : float , / ) -> Self :
339+ """Scale this quantity by a scalar.
340+
341+ Args:
342+ scalar: The scalar by which to scale this quantity.
343+
344+ Returns:
345+ The scaled quantity.
346+ """
347+
348+ @overload
349+ def __mul__ (self , percent : Percentage , / ) -> Self :
338350 """Scale this quantity by a percentage.
339351
340352 Args:
@@ -343,12 +355,23 @@ def __mul__(self, percent: Percentage) -> Self:
343355 Returns:
344356 The scaled quantity.
345357 """
346- if not isinstance (percent , Percentage ):
347- return NotImplemented
348358
349- product = type (self ).__new__ (type (self ))
350- product ._base_value = self ._base_value * percent .as_fraction ()
351- return product
359+ def __mul__ (self , value : float | Percentage , / ) -> Self :
360+ """Scale this quantity by a scalar or percentage.
361+
362+ Args:
363+ value: The scalar or percentage by which to scale this quantity.
364+
365+ Returns:
366+ The scaled quantity.
367+ """
368+ match value :
369+ case float ():
370+ return type (self )._new (self ._base_value * value )
371+ case Percentage ():
372+ return type (self )._new (self ._base_value * value .as_fraction ())
373+ case _:
374+ return NotImplemented
352375
353376 def __gt__ (self , other : Self ) -> bool :
354377 """Return whether this quantity is greater than another.
@@ -583,19 +606,38 @@ def as_megawatts(self) -> float:
583606 """
584607 return self ._base_value / 1e6
585608
586- @overload # type: ignore
587- def __mul__ (self , other : Percentage ) -> Self :
609+ # We need the ignore here because otherwise mypy will give this error:
610+ # > Overloaded operator methods can't have wider argument types in overrides
611+ # The problem seems to be when the other type implements an **incompatible**
612+ # __rmul__ method, which is not the case here, so we should be safe.
613+ # Please see this example:
614+ # https://github.com/python/mypy/blob/c26f1297d4f19d2d1124a30efc97caebb8c28616/test-data/unit/check-overloading.test#L4738C1-L4769C55
615+ # And a discussion in a mypy issue here:
616+ # https://github.com/python/mypy/issues/4985#issuecomment-389692396
617+ @overload # type: ignore[override]
618+ def __mul__ (self , scalar : float , / ) -> Self :
619+ """Scale this power by a scalar.
620+
621+ Args:
622+ scalar: The scalar by which to scale this power.
623+
624+ Returns:
625+ The scaled power.
626+ """
627+
628+ @overload
629+ def __mul__ (self , percent : Percentage , / ) -> Self :
588630 """Scale this power by a percentage.
589631
590632 Args:
591- other : The percentage by which to scale this power.
633+ percent : The percentage by which to scale this power.
592634
593635 Returns:
594636 The scaled power.
595637 """
596638
597639 @overload
598- def __mul__ (self , other : timedelta ) -> Energy :
640+ def __mul__ (self , other : timedelta , / ) -> Energy :
599641 """Return an energy from multiplying this power by the given duration.
600642
601643 Args:
@@ -605,23 +647,22 @@ def __mul__(self, other: timedelta) -> Energy:
605647 The calculated energy.
606648 """
607649
608- def __mul__ (self , other : Percentage | timedelta ) -> Self | Energy :
650+ def __mul__ (self , other : float | Percentage | timedelta , / ) -> Self | Energy :
609651 """Return a power or energy from multiplying this power by the given value.
610652
611653 Args:
612- other: The percentage or duration to multiply by.
654+ other: The scalar, percentage or duration to multiply by.
613655
614656 Returns:
615657 A power or energy.
616658 """
617- if isinstance (other , Percentage ):
618- return super ().__mul__ (other )
619- if isinstance (other , timedelta ):
620- return Energy .from_watt_hours (
621- self ._base_value * other .total_seconds () / 3600.0
622- )
623-
624- return NotImplemented
659+ match other :
660+ case float () | Percentage ():
661+ return super ().__mul__ (other )
662+ case timedelta ():
663+ return Energy ._new (self ._base_value * other .total_seconds () / 3600.0 )
664+ case _:
665+ return NotImplemented
625666
626667 @overload
627668 def __truediv__ (self , other : Current ) -> Voltage :
@@ -725,19 +766,31 @@ def as_milliamperes(self) -> float:
725766 """
726767 return self ._base_value * 1e3
727768
728- @overload # type: ignore
729- def __mul__ (self , other : Percentage ) -> Self :
769+ # See comment for Power.__mul__ for why we need the ignore here.
770+ @overload # type: ignore[override]
771+ def __mul__ (self , scalar : float , / ) -> Self :
772+ """Scale this current by a scalar.
773+
774+ Args:
775+ scalar: The scalar by which to scale this current.
776+
777+ Returns:
778+ The scaled current.
779+ """
780+
781+ @overload
782+ def __mul__ (self , percent : Percentage , / ) -> Self :
730783 """Scale this current by a percentage.
731784
732785 Args:
733- other : The percentage by which to scale this current.
786+ percent : The percentage by which to scale this current.
734787
735788 Returns:
736789 The scaled current.
737790 """
738791
739792 @overload
740- def __mul__ (self , other : Voltage ) -> Power :
793+ def __mul__ (self , other : Voltage , / ) -> Power :
741794 """Multiply the current by a voltage to get a power.
742795
743796 Args:
@@ -747,21 +800,22 @@ def __mul__(self, other: Voltage) -> Power:
747800 The calculated power.
748801 """
749802
750- def __mul__ (self , other : Percentage | Voltage ) -> Self | Power :
803+ def __mul__ (self , other : float | Percentage | Voltage , / ) -> Self | Power :
751804 """Return a current or power from multiplying this current by the given value.
752805
753806 Args:
754- other: The percentage or voltage to multiply by.
807+ other: The scalar, percentage or voltage to multiply by.
755808
756809 Returns:
757810 A current or power.
758811 """
759- if isinstance (other , Percentage ):
760- return super ().__mul__ (other )
761- if isinstance (other , Voltage ):
762- return Power .from_watts (self ._base_value * other ._base_value )
763-
764- return NotImplemented
812+ match other :
813+ case float () | Percentage ():
814+ return super ().__mul__ (other )
815+ case Voltage ():
816+ return Power ._new (self ._base_value * other ._base_value )
817+ case _:
818+ return NotImplemented
765819
766820
767821class Voltage (
@@ -840,19 +894,31 @@ def as_kilovolts(self) -> float:
840894 """
841895 return self ._base_value / 1e3
842896
843- @overload # type: ignore
844- def __mul__ (self , other : Percentage ) -> Self :
897+ # See comment for Power.__mul__ for why we need the ignore here.
898+ @overload # type: ignore[override]
899+ def __mul__ (self , scalar : float , / ) -> Self :
900+ """Scale this voltage by a scalar.
901+
902+ Args:
903+ scalar: The scalar by which to scale this voltage.
904+
905+ Returns:
906+ The scaled voltage.
907+ """
908+
909+ @overload
910+ def __mul__ (self , percent : Percentage , / ) -> Self :
845911 """Scale this voltage by a percentage.
846912
847913 Args:
848- other : The percentage by which to scale this voltage.
914+ percent : The percentage by which to scale this voltage.
849915
850916 Returns:
851917 The scaled voltage.
852918 """
853919
854920 @overload
855- def __mul__ (self , other : Current ) -> Power :
921+ def __mul__ (self , other : Current , / ) -> Power :
856922 """Multiply the voltage by the current to get the power.
857923
858924 Args:
@@ -862,21 +928,22 @@ def __mul__(self, other: Current) -> Power:
862928 The calculated power.
863929 """
864930
865- def __mul__ (self , other : Percentage | Current ) -> Self | Power :
931+ def __mul__ (self , other : float | Percentage | Current , / ) -> Self | Power :
866932 """Return a voltage or power from multiplying this voltage by the given value.
867933
868934 Args:
869- other: The percentage or current to multiply by.
935+ other: The scalar, percentage or current to multiply by.
870936
871937 Returns:
872938 The calculated voltage or power.
873939 """
874- if isinstance (other , Percentage ):
875- return super ().__mul__ (other )
876- if isinstance (other , Current ):
877- return Power .from_watts (self ._base_value * other ._base_value )
878-
879- return NotImplemented
940+ match other :
941+ case float () | Percentage ():
942+ return super ().__mul__ (other )
943+ case Current ():
944+ return Power ._new (self ._base_value * other ._base_value )
945+ case _:
946+ return NotImplemented
880947
881948
882949class Energy (
@@ -959,6 +1026,23 @@ def as_megawatt_hours(self) -> float:
9591026 """
9601027 return self ._base_value / 1e6
9611028
1029+ def __mul__ (self , other : float | Percentage ) -> Self :
1030+ """Scale this energy by a percentage.
1031+
1032+ Args:
1033+ other: The percentage by which to scale this energy.
1034+
1035+ Returns:
1036+ The scaled energy.
1037+ """
1038+ match other :
1039+ case float ():
1040+ return self ._new (self ._base_value * other )
1041+ case Percentage ():
1042+ return self ._new (self ._base_value * other .as_fraction ())
1043+ case _:
1044+ return NotImplemented
1045+
9621046 @overload
9631047 def __truediv__ (self , other : timedelta ) -> Power :
9641048 """Return a power from dividing this energy by the given duration.
0 commit comments