@@ -139,6 +139,23 @@ def _round_to_figures(n, d, figures):
139139 return sign , significand , exponent
140140
141141
142+ # Pattern for matching non-float-style format specifications.
143+ _GENERAL_FORMAT_SPECIFICATION_MATCHER = re .compile (r"""
144+ (?:
145+ (?P<fill>.)?
146+ (?P<align>[<>=^])
147+ )?
148+ (?P<sign>[-+ ]?)
149+ # Alt flag forces a slash and denominator in the output, even for
150+ # integer-valued Fraction objects.
151+ (?P<alt>\#)?
152+ # We don't implement the zeropad flag since there's no single obvious way
153+ # to interpret it.
154+ (?P<minimumwidth>0|[1-9][0-9]*)?
155+ (?P<thousands_sep>[,_])?
156+ """ , re .DOTALL | re .VERBOSE ).fullmatch
157+
158+
142159# Pattern for matching float-style format specifications;
143160# supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types.
144161_FLOAT_FORMAT_SPECIFICATION_MATCHER = re .compile (r"""
@@ -414,27 +431,42 @@ def __str__(self):
414431 else :
415432 return '%s/%s' % (self ._numerator , self ._denominator )
416433
417- def __format__ (self , format_spec , / ):
418- """Format this fraction according to the given format specification."""
419-
420- # Backwards compatiblility with existing formatting.
421- if not format_spec :
422- return str (self )
434+ def _format_general (self , match ):
435+ """Helper method for __format__.
423436
437+ Handles fill, alignment, signs, and thousands separators in the
438+ case of no presentation type.
439+ """
424440 # Validate and parse the format specifier.
425- match = _FLOAT_FORMAT_SPECIFICATION_MATCHER (format_spec )
426- if match is None :
427- raise ValueError (
428- f"Invalid format specifier { format_spec !r} "
429- f"for object of type { type (self ).__name__ !r} "
430- )
431- elif match ["align" ] is not None and match ["zeropad" ] is not None :
432- # Avoid the temptation to guess.
433- raise ValueError (
434- f"Invalid format specifier { format_spec !r} "
435- f"for object of type { type (self ).__name__ !r} ; "
436- "can't use explicit alignment when zero-padding"
437- )
441+ fill = match ["fill" ] or " "
442+ align = match ["align" ] or ">"
443+ pos_sign = "" if match ["sign" ] == "-" else match ["sign" ]
444+ alternate_form = bool (match ["alt" ])
445+ minimumwidth = int (match ["minimumwidth" ] or "0" )
446+ thousands_sep = match ["thousands_sep" ] or ''
447+
448+ # Determine the body and sign representation.
449+ n , d = self ._numerator , self ._denominator
450+ if d > 1 or alternate_form :
451+ body = f"{ abs (n ):{thousands_sep }} /{ d :{thousands_sep }} "
452+ else :
453+ body = f"{ abs (n ):{thousands_sep }} "
454+ sign = '-' if n < 0 else pos_sign
455+
456+ # Pad with fill character if necessary and return.
457+ padding = fill * (minimumwidth - len (sign ) - len (body ))
458+ if align == ">" :
459+ return padding + sign + body
460+ elif align == "<" :
461+ return sign + body + padding
462+ elif align == "^" :
463+ half = len (padding ) // 2
464+ return padding [:half ] + sign + body + padding [half :]
465+ else : # align == "="
466+ return sign + padding + body
467+
468+ def _format_float_style (self , match ):
469+ """Helper method for __format__; handles float presentation types."""
438470 fill = match ["fill" ] or " "
439471 align = match ["align" ] or ">"
440472 pos_sign = "" if match ["sign" ] == "-" else match ["sign" ]
@@ -449,6 +481,9 @@ def __format__(self, format_spec, /):
449481 trim_point = not alternate_form
450482 exponent_indicator = "E" if presentation_type in "EFG" else "e"
451483
484+ if align == '=' and fill == '0' :
485+ zeropad = True
486+
452487 # Round to get the digits we need, figure out where to place the point,
453488 # and decide whether to use scientific notation. 'point_pos' is the
454489 # relative to the _end_ of the digit string: that is, it's the number
@@ -530,7 +565,25 @@ def __format__(self, format_spec, /):
530565 else : # align == "="
531566 return sign + padding + body
532567
533- def _operator_fallbacks (monomorphic_operator , fallback_operator ):
568+ def __format__ (self , format_spec , / ):
569+ """Format this fraction according to the given format specification."""
570+
571+ if match := _GENERAL_FORMAT_SPECIFICATION_MATCHER (format_spec ):
572+ return self ._format_general (match )
573+
574+ if match := _FLOAT_FORMAT_SPECIFICATION_MATCHER (format_spec ):
575+ # Refuse the temptation to guess if both alignment _and_
576+ # zero padding are specified.
577+ if match ["align" ] is None or match ["zeropad" ] is None :
578+ return self ._format_float_style (match )
579+
580+ raise ValueError (
581+ f"Invalid format specifier { format_spec !r} "
582+ f"for object of type { type (self ).__name__ !r} "
583+ )
584+
585+ def _operator_fallbacks (monomorphic_operator , fallback_operator ,
586+ handle_complex = True ):
534587 """Generates forward and reverse operators given a purely-rational
535588 operator and a function from the operator module.
536589
@@ -617,7 +670,7 @@ def forward(a, b):
617670 return monomorphic_operator (a , Fraction (b ))
618671 elif isinstance (b , float ):
619672 return fallback_operator (float (a ), b )
620- elif isinstance (b , complex ):
673+ elif handle_complex and isinstance (b , complex ):
621674 return fallback_operator (complex (a ), b )
622675 else :
623676 return NotImplemented
@@ -630,7 +683,7 @@ def reverse(b, a):
630683 return monomorphic_operator (Fraction (a ), b )
631684 elif isinstance (a , numbers .Real ):
632685 return fallback_operator (float (a ), float (b ))
633- elif isinstance (a , numbers .Complex ):
686+ elif handle_complex and isinstance (a , numbers .Complex ):
634687 return fallback_operator (complex (a ), complex (b ))
635688 else :
636689 return NotImplemented
@@ -781,22 +834,22 @@ def _floordiv(a, b):
781834 """a // b"""
782835 return (a .numerator * b .denominator ) // (a .denominator * b .numerator )
783836
784- __floordiv__ , __rfloordiv__ = _operator_fallbacks (_floordiv , operator .floordiv )
837+ __floordiv__ , __rfloordiv__ = _operator_fallbacks (_floordiv , operator .floordiv , False )
785838
786839 def _divmod (a , b ):
787840 """(a // b, a % b)"""
788841 da , db = a .denominator , b .denominator
789842 div , n_mod = divmod (a .numerator * db , da * b .numerator )
790843 return div , Fraction (n_mod , da * db )
791844
792- __divmod__ , __rdivmod__ = _operator_fallbacks (_divmod , divmod )
845+ __divmod__ , __rdivmod__ = _operator_fallbacks (_divmod , divmod , False )
793846
794847 def _mod (a , b ):
795848 """a % b"""
796849 da , db = a .denominator , b .denominator
797850 return Fraction ((a .numerator * db ) % (b .numerator * da ), da * db )
798851
799- __mod__ , __rmod__ = _operator_fallbacks (_mod , operator .mod )
852+ __mod__ , __rmod__ = _operator_fallbacks (_mod , operator .mod , False )
800853
801854 def __pow__ (a , b ):
802855 """a ** b
@@ -825,8 +878,10 @@ def __pow__(a, b):
825878 # A fractional power will generally produce an
826879 # irrational number.
827880 return float (a ) ** float (b )
828- else :
881+ elif isinstance ( b , ( float , complex )) :
829882 return float (a ) ** b
883+ else :
884+ return NotImplemented
830885
831886 def __rpow__ (b , a ):
832887 """a ** b"""
0 commit comments