| | 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
#
# 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
(?:
(?P<align>[<>=^])
)?
(?P<sign>[-+ ])?
+(?P<alt>\#)?
(?P<zeropad>0)?
(?P<minimumwidth>(?!0)\d+)?
(?P<thousands_sep>,)?
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':
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')
# 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)
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)
+ fmt = fmt[1:] # strip off the %
+ self.assertEqual(format(value, fmt), expected, fmt)
for fmt in ['%e', '%f', '%g', '%.0e', '%.6f', '%.20g',
'%#e', '%#f', '%#g', '%#.20e', '%#.15f', '%#.3g']:
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')
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')
Hans de Graaff
Eddy De Greef
Duncan Grisby
+Eric Groo
Dag Gruneau
Michael Guravage
Lars Gustäbel
Pat Knight
Greg Kochanski
Damon Kohler
+Vlad Korolev
Joseph Koshy
Maksim Kozyarchuk
Stefan Krah
Doug Marien
Alex Martelli
Anthony Martin
+Owen Martin
Sébastien Martini
Roger Masse
Nick Mathewson
Andreas Schawo
Neil Schemenauer
David Scherer
+Bob Schmertz
Gregor Schmid
Ralf Schmitt
Michael Schneider
- 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
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)
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 "
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';