Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 79 additions & 10 deletions Doc/library/string.rst
Original file line number Diff line number Diff line change
Expand Up @@ -403,13 +403,24 @@ following:

.. index:: single: z; in string formatting

The ``'z'`` option coerces negative zero floating-point values to positive
zero after rounding to the format precision. This option is only valid for
floating-point presentation types.
For floating-point presentation types the ``'z'`` option coerces negative zero
floating-point values to positive zero after rounding to the format precision.

For integer presentation types ``'b'``, ``'o'``, ``'x'``, and ``'X'`` formatted
with precision, the ``'z'`` 'modulo-precision' option first reduces the integer
into ``range(base ** precision)``. The result is a predictable two's complement
style formatting with the number of digits *exactly* equal to the precision.
This is especially useful for formatting negative numbers with known bounds
in environments that deal with fixed widths integers, such as :mod:`struct`.

For other presentation types ``z`` is an invalid specifier.

.. versionchanged:: 3.11
Added the ``'z'`` option (see also :pep:`682`).

.. versionchanged:: next
Implemented the ``'z'`` specifier for integer fields (see also :pep:`786`).

.. index:: single: # (hash); in string formatting

The ``'#'`` option causes the "alternate form" to be used for the
Expand Down Expand Up @@ -437,13 +448,30 @@ excluding :class:`complex`. This is equivalent to a *fill* character of
Preceding the *width* field by ``'0'`` no longer affects the default
alignment for strings.

The *precision* is a decimal integer indicating how many digits should be
displayed after the decimal point for presentation types
``'f'`` and ``'F'``, or before and after the decimal point for presentation
types ``'g'`` or ``'G'``. For string presentation types the field
indicates the maximum field size - in other words, how many characters will be
used from the field content. The *precision* is not allowed for integer
presentation types.
.. index:: single: precision; in string formatting

For floating point presentation types ``'f'`` and ``'F'`` the *precision*
is a decimal integer indicating how many digits should be displayed after
the decimal point. For presentation types ``'g'`` and ``'G'`` the precision
is how many digits should be displayed in total before and after the
decimal point.

For string presentation types the precision indicates the maximum
field size - in other words, how many characters will be used from the
field content.

For integer presentation types (excluding ``'c'``), the precision defines the
minimum number of digits to be displayed, the result padded with leading
zeros if the length of the digits is smaller than the precision specified.
Precision differs from *width*, as only the digits of the number contribute
to the precision count - this is useful when one combines multiple format
specifiers together, and one desires a minimum number of digits, not a
minimum overall string length. ``z`` can be combined with precision to
format the number to **exactly** the precision number of digits, truncating
the result as necessary.

.. versionchanged:: next
Implemented the *precision* specifier for integer presentation types.

The *grouping* option after *width* and *precision* fields specifies
a digit group separator for the integral and fractional parts
Expand Down Expand Up @@ -793,6 +821,47 @@ Nesting arguments and more complex examples::
10 A 12 1010
11 B 13 1011

Comparing the precision and width specifiers::

>>> x = 10
>>> f"{x:#02x}"
'0xa'
>>> # we really wanted 2 digits
>>> f"{x:#.2x}"
'0x0a'
>>> # that's better
>>>
>>> def hexdump(b: bytes) -> str:
... return " ".join(f"{c:#.2x}" for c in b)
>>>
>>> hexdump(b"GET /\r\n\r\n")
'0x47 0x45 0x54 0x20 0x2f 0x0d 0x0a 0x0d 0x0a'
>>> # observe the CR and LF bytes padded to precision 2
>>> # in this basic HTTP/0.9 request
>>>
>>> def unicode_dump(s: str) -> str:
... return " ".join(f"U+{ord(c):.4X}" for c in s)
>>>
>>> unicode_dump("USA 🦅")
'U+0055 U+0053 U+0041 U+0020 U+1F985'
>>> # observe the last character's Unicode codepoint has 5 digits;
>>> # precision is only the minimum number of digits

Using the modulo-precision flag::

>>> import struct
>>> my_struct = b"\xff"
>>> (t,) = struct.unpack('b', my_struct) # signed char
>>> print(t, f"{t:#.2x}", f"{t:z#.2x}")
'-1 -0x01 0xff'
>>> (t,) = struct.unpack('B', my_struct) # unsigned char
>>> print(t, f"{t:#.2x}", f"{t:z#.2x}")
'255 0xff 0xff'

Observe that in both the signed and unsigned unpacking the two's complement
formatting mode (``z``) produces a predictable, consistent string, suitable
for displaying byte-like output.



.. _template-strings-pep292:
Expand Down
14 changes: 12 additions & 2 deletions Lib/test/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,9 +775,19 @@ def test_specifier_z_error(self):
with self.assertRaisesRegex(ValueError, error_msg):
f"{0:fz}" # wrong position

error_msg = re.escape("Negative zero coercion (z) not allowed")
error_msg = re.escape("Two's complement (z) requires precision (.) format specifier")
with self.assertRaisesRegex(ValueError, error_msg):
f"{0:zx}" # can't apply two's complement without precision

error_msg = re.escape("Two's complement (z) only allowed with integer presentation types 'b', 'o', 'x', and 'X'")
with self.assertRaisesRegex(ValueError, error_msg):
f"{0:z.8}" # can't apply to '' int presentation type
with self.assertRaisesRegex(ValueError, error_msg):
f"{0:zd}" # can't apply to int presentation type
f"{0:z.8d}" # can't apply to 'd' int presentation type
with self.assertRaisesRegex(ValueError, error_msg):
f"{0:z.8n}" # can't apply to 'n' int presentation type

error_msg = re.escape("Negative zero coercion (z) not allowed")
with self.assertRaisesRegex(ValueError, error_msg):
f"{'x':zs}" # can't apply to string

Expand Down
73 changes: 68 additions & 5 deletions Lib/test/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,16 @@ def test__format__(self):
self.assertEqual(format(123456789, ','), '123,456,789')
self.assertEqual(format(123456789, '_'), '123_456_789')

## precision
self.assertEqual(format(0, '.0'), '0')
self.assertEqual(format(67, '2.3'), '067')
self.assertEqual(format(67, '8.3'), ' 067')
self.assertEqual(format(67, ' .3'), ' 067')

with self.assertRaises(ValueError):
format(123, ".2147483648")
# too large

# sign and aligning are interdependent
self.assertEqual(format(1, "-"), '1')
self.assertEqual(format(-1, "-"), '-1')
Expand Down Expand Up @@ -707,6 +717,23 @@ def test__format__(self):
self.assertEqual(format(1234567890, '_x'), '4996_02d2')
self.assertEqual(format(1234567890, '_X'), '4996_02D2')

## precision
self.assertEqual(format(10, '#.2x'), '0x0a')
self.assertEqual(format(21, '#.4x'), '0x0015')
self.assertEqual(format(8086, '#.4x'), '0x1f96')
self.assertEqual(format(314159, '#.4x'), '0x4cb2f')
self.assertEqual(format(-21, '#.4x'), '-0x0015')
self.assertEqual(format(-8086, '#.4x'), '-0x1f96')
self.assertEqual(format(-314159, '#.4x'), '-0x4cb2f')

## modulo / two's complement wrap
self.assertEqual(format(-129, 'z#.2x'), '0x7f')
self.assertEqual(format(127, 'z#.2x'), '0x7f')
self.assertEqual(format(383, 'z#.2x'), '0x7f')
self.assertEqual(format(-1, 'z#.2x'), '0xff')
self.assertEqual(format(255, 'z#.2x'), '0xff')
self.assertEqual(format(10, 'z#.2x'), '0x0a')

# octal
self.assertEqual(format(3, "o"), "3")
self.assertEqual(format(-3, "o"), "-3")
Expand All @@ -721,6 +748,18 @@ def test__format__(self):
self.assertRaises(ValueError, format, 1234567890, ',o')
self.assertEqual(format(1234567890, '_o'), '111_4540_1322')

## precision
self.assertEqual(format(18, '#.3o'), '0o022')
self.assertEqual(format(256, 'z.3o'), '400')
self.assertEqual(format(64, 'z.2o'), '00')
self.assertEqual(format(-257, 'z.3o'), '377')

self.assertEqual(format(-1, 'z.3o'), '777')
# different from C `printf("%hho\n", -1);` which is '377'
# because hh is 8 bits, not divisible by 3, the number of
# bits per octal digit, whereas Python has unlimited precision
# and we request 9 bits (3 bits per octal digit * 3 digits)

# binary
self.assertEqual(format(3, "b"), "11")
self.assertEqual(format(-3, "b"), "-11")
Expand All @@ -735,12 +774,36 @@ def test__format__(self):
self.assertRaises(ValueError, format, 1234567890, ',b')
self.assertEqual(format(12345, '_b'), '11_0000_0011_1001')

# make sure these are errors
self.assertRaises(ValueError, format, 3, "1.3") # precision disallowed
## precision
self.assertEqual(format( 1, '#.8b'), "0b00000001")
self.assertEqual(format(-1, '#.8b'), "-0b00000001")
self.assertEqual(format(127, '#.8b'), "0b01111111")
self.assertEqual(format(128, '#.8b'), "0b10000000")
self.assertEqual(format(129, '#.8b'), "0b10000001")
self.assertEqual(format(301, '#.8b'), "0b100101101")
self.assertEqual(format( 15, ' #.8b'), " 0b00001111")
self.assertEqual(format( 15, '+#.8b'), "+0b00001111")
self.assertEqual(format(-15, ' #.8b'), "-0b00001111")

## modulo / two's complement wrap
self.assertEqual(format(-129, 'z#.8b'), '0b01111111')
self.assertEqual(format(127, 'z#.8b'), '0b01111111')
self.assertEqual(format(383, 'z#.8b'), '0b01111111')
self.assertEqual(format(-1, 'z#.8b'), '0b11111111')
self.assertEqual(format(255, 'z#.8b'), '0b11111111')
self.assertEqual(format(10, 'z#.8b'), '0b00001010')
self.assertEqual(format(600, ' z#8.8b'), ' 0b01011000')
self.assertEqual(format(600, ' z#12.8b'), ' 0b01011000')

# make sure these are errors for 'c' presentation type
self.assertRaises(ValueError, format, 3, ".3c") # precision,
self.assertRaises(ValueError, format, 3, "zc") # two's complement,
self.assertRaises(ValueError, format, 3, "#c") # alternate,
self.assertRaises(ValueError, format, 3, "_c") # underscore,
self.assertRaises(ValueError, format, 3, ",c") # comma, and
self.assertRaises(ValueError, format, 3, "+c") # sign not allowed
# with 'c'
self.assertRaises(ValueError, format, 3, ",c") # comma,
self.assertRaises(ValueError, format, 3, "+c") # + sign
self.assertRaises(ValueError, format, 3, "-c") # - sign
self.assertRaises(ValueError, format, 3, " c") # ' ' sign

self.assertRaisesRegex(ValueError, 'Cannot specify both', format, 3, '_,')
self.assertRaisesRegex(ValueError, 'Cannot specify both', format, 3, ',_')
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,6 @@ def test(i, format_spec, result):

# make sure these are errors

# precision disallowed
self.assertRaises(ValueError, 3 .__format__, "1.3")
# sign not allowed with 'c'
self.assertRaises(ValueError, 3 .__format__, "+c")
# format spec must be string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Support precision format specifier (.) for integer fields in string formatting.
Support two's complement format specifier (z) for integer fields formatted
with precision and binary, octal, or hexadecimal presentation type.
Patch by Jay Berry.
Loading
Loading