An irritating incompatible change in Python 3.14's <code>argparse</code>

Today I discovered that Python 3.14 contains an irritating, incompatible change to historical argparse behavior, which I can best illustrate with a little piece of code.

import argparse
def demo():
    p = argparse.ArgumentParser()
    p.add_argument("-n", dest="anumber", type=int,
                   action='store', metavar="NUM",
                   help="A number, default %(default)d")
    p.set_defaults(anumber = 1)
    o = p.parse_args()
    print("Parsed.")
if __name__ == "__main__":
    demo()

Before Python 3.14, this program would run successfully (including with '--help', which will report that the default number is '1'). In Python 3.14 and later, this program will fail with a Python exception from argparse in _check_help() that runs more or less:

[...]
TypeError: %d format: a real number is required, not NoneType
[...]
ValueError: badly formed help string

This change is not in the 3.14 release notes for argparse , but with digging you can find that it's from gh-124899 aka gh-65865 , which is described as "Raise early errors for invalid help strings in argparse". This is a perfectly good change with good intentions, but it has a problem.

The problem is that through Python 3.13, it was perfectly valid to refer to things like '%(default)' in your help text but not have the default value set until later, in a following .set_defaults() call that set the default value of a block of related options (or all of them). After gh-124899 , this will fail if you use any formatting option for '%(default)' that can't be satisfied with None , because the help text is formatted immediately and so has the unspecified default value of None . This doesn't happen if you use '%(default)s' for everything, because a None can be formatted as a string.

Currently this restriction isn't documented for help= , but all of the examples use string formatting, ie '%(default)s', even for something that's an int, and also the example sets a default= in the add_argument() call.

Unfortunately I don't really see an easy and clean way to change this situation. If argparse doesn't check the help text immediately, there's no guarantee that it will have a chance to do so later, before you parse arguments (you might not even call .set_defaults() ). In theory argparse could make up a temporary default value when checking the help string (if you use '%(default)' in it), but in practice this is at least somewhat complex and might hide errors if the default is never set. In a way the cleanest fix would be to make all uses of '%(default)' with an unset default be an error (and document this), but that would be a clear API break.

(Since Python 3.14 has shipped with this issue and it's survived through 3.14.7 as far as I know, the overall change to check help strings early probably isn't going to be reverted any time soon. I'm probably a highly unusual person in combining .set_defaults() with using '%(default)d' instead of formatting all default values as strings.)