Python 3.9.9 restores subparser argument precedence
Python 3.9.9 reverses an argparse change that prevented subcommand values from overriding root values. The regression concerns namespace merging as well as command selection.
A command-line parser can recognize a subcommand and still return the wrong configuration for it. Values from the selected command have to reach the final namespace with the intended precedence. Python 3.9.9, released on November 15, repairs a regression in that step. The release arrived outside the normal schedule because Python 3.9.8 had broken behavior used by complex command-line tools.
The hotfix changelog describes issue 45235 as a reverted argparse fix. The earlier change prevented leaf-level arguments from taking precedence over root-level arguments. That is more specific than a generic failure to recognize command names. It concerns which values survive after the selected parser has done its work.
I value command-line libraries that let a program express its grammar directly. Their defaults and merge rules still become part of the application's behavior. A maintenance change to those rules can alter a tool without changing its declared flags or its command handlers.
Subcommands produce their own namespace
The 3.9.9 documentation describes add_subparsers as creating an action that selects a parser for a command. Each child parser can define the arguments appropriate to that operation. The result includes the root parser's attributes and the selected child's attributes, while unrelated sibling parsers do not contribute their options.
The 3.9.9 implementation parses the selected command's remaining tokens into a separate namespace. It then copies those attributes into the root namespace. This merge is where an existing destination name can collide with a value supplied by the child.
In the 3.9.8 implementation, the copy occurs only if the root namespace does not already have that attribute. A default value can therefore block a subcommand value. The namespace contains the destination, so the merge preserves it even though the selected child has produced a more specific result.
The hotfix restores unconditional assignment during that copy. The selected subparser's value can again replace a root value under the same destination name. This is the precedence rule identified in the changelog.
Defaults participate before explicit selection
The problem is visible with a destination used at both levels. A root default can already occupy the attribute before the command's arguments are merged.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--mode", default="root")
commands = parser.add_subparsers(dest="command", required=True)
run = commands.add_parser("run")
run.add_argument("--mode", default="child")
options = parser.parse_args(["run", "--mode", "chosen"])
assert options.mode == "chosen"
The expected result in Python 3.9.9 is the explicit value produced by the selected child parser. Under the conditional merge in 3.9.8, the existing root attribute prevents that replacement. The example isolates namespace precedence without depending on the later behavior of a particular command handler.
This distinction explains why testing only that a command name is accepted would miss the defect. A parser may produce a namespace without raising an error, yet that namespace can select the wrong configuration. The semantic output includes the attribute values, not merely successful parsing.
The patch also moves parser-default initialization before argument parsing. Defaults that do not already have attributes enter the namespace before _parse_known_args runs. That ordering and the restored subparser overwrite belong to the same precedence behavior. Treating either operation as an unrelated dictionary assignment misses the sequence that determines the result.
Dispatch can depend on the same merge
The documentation recommends combining subparsers with set_defaults so that a selected command can carry the function it should execute. That means namespace construction can influence dispatch as well as ordinary flag values. A default is not necessarily an inert string waiting for a user option to replace it.
For a command tree, more specific parsers can contribute values under shared destination names. The restored behavior lets those child values take precedence during the merge. A tool's intended hierarchy therefore depends on a library operation that occurs after token recognition.
I prefer explicit tests of the resulting namespace for this sort of interface. They describe the values a command handler receives, including defaults when the user omits an option. Testing only help output or acceptance of the command name does not establish the same contract.
The release is deliberately a hotfix for a regression, rather than a new argument-precedence design. Existing tools should not need to redefine their command tree to accommodate the conditional merge introduced in the preceding maintenance version. Python 3.9.9 restores the behavior on which those tools relied.
The important result is that selecting a child parser again lets its parsed attributes replace root-level attributes. Programs that reuse destination names across a command hierarchy regain the intended precedence, and the final namespace once more reflects the selected subcommand's contribution. That includes explicitly supplied values even when a root default already populated the same destination.