mirror of https://github.com/python/cpython
Issue #7094: Add alternate ('#') flag to __format__ methods for float, complex and Decimal. Allows greater control over when decimal points appear. Added to make transitioning from %-formatting easier. '#g' still has a problem with Decimal which I'll fix soon.
This commit is contained in:
parent
c1d98d6850
commit
984bb58000
|
@ -350,9 +350,18 @@ following:
|
|||
| | positive numbers, and a minus sign on negative numbers. |
|
||||
+---------+----------------------------------------------------------+
|
||||
|
||||
The ``'#'`` option is only valid for integers, and only for binary, octal, or
|
||||
hexadecimal output. If present, it specifies that the output will be prefixed
|
||||
by ``'0b'``, ``'0o'``, or ``'0x'``, respectively.
|
||||
|
||||
The ``'#'`` option causes the "alternate form" to be used for the
|
||||
conversion. The alternate form is defined differently for different
|
||||
types. This option is only valid for integer, float, complex and
|
||||
Decimal types. For integers, when binary, octal, or hexadecimal output
|
||||
is used, this option adds the prefix respective ``'0b'``, ``'0o'``, or
|
||||
``'0x'`` to the output value. For floats, complex and Decimal the
|
||||
alternate form causes the result of the conversion to always contain a
|
||||
decimal-point character, even if no digits follow it. Normally, a
|
||||
decimal-point character appears in the result of these conversions
|
||||
only if a digit follows it. In addition, for ``'g'`` and ``'G'``
|
||||
conversions, trailing zeros are not removed from the result.
|
||||
|
||||
The ``','`` option signals the use of a comma for a thousands separator.
|
||||
For a locale aware separator, use the ``'n'`` integer presentation type
|
||||
|
|
|
@ -5991,7 +5991,7 @@ _exact_half = re.compile('50*$').match
|
|||
#
|
||||
# A format specifier for Decimal looks like:
|
||||
#
|
||||
# [[fill]align][sign][0][minimumwidth][,][.precision][type]
|
||||
# [[fill]align][sign][#][0][minimumwidth][,][.precision][type]
|
||||
|
||||
_parse_format_specifier_regex = re.compile(r"""\A
|
||||
(?:
|
||||
|
@ -5999,6 +5999,7 @@ _parse_format_specifier_regex = re.compile(r"""\A
|
|||
(?P<align>[<>=^])
|
||||
)?
|
||||
(?P<sign>[-+ ])?
|
||||
(?P<alt>\#)?
|
||||
(?P<zeropad>0)?
|
||||
(?P<minimumwidth>(?!0)\d+)?
|
||||
(?P<thousands_sep>,)?
|
||||
|
@ -6214,7 +6215,7 @@ def _format_number(is_negative, intpart, fracpart, exp, spec):
|
|||
|
||||
sign = _format_sign(is_negative, spec)
|
||||
|
||||
if fracpart:
|
||||
if fracpart or spec['alt']:
|
||||
fracpart = spec['decimal_point'] + fracpart
|
||||
|
||||
if exp != 0 or spec['type'] in 'eE':
|
||||
|
|
|
@ -555,8 +555,28 @@ class ComplexTest(unittest.TestCase):
|
|||
self.assertEqual(format(1.5e21+3j, '^40,.2f'), ' 1,500,000,000,000,000,000,000.00+3.00j ')
|
||||
self.assertEqual(format(1.5e21+3000j, ',.2f'), '1,500,000,000,000,000,000,000.00+3,000.00j')
|
||||
|
||||
# alternate is invalid
|
||||
self.assertRaises(ValueError, (1.5+0.5j).__format__, '#f')
|
||||
# Issue 7094: Alternate formatting (specified by #)
|
||||
self.assertEqual(format(1+1j, '.0e'), '1e+00+1e+00j')
|
||||
self.assertEqual(format(1+1j, '#.0e'), '1.e+00+1.e+00j')
|
||||
self.assertEqual(format(1+1j, '.0f'), '1+1j')
|
||||
self.assertEqual(format(1+1j, '#.0f'), '1.+1.j')
|
||||
self.assertEqual(format(1.1+1.1j, 'g'), '1.1+1.1j')
|
||||
self.assertEqual(format(1.1+1.1j, '#g'), '1.10000+1.10000j')
|
||||
|
||||
# Alternate doesn't make a difference for these, they format the same with or without it
|
||||
self.assertEqual(format(1+1j, '.1e'), '1.0e+00+1.0e+00j')
|
||||
self.assertEqual(format(1+1j, '#.1e'), '1.0e+00+1.0e+00j')
|
||||
self.assertEqual(format(1+1j, '.1f'), '1.0+1.0j')
|
||||
self.assertEqual(format(1+1j, '#.1f'), '1.0+1.0j')
|
||||
|
||||
# Misc. other alternate tests
|
||||
self.assertEqual(format((-1.5+0.5j), '#f'), '-1.500000+0.500000j')
|
||||
self.assertEqual(format((-1.5+0.5j), '#.0f'), '-2.+0.j')
|
||||
self.assertEqual(format((-1.5+0.5j), '#e'), '-1.500000e+00+5.000000e-01j')
|
||||
self.assertEqual(format((-1.5+0.5j), '#.0e'), '-2.e+00+5.e-01j')
|
||||
self.assertEqual(format((-1.5+0.5j), '#g'), '-1.50000+0.500000j')
|
||||
self.assertEqual(format((-1.5+0.5j), '.0g'), '-2+0.5j')
|
||||
self.assertEqual(format((-1.5+0.5j), '#.0g'), '-2.+0.5j')
|
||||
|
||||
# zero padding is invalid
|
||||
self.assertRaises(ValueError, (1.5+0.5j).__format__, '010f')
|
||||
|
|
|
@ -818,6 +818,18 @@ class DecimalFormatTest(unittest.TestCase):
|
|||
|
||||
# issue 6850
|
||||
('a=-7.0', '0.12345', 'aaaa0.1'),
|
||||
|
||||
# Issue 7094: Alternate formatting (specified by #)
|
||||
('.0e', '1.0', '1e+0'),
|
||||
('#.0e', '1.0', '1.e+0'),
|
||||
('.0f', '1.0', '1'),
|
||||
('#.0f', '1.0', '1.'),
|
||||
('g', '1.1', '1.1'),
|
||||
('#g', '1.1', '1.1'),
|
||||
('.0g', '1', '1'),
|
||||
('#.0g', '1', '1.'),
|
||||
('.0%', '1.0', '100%'),
|
||||
('#.0%', '1.0', '100.%'),
|
||||
]
|
||||
for fmt, d, result in test_values:
|
||||
self.assertEqual(format(Decimal(d), fmt), result)
|
||||
|
|
|
@ -706,9 +706,6 @@ class RoundTestCase(unittest.TestCase):
|
|||
def test(fmt, value, expected):
|
||||
# Test with both % and format().
|
||||
self.assertEqual(fmt % value, expected, fmt)
|
||||
if not '#' in fmt:
|
||||
# Until issue 7094 is implemented, format() for floats doesn't
|
||||
# support '#' formatting
|
||||
fmt = fmt[1:] # strip off the %
|
||||
self.assertEqual(format(value, fmt), expected, fmt)
|
||||
|
||||
|
|
|
@ -396,13 +396,9 @@ class TypesTests(unittest.TestCase):
|
|||
self.assertEqual(len(format(0, cfmt)), len(format(x, cfmt)))
|
||||
|
||||
def test_float__format__(self):
|
||||
# these should be rewritten to use both format(x, spec) and
|
||||
# x.__format__(spec)
|
||||
|
||||
def test(f, format_spec, result):
|
||||
assert type(f) == float
|
||||
assert type(format_spec) == str
|
||||
self.assertEqual(f.__format__(format_spec), result)
|
||||
self.assertEqual(format(f, format_spec), result)
|
||||
|
||||
test(0.0, 'f', '0.000000')
|
||||
|
||||
|
@ -516,9 +512,27 @@ class TypesTests(unittest.TestCase):
|
|||
self.assertRaises(ValueError, format, 1e-100, format_spec)
|
||||
self.assertRaises(ValueError, format, -1e-100, format_spec)
|
||||
|
||||
# Alternate formatting is not supported
|
||||
self.assertRaises(ValueError, format, 0.0, '#')
|
||||
self.assertRaises(ValueError, format, 0.0, '#20f')
|
||||
# Alternate float formatting
|
||||
test(1.0, '.0e', '1e+00')
|
||||
test(1.0, '#.0e', '1.e+00')
|
||||
test(1.0, '.0f', '1')
|
||||
test(1.0, '#.0f', '1.')
|
||||
test(1.1, 'g', '1.1')
|
||||
test(1.1, '#g', '1.10000')
|
||||
test(1.0, '.0%', '100%')
|
||||
test(1.0, '#.0%', '100.%')
|
||||
|
||||
# Issue 7094: Alternate formatting (specified by #)
|
||||
test(1.0, '0e', '1.000000e+00')
|
||||
test(1.0, '#0e', '1.000000e+00')
|
||||
test(1.0, '0f', '1.000000' )
|
||||
test(1.0, '#0f', '1.000000')
|
||||
test(1.0, '.1e', '1.0e+00')
|
||||
test(1.0, '#.1e', '1.0e+00')
|
||||
test(1.0, '.1f', '1.0')
|
||||
test(1.0, '#.1f', '1.0')
|
||||
test(1.0, '.1%', '100.0%')
|
||||
test(1.0, '#.1%', '100.0%')
|
||||
|
||||
# Issue 6902
|
||||
test(12345.6, "0<20", '12345.60000000000000')
|
||||
|
|
|
@ -318,6 +318,7 @@ David Goodger
|
|||
Hans de Graaff
|
||||
Eddy De Greef
|
||||
Duncan Grisby
|
||||
Eric Groo
|
||||
Dag Gruneau
|
||||
Michael Guravage
|
||||
Lars Gustäbel
|
||||
|
@ -457,6 +458,7 @@ Lenny Kneler
|
|||
Pat Knight
|
||||
Greg Kochanski
|
||||
Damon Kohler
|
||||
Vlad Korolev
|
||||
Joseph Koshy
|
||||
Maksim Kozyarchuk
|
||||
Stefan Krah
|
||||
|
@ -536,6 +538,7 @@ David Marek
|
|||
Doug Marien
|
||||
Alex Martelli
|
||||
Anthony Martin
|
||||
Owen Martin
|
||||
Sébastien Martini
|
||||
Roger Masse
|
||||
Nick Mathewson
|
||||
|
@ -733,6 +736,7 @@ Michael Scharf
|
|||
Andreas Schawo
|
||||
Neil Schemenauer
|
||||
David Scherer
|
||||
Bob Schmertz
|
||||
Gregor Schmid
|
||||
Ralf Schmitt
|
||||
Michael Schneider
|
||||
|
|
|
@ -15,6 +15,10 @@ Core and Builtins
|
|||
- Issue #10027. st_nlink was not being set on Windows calls to os.stat or
|
||||
os.lstat. Patch by Hirokazu Yamamoto.
|
||||
|
||||
- Issue #7094: Added alternate formatting (specified by '#') to
|
||||
__format__ method of float, complex, and Decimal. This allows more
|
||||
precise control over when decimal points are displayed.
|
||||
|
||||
- Issue #10474: range().count() should return integers.
|
||||
|
||||
- Issue #10255: Fix reference leak in Py_InitializeEx(). Patch by Neil
|
||||
|
|
|
@ -941,13 +941,8 @@ format_float_internal(PyObject *value,
|
|||
from a hard-code pseudo-locale */
|
||||
LocaleInfo locale;
|
||||
|
||||
/* Alternate is not allowed on floats. */
|
||||
if (format->alternate) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"Alternate form (#) not allowed in float format "
|
||||
"specifier");
|
||||
goto done;
|
||||
}
|
||||
if (format->alternate)
|
||||
flags |= Py_DTSF_ALT;
|
||||
|
||||
if (type == '\0') {
|
||||
/* Omitted type specifier. Behaves in the same way as repr(x)
|
||||
|
@ -1104,15 +1099,7 @@ format_complex_internal(PyObject *value,
|
|||
from a hard-code pseudo-locale */
|
||||
LocaleInfo locale;
|
||||
|
||||
/* Alternate is not allowed on complex. */
|
||||
if (format->alternate) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"Alternate form (#) not allowed in complex format "
|
||||
"specifier");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Neither is zero pading. */
|
||||
/* Zero padding is not allowed. */
|
||||
if (format->fill_char == '0') {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"Zero padding is not allowed in complex format "
|
||||
|
@ -1135,6 +1122,9 @@ format_complex_internal(PyObject *value,
|
|||
if (im == -1.0 && PyErr_Occurred())
|
||||
goto done;
|
||||
|
||||
if (format->alternate)
|
||||
flags |= Py_DTSF_ALT;
|
||||
|
||||
if (type == '\0') {
|
||||
/* Omitted type specifier. Should be like str(self). */
|
||||
type = 'r';
|
||||
|
|
Loading…
Reference in New Issue