From 3e849d75f400569cbf3c29c356061c788284b71e Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 7 Jul 2025 11:16:31 +0300 Subject: [PATCH] gh-87790: support thousands separators for formatting fractional part of Fraction (#132204) --- Lib/fractions.py | 10 +++++++++- Lib/test/test_fractions.py | 13 +++++++++++++ .../2025-04-07-10-20-16.gh-issue-87790.X2SjJe.rst | 2 ++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-04-07-10-20-16.gh-issue-87790.X2SjJe.rst diff --git a/Lib/fractions.py b/Lib/fractions.py index a8c67068522..c1b12e7a1c0 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -170,7 +170,11 @@ _FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r""" (?P0(?=[0-9]))? (?P[0-9]+)? (?P[,_])? - (?:\.(?P[0-9]+))? + (?:\. + (?=[,_0-9]) # lookahead for digit or separator + (?P[0-9]+)? + (?P[,_])? + )? (?P[eEfFgG%]) """, re.DOTALL | re.VERBOSE).fullmatch @@ -499,6 +503,7 @@ class Fraction(numbers.Rational): minimumwidth = int(match["minimumwidth"] or "0") thousands_sep = match["thousands_sep"] precision = int(match["precision"] or "6") + frac_sep = match["frac_separators"] or "" presentation_type = match["presentation_type"] trim_zeros = presentation_type in "gG" and not alternate_form trim_point = not alternate_form @@ -555,6 +560,9 @@ class Fraction(numbers.Rational): if trim_zeros: frac_part = frac_part.rstrip("0") separator = "" if trim_point and not frac_part else "." + if frac_sep: + frac_part = frac_sep.join(frac_part[pos:pos + 3] + for pos in range(0, len(frac_part), 3)) trailing = separator + frac_part + suffix # Do zero padding if required. diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 1875a2f529c..cf42b86358d 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1322,6 +1322,8 @@ class FractionTest(unittest.TestCase): # Thousands separators (F('1234567.123456'), ',.5e', '1.23457e+06'), (F('123.123456'), '012_.2e', '0_001.23e+02'), + # Thousands separators for fractional part (or for integral too) + (F('1234567.123456'), '.5_e', '1.234_57e+06'), # z flag is legal, but never makes a difference to the output (F(-1, 7**100), 'z.6e', '-3.091690e-85'), ] @@ -1447,6 +1449,12 @@ class FractionTest(unittest.TestCase): (F('1234567'), ',.2f', '1,234,567.00'), (F('12345678'), ',.2f', '12,345,678.00'), (F('12345678'), ',f', '12,345,678.000000'), + # Thousands separators for fractional part (or for integral too) + (F('123456.789123123'), '._f', '123456.789_123'), + (F('123456.789123123'), '.7_f', '123456.789_123_1'), + (F('123456.789123123'), '.9_f', '123456.789_123_123'), + (F('123456.789123123'), '.,f', '123456.789,123'), + (F('123456.789123123'), '_.,f', '123_456.789,123'), # Underscore as thousands separator (F(2, 3), '_.2f', '0.67'), (F(2, 3), '_.7f', '0.6666667'), @@ -1620,6 +1628,11 @@ class FractionTest(unittest.TestCase): '.f', '.g', '.%', + # Thousands separators before precision + '._6e', + '._6f', + '._6g', + '._6%', # Z instead of z for negative zero suppression 'Z.2f' # z flag not supported for general formatting diff --git a/Misc/NEWS.d/next/Library/2025-04-07-10-20-16.gh-issue-87790.X2SjJe.rst b/Misc/NEWS.d/next/Library/2025-04-07-10-20-16.gh-issue-87790.X2SjJe.rst new file mode 100644 index 00000000000..be2a30d69ca --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-07-10-20-16.gh-issue-87790.X2SjJe.rst @@ -0,0 +1,2 @@ +Support underscore and comma as thousands separators in the fractional part +for :class:`~fractions.Fraction`'s formatting. Patch by Sergey B Kirpichev.