Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf8d56b9

Browse files
mdickinsonserhiy-storchaka
authored andcommitted
pythongh-67790: Support basic formatting for Fraction (python#111320)
PRpython#100161 added fancy float-style formatting for the Fraction type,but left us in a state where basic formatting for fractions (alignment,fill, minimum width, thousands separators) still wasn't supported.This PR adds that support.---------Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent18e68d2 commitf8d56b9

File tree

5 files changed

+155
-31
lines changed

5 files changed

+155
-31
lines changed

‎Doc/library/fractions.rst‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ another rational number, or from a string.
106106
presentation types ``"e"``, ``"E"``, ``"f"``, ``"F"``, ``"g"``, ``"G"``
107107
and ``"%""``.
108108

109+
..versionchanged::3.13
110+
Formatting of:class:`Fraction` instances without a presentation type
111+
now supports fill, alignment, sign handling, minimum width and grouping.
112+
109113
..attribute::numerator
110114

111115
Numerator of the Fraction in lowest term.
@@ -201,17 +205,36 @@ another rational number, or from a string.
201205

202206
..method::__format__(format_spec, /)
203207

204-
Provides support for float-style formatting of:class:`Fraction`
205-
instances via the:meth:`str.format` method, the:func:`format` built-in
206-
function, or:ref:`Formatted string literals<f-strings>`. The
207-
presentation types ``"e"``, ``"E"``, ``"f"``, ``"F"``, ``"g"``, ``"G"``
208-
and ``"%"`` are supported. For these presentation types, formatting for a
209-
:class:`Fraction` object ``x`` follows the rules outlined for
210-
the:class:`float` type in the:ref:`formatspec` section.
208+
Provides support for formatting of:class:`Fraction` instances via the
209+
:meth:`str.format` method, the:func:`format` built-in function, or
210+
:ref:`Formatted string literals<f-strings>`.
211+
212+
If the ``format_spec`` format specification string does not end with one
213+
of the presentation types ``'e'``, ``'E'``, ``'f'``, ``'F'``, ``'g'``,
214+
``'G'`` or ``'%'`` then formatting follows the general rules for fill,
215+
alignment, sign handling, minimum width, and grouping as described in the
216+
:ref:`format specification mini-language<formatspec>`. The "alternate
217+
form" flag ``'#'`` is supported: if present, it forces the output string
218+
to always include an explicit denominator, even when the value being
219+
formatted is an exact integer. The zero-fill flag ``'0'`` is not
220+
supported.
221+
222+
If the ``format_spec`` format specification string ends with one of
223+
the presentation types ``'e'``, ``'E'``, ``'f'``, ``'F'``, ``'g'``,
224+
``'G'`` or ``'%'`` then formatting follows the rules outlined for the
225+
:class:`float` type in the:ref:`formatspec` section.
211226

212227
Here are some examples::
213228

214229
>>> from fractions import Fraction
230+
>>> format(Fraction(103993, 33102), '_')
231+
'103_993/33_102'
232+
>>> format(Fraction(1, 7), '.^+10')
233+
'...+1/7...'
234+
>>> format(Fraction(3, 1), '')
235+
'3'
236+
>>> format(Fraction(3, 1), '#')
237+
'3/1'
215238
>>> format(Fraction(1, 7), '.40g')
216239
'0.1428571428571428571428571428571428571429'
217240
>>> format(Fraction('1234567.855'), '_.2f')

‎Doc/whatsnew/3.13.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,14 @@ email
212212
(Contributed by Thomas Dwyer and Victor Stinner for:gh:`102988` to improve
213213
the CVE-2023-27043 fix.)
214214

215+
fractions
216+
---------
217+
218+
* Formatting for objects of type:class:`fractions.Fraction` now supports
219+
the standard format specification mini-language rules for fill, alignment,
220+
sign handling, minimum width and grouping. (Contributed by Mark Dickinson
221+
in:gh:`111320`)
222+
215223
glob
216224
----
217225

‎Lib/fractions.py‎

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,23 @@ def _round_to_figures(n, d, figures):
139139
returnsign,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-
ifnotformat_spec:
422-
returnstr(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-
ifmatchisNone:
427-
raiseValueError(
428-
f"Invalid format specifier{format_spec!r} "
429-
f"for object of type{type(self).__name__!r}"
430-
)
431-
elifmatch["align"]isnotNoneandmatch["zeropad"]isnotNone:
432-
# Avoid the temptation to guess.
433-
raiseValueError(
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=""ifmatch["sign"]=="-"elsematch["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+
ifd>1oralternate_form:
451+
body=f"{abs(n):{thousands_sep}}/{d:{thousands_sep}}"
452+
else:
453+
body=f"{abs(n):{thousands_sep}}"
454+
sign='-'ifn<0elsepos_sign
455+
456+
# Pad with fill character if necessary and return.
457+
padding=fill* (minimumwidth-len(sign)-len(body))
458+
ifalign==">":
459+
returnpadding+sign+body
460+
elifalign=="<":
461+
returnsign+body+padding
462+
elifalign=="^":
463+
half=len(padding)//2
464+
returnpadding[:half]+sign+body+padding[half:]
465+
else:# align == "="
466+
returnsign+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=""ifmatch["sign"]=="-"elsematch["sign"]
@@ -530,6 +562,23 @@ def __format__(self, format_spec, /):
530562
else:# align == "="
531563
returnsign+padding+body
532564

565+
def__format__(self,format_spec,/):
566+
"""Format this fraction according to the given format specification."""
567+
568+
ifmatch:=_GENERAL_FORMAT_SPECIFICATION_MATCHER(format_spec):
569+
returnself._format_general(match)
570+
571+
ifmatch:=_FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec):
572+
# Refuse the temptation to guess if both alignment _and_
573+
# zero padding are specified.
574+
ifmatch["align"]isNoneormatch["zeropad"]isNone:
575+
returnself._format_float_style(match)
576+
577+
raiseValueError(
578+
f"Invalid format specifier{format_spec!r} "
579+
f"for object of type{type(self).__name__!r}"
580+
)
581+
533582
def_operator_fallbacks(monomorphic_operator,fallback_operator):
534583
"""Generates forward and reverse operators given a purely-rational
535584
operator and a function from the operator module.

‎Lib/test/test_fractions.py‎

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -849,12 +849,50 @@ def denominator(self):
849849
self.assertEqual(type(f.denominator),myint)
850850

851851
deftest_format_no_presentation_type(self):
852-
# Triples (fraction, specification, expected_result)
852+
# Triples (fraction, specification, expected_result).
853853
testcases= [
854-
(F(1,3),'','1/3'),
855-
(F(-1,3),'','-1/3'),
856-
(F(3),'','3'),
857-
(F(-3),'','-3'),
854+
# Explicit sign handling
855+
(F(2,3),'+','+2/3'),
856+
(F(-2,3),'+','-2/3'),
857+
(F(3),'+','+3'),
858+
(F(-3),'+','-3'),
859+
(F(2,3),' ',' 2/3'),
860+
(F(-2,3),' ','-2/3'),
861+
(F(3),' ',' 3'),
862+
(F(-3),' ','-3'),
863+
(F(2,3),'-','2/3'),
864+
(F(-2,3),'-','-2/3'),
865+
(F(3),'-','3'),
866+
(F(-3),'-','-3'),
867+
# Padding
868+
(F(0),'5',' 0'),
869+
(F(2,3),'5',' 2/3'),
870+
(F(-2,3),'5',' -2/3'),
871+
(F(2,3),'0','2/3'),
872+
(F(2,3),'1','2/3'),
873+
(F(2,3),'2','2/3'),
874+
# Alignment
875+
(F(2,3),'<5','2/3 '),
876+
(F(2,3),'>5',' 2/3'),
877+
(F(2,3),'^5',' 2/3 '),
878+
(F(2,3),'=5',' 2/3'),
879+
(F(-2,3),'<5','-2/3 '),
880+
(F(-2,3),'>5',' -2/3'),
881+
(F(-2,3),'^5','-2/3 '),
882+
(F(-2,3),'=5','- 2/3'),
883+
# Fill
884+
(F(2,3),'X>5','XX2/3'),
885+
(F(-2,3),'.<5','-2/3.'),
886+
(F(-2,3),'\n^6','\n-2/3\n'),
887+
# Thousands separators
888+
(F(1234,5679),',','1,234/5,679'),
889+
(F(-1234,5679),'_','-1_234/5_679'),
890+
(F(1234567),'_','1_234_567'),
891+
(F(-1234567),',','-1,234,567'),
892+
# Alternate form forces a slash in the output
893+
(F(123),'#','123/1'),
894+
(F(-123),'#','-123/1'),
895+
(F(0),'#','0/1'),
858896
]
859897
forfraction,spec,expectedintestcases:
860898
withself.subTest(fraction=fraction,spec=spec):
@@ -1218,6 +1256,10 @@ def test_invalid_formats(self):
12181256
'.%',
12191257
# Z instead of z for negative zero suppression
12201258
'Z.2f'
1259+
# z flag not supported for general formatting
1260+
'z',
1261+
# zero padding not supported for general formatting
1262+
'05',
12211263
]
12221264
forspecininvalid_specs:
12231265
withself.subTest(spec=spec):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Implement basic formatting support (minimum width, alignment, fill) for
2+
:class:`fractions.Fraction`.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp