GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Improve `printf`-style formatting docs for non-built-in types · Issue #142108 · python/cpython

Improve printf-style formatting docs for non-built-in types #142108

New issue
Copy link
New issue
Copy link
Open
Open
Improve printf-style formatting docs for non-built-in types#142108
Copy link
Labels
docsDocumentation in the Doc dirDocumentation in the Doc dir

Description

@1nftf
1nftf
opened on Nov 30, 2025
Issue body actions

Add a description about how Decimal behaves differently with format and % formatting in https://docs.python.org/3/library/string.html#format-specification-mini-language

For __format__, Decimal is formatted with the highest possible precision as it is.

For % formatting, Decimal is converted to float before formatting.

These behaviors have not changed since they were added.

  • The Decimal class was added in Python 2.4 (https://docs.python.org/2.4/whatsnew/whatsnew24.html).
  • The __format__ method was added in Python 2.6 (https://docs.python.org/2.6/whatsnew/2.6.html).

Compare:

sys.version '%f' % 11111111111111111111.0 '%f' % Decimal('11111111111111111111.0') Decimal('11111111111111111111.0').__format__('f') '%e' % 0.0 '%e' % Decimal('0.0') Decimal('0.0').__format__('e') '%.20e' % 11111111111111111111.0 '%.20e' % Decimal('11111111111111111111.0') Decimal('11111111111111111111.0').__format__('.20e') '%.20g' % 11111111111111111111.0 '%.20g' % Decimal('11111111111111111111.0') Decimal('11111111111111111111.0').__format__('.20g')
'2.4.4 (#71, Oct 18 2006, 08:34:43) [MSC v.1310 32 bit (Intel)]' '11111111111111111000.000000' '11111111111111111000.000000' Exception '0.000000e+000' '0.000000e+000' Exception '1.11111111111111110000e+019' '1.11111111111111110000e+019' Exception '11111111111111111000' '11111111111111111000' Exception
'2.6.6 (r266:84297, Aug 24 2010, 18:13:38) [MSC v.1500 64 bit (AMD64)]' '11111111111111111000.000000' '11111111111111111000.000000' '11111111111111111111.0' '0.000000e+00' '0.000000e+00' '0e-1' '1.11111111111111110000e+19' '1.11111111111111110000e+19' '1.11111111111111111110e+19' '11111111111111111000' '11111111111111111000' '11111111111111111111'
'2.7.18 (v2.7.18:8d21aa21f2, Apr 20 2020, 13:25:05) [MSC v.1500 64 bit (AMD64)]' '11111111111111110656.000000' '11111111111111110656.000000' '11111111111111111111.0' '0.000000e+00' '0.000000e+00' '0e-1' '1.11111111111111106560e+19' '1.11111111111111106560e+19' '1.11111111111111111110e+19' '11111111111111110656' '11111111111111110656' '11111111111111111111'
'3.14.0 (tags/v3.14.0:ebf955d, Oct 7 2025, 10:06:33) [MSC v.1944 32 bit (Intel)]' '11111111111111110656.000000' '11111111111111110656.000000' '11111111111111111111.0' '0.000000e+00' '0.000000e+00' '0e-1' '1.11111111111111106560e+19' '1.11111111111111106560e+19' '1.11111111111111111110e+19' '11111111111111110656' '11111111111111110656' '11111111111111111111'
'3.14.0 (tags/v3.14.0:ebf955d, Oct 7 2025, 10:15:03) [MSC v.1944 64 bit (AMD64)]' '11111111111111110656.000000' '11111111111111110656.000000' '11111111111111111111.0' '0.000000e+00' '0.000000e+00' '0e-1' '1.11111111111111106560e+19' '1.11111111111111106560e+19' '1.11111111111111111110e+19' '11111111111111110656' '11111111111111110656' '11111111111111111111'
'3.15.0a2 (tags/v3.15.0a2:a625628, Nov 18 2025, 18:12:01) [MSC v.1944 64 bit (AMD64)]' '11111111111111110656.000000' '11111111111111110656.000000' '11111111111111111111.0' '0.000000e+00' '0.000000e+00' '0e-1' '1.11111111111111106560e+19' '1.11111111111111106560e+19' '1.11111111111111111110e+19' '11111111111111110656' '11111111111111110656' '11111111111111111111'
# ./test.py
import sys
from decimal import Decimal

for expr in (
    "sys.version",

    "'%f' % 12345123451234512345.0",
    "'%f' % Decimal('12345123451234512345.0')",
    "Decimal('12345123451234512345.0').__format__('f')",

    "'%e' % 0.0",
    "'%e' % Decimal('0.0')",
    "Decimal('0.0').__format__('e')",

    "'%.20e' % 12345123451234512345.0",
    "'%.20e' % Decimal('12345123451234512345.0')",
    "Decimal('12345123451234512345.0').__format__('.20e')",

    "'%.20g' % 12345123451234512345.0",
    "'%.20g' % Decimal('12345123451234512345.0')",
    "Decimal('12345123451234512345.0').__format__('.20g')",
):
    print(expr)
    try:
        print(repr(eval(expr)))
    except Exception:
        print('Exception')
    print('')
# ./test_runner.py
from pathlib import Path
from subprocess import run, PIPE

PYTHON_DIR_ROOT = r"C:\python_dir_root"

def print_row(row, file):
    for v in row: print(f"| {v} ", end='', file=file)
    print("|", file=file)

def print_md_table(headers, rows, file):
    print_row(headers, file=file)
    print_row(['---:'] * len(headers), file=file)
    for row in rows: print_row(row, file=file)
    print(file=file)

rows = []
for dir in Path(PYTHON_DIR_ROOT).iterdir():
    out = run([str(dir/"python.exe"), "./test.py"], stdout=PIPE).stdout
    out = out.decode('utf-8').splitlines()
    headers = [f"`{h}`" for h in out[::3]]
    rows.append(out[1::3])

with open("./test_result.md", 'w', encoding='utf-8') as fp:
    print_md_table(headers, rows, file=fp)

Linked PRs

  • gh-142108: Document Decimal formatting behavior differences #142117
  • gh-142108: document conversion rules for printf-style formatting #142126

Activity

  1. 1nftf
    added
    docsDocumentation in the Doc dirDocumentation in the Doc dir
    on Nov 30, 2025
  2. github-project-automation
    added this to docs issueson Nov 30, 2025
  3. github-project-automation
    moved this to Todo in docs issueson Nov 30, 2025
  4. skirpichev commented on Nov 30, 2025

    @skirpichev
    skirpichev
    on Nov 30, 2025
    Member
    More actions

    For __format__, Decimal is formatted with the highest possible precision.

    It's just not true. Only when precision is omitted in f/e/g formats. This is documented.

    For % formatting, Decimal is converted to float before formatting.

    This is not specific to Decimal's at all:

    >>> class A:
    ...     def __int__(self):
    ...         return 42
    ...     def __float__(self):
    ...         return 1.25
    ...         
    >>> "%.10f" % A()
    '1.2500000000'
    >>> "%d" % A()
    '42'

    Old-style formatting is documented here: https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting

    Maybe rules are obvious, yet they aren't documented. N.B.: implementation is in the Objects/unicode_format.c

  5. picnixz
    added
    pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
    on Nov 30, 2025
  6. Yashp002 commented on Nov 30, 2025

    @Yashp002
    Yashp002
    on Nov 30, 2025
    Contributor
    More actions

    I'd like to work on this. I'll add clarification about Decimal's formatting behavior to the format specification docs.

  7. picnixz commented on Nov 30, 2025

    @picnixz
    picnixz
    on Nov 30, 2025
    Member
    More actions

    When we have a pending label, usually this means that we still don't know whether this is triaged. AFAICT, what the OP described as an issue was not an issue. IMO, there is nothing to do (unless @skirpichev had something precise in mind?)

  8. skirpichev
    removed
    pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
    on Nov 30, 2025
  9. skirpichev commented on Nov 30, 2025

    @skirpichev
    skirpichev
    on Nov 30, 2025
    Member
    More actions

    Sorry, perhaps I wasn't clear.

    Documentation for printf-style formatting lacks description of rules to format non-builtin types. @picnixz, if such rules are obvious for you - perhaps there is no issue.

  10. picnixz commented on Nov 30, 2025

    @picnixz
    picnixz
    on Nov 30, 2025
    Member
    More actions

    Oh if for non-built-in types, we don't document %-format, then yes I'm fine with documenting them. But I don't really where we would put them. I think we can amend the printf-style sections for str and bytes to say something like:

    For non built-in types, for instance, %d calls the __int__ method of the value to convert to get its int representation to format.

    The above sentence is clearly not perfect and I would rather have something equivalent and better but that convenes this intent (that is, we have an implicit conversion).

  11. picnixz
    changed the title [-]Add a description about how Decimal behaves differently with __format__ and % formatting[/-] [+]Improve `printf`-style formatting for non-built-in types[/+] on Nov 30, 2025
  12. picnixz
    changed the title [-]Improve `printf`-style formatting for non-built-in types[/-] [+]Improve `printf`-style formatting docs for non-built-in types[/+] on Nov 30, 2025
  13. skirpichev commented on Nov 30, 2025

    @skirpichev
    skirpichev
    on Nov 30, 2025
    Member
    More actions

    But I don't really where we would put them.

    We have a dedicated section for %-formatting: https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting

    %d calls the __int__ method of the value to convert to get its int representation to format.

    Yes, something like that. Though, logic is much more complex, e.g. for integer formatting types:

    cpython/Objects/unicode_format.c

    Lines 297 to 317 in 229ed3d

    if (!PyNumber_Check(v))
    goto wrongtype;
    /* make sure number is a type of integer for o, x, and X */
    if (!PyLong_Check(v)) {
    if (type == 'o' || type == 'x' || type == 'X') {
    iobj = _PyNumber_Index(v);
    }
    else {
    iobj = PyNumber_Long(v);
    }
    if (iobj == NULL ) {
    if (PyErr_ExceptionMatches(PyExc_TypeError))
    goto wrongtype;
    return -1;
    }
    assert(PyLong_Check(iobj));
    }
    else {
    iobj = Py_NewRef(v);
    }

    (For floats it's more close to float(obj) for non-string obj's.)

  14. picnixz commented on Nov 30, 2025

    @picnixz
    picnixz
    on Nov 30, 2025
    Member
    More actions

    For %o & co, it's actually documented in object.__index__ but it's honestly better if we were to indicate this in the printf-style section. So I'm in favor of having something that really explains the logic at the same place (I guess the entire thing is documented but probably in differnt places which makes it very hard to search)

  15. 1nftf commented on Nov 30, 2025

    @1nftf
    1nftf
    on Nov 30, 2025
    ContributorAuthor
    More actions

    I discovered the formatting difference while investigating #142019

    I found this document first:
    https://docs.python.org/3/library/string.html#format-specification-mini-language.

    When I tried to find another document, I searched for '% format'
    and '% formatting' but found nothing related to
    https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting.

    I misunderstood that there is only one document about formatting,
    and that the basic functionalities of the format-specification mini-language are also applicable to % formatting,
    except for some additional functions.

    Perhaps a note could be added at the beginning of
    https://docs.python.org/3/library/string.html#format-specification-mini-language
    stating that this is not suitable for % formatting, along with a link back to
    https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting

  16. 1nftf commented on Nov 30, 2025

    @1nftf
    1nftf
    on Nov 30, 2025
    ContributorAuthor
    More actions

    I'm focusing on #142019, so I can't pay much attention to this currently.

  17. skirpichev commented on Nov 30, 2025

    @skirpichev
    skirpichev
    on Nov 30, 2025
    Member
    More actions

    For %o & co, it's actually documented in object.__index__

    Not, unless I misread it:

    Called to implement operator.index(), and whenever Python needs to losslessly convert the numeric object to an integer object (such as in slicing, or in the built-in bin(), hex() and oct() functions).

    But you are right, in sense that we could reference to this logic in formatting docs.

    Perhaps a note could be added at the beginning of https://docs.python.org/3/library/string.html#format-specification-mini-language stating that this is not suitable for % formatting

    I don't think we need this.

  18. 1nftf commented on Nov 30, 2025

    @1nftf
    1nftf
    on Nov 30, 2025
    ContributorAuthor
    More actions

    I don't think we need this.

    Why? Because this old formatting syntax is not encouraged to be used?

  19. picnixz commented on Nov 30, 2025

    @picnixz
    picnixz
    on Nov 30, 2025
    Member
    More actions

    Format specifications and printf-style are different. One is for fmt % value, the other is for format() or str.format.

  20. picnixz commented on Nov 30, 2025

    @picnixz
    picnixz
    on Nov 30, 2025
    Member
    More actions

    I don't think this is issue is very newcomer friendly actually. It requires quite a deep knowledge in the subtle terms as well as knowledge in the implementation. I would suggest either @skirpichev or me to document this but I don't have much time.

    Not, unless I misread it:

    It's not explicitly stated, but I would have assumed that mention to bin/hex/oct combined with "whenever Python needs to losslessly convert the numeric object to an integer object" actually covered this. Actually the oct docs say:

    Convert an integer number to an octal string prefixed with “0o”.
    [...]
    If you want to convert an integer number to an octal string either with the prefix “0o” or not, you can use either of the following ways.

    '%#o' % 10, '%o' % 10
    ('0o12', '12')
    
    format(10, '#o'), format(10, 'o')
    ('0o12', '12')
    
    f'{10:#o}', f'{10:o}'
    ('0o12', '12')
    

    So I guess we could say it's documented, though maybe a bit hard to find.

    Reacted by Sergey B Kirpichev
  21. skirpichev commented on Nov 30, 2025

    @skirpichev
    skirpichev
    on Nov 30, 2025
    Member
    More actions

    Why?

    Because we can't clutter docs with notes each time someone misread them in some way.

    I would suggest either @skirpichev or me to document this but I don't have much time.

    I think it's a good opportunity to learn. There is an open pr, lets see if @Yashp002 will read discussion thread and address review comments.

    If not, I'll try to finish this.

    BTW,

    '%#o' % 10, '%o' % 10

    Side story. Should we promote old-style formatting in usual examples? I don't think so.

  22. skirpichev
    self-assigned this
    on Nov 30, 2025
  23. 1nftf commented on Nov 30, 2025

    @1nftf
    1nftf
    on Nov 30, 2025
    ContributorAuthor
    More actions

    I believe there should at least be a link to #printf-style-string-formatting in #format-specification-mini-language.
    Or maybe replace the literal %-formatting in #format-examples by a link

    This section contains examples of the str.format() syntax and comparison with the old %-formatting.

    I didn't realize #printf-style-string-formatting exists, until @skirpichev mentioned it.

    When I tried to find another document, I searched for '% format'
    and '% formatting' but found nothing related to
    https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting.

    The built-in document search engine is basically useless when it comes to searching for symbols and multiple keywords.

  24. picnixz commented on Nov 30, 2025

    @picnixz
    picnixz
    on Nov 30, 2025
    Member
    More actions

    I guess it doesn't hurt to also backreference %-style in format-style with a single sentence at the end of the first paragraph. "For old-style formatting, see [...]".

    Side story. Should we promote old-style formatting in usual examples? I don't think so.

    Personally, I use old-style formatting because it's easier to me to read. There is a real visual benefit IMO where I can more easily parse % tokens rather than {varname}. So while I understand that people don't always like old-style formatting I do use it myself if needed (it's especially useful for double-formatting; (fmt % args).format(after), which happens when you want to pre-compute the width of your field (it's possible to do it with f-strings but it's unreadable to me honestly).

  25. skirpichev commented on Nov 30, 2025

    @skirpichev
    skirpichev
    on Nov 30, 2025
    Member
    More actions

    Or maybe replace the literal %-formatting in #format-examples by a link

    Seems fine.

    I didn't realize #printf-style-string-formatting exists

    It depends on how you read the docs. Starting from the Tutorial here you will quickly navigate to both pages.

  26. Yashp002 commented on Dec 1, 2025

    @Yashp002
    Yashp002
    on Dec 1, 2025
    Contributor
    More actions

    Hi, I attempted a PR but realized after review that I may have misunderstood the scope of this issue.

    The issue title mentions "printf-style formatting" (% operator) but the linked doc section is about the Format Specification Mini-Language (format / f-strings / .format()).

    Could you clarify:

    1. Should the documentation changes be in the printf-style docs instead? (https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting)
    2. Or is the goal to document Decimal's behavior in the Format Spec section and clarify the % vs format difference there?
    3. Or is this already documented elsewhere and no changes are needed?
  27. skirpichev
    added a commit that references this issue on Dec 1, 2025

    pythongh-142108: document conversion rules for printf-style formatting

    bcf135a
  28. skirpichev
    removed their assignment
    on Dec 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    docsDocumentation in the Doc dirDocumentation in the Doc dir

    Projects

    • docs issues
      Status
      Todo
      Show more project fields

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.