Skip to content

argparse: Strange behaviour of positional arguments with store_true, store_false, or store_const action #97848

Description

@kkarbowiak

Bug report

When store_true, store_false, or store_const is set as an action of positional argument, the behaviour is counterintuitive. Running this small program:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('pos', action='store_true')
print(parser.parse_args([]))

outputs Namespace(pos=True). Changing last line to print(parser.parse_args(['foo'])) gives an error:

usage: prog.py [-h]
prog.py: error: unrecognized arguments: foo

Your environment

  • CPython versions tested on: 3.10.2
  • Operating system and architecture: Linux x86-64

Expected behaviour

I believe there is no good reason to allow setting those actions to positional arguments. Trying to do so should result in raising an exception (as is the case when trying to set required).

Linked PRs

Activity

  1. added
    type-featureA feature request or enhancement
    and removed
    type-bugAn unexpected behavior, bug, or error
    on Oct 5, 2022
  2. rhettinger commented on Oct 5, 2022

    @rhettinger
    Contributor

    That behavior is weird.

    It doesn't seem to have caused problems in practice. If an exception does get added, it should not be backported. Essentially, this is a feature request to provide early warning about an action that doesn't make sense for positional arguments.

  3. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 5, 2022
  4. changed the title [-]Strange behaviour of positional arguments with `store_true`, `store_false`, or `store_const` action[/-] [+]argparse: Strange behaviour of positional arguments with `store_true`, `store_false`, or `store_const` action[/+] on Nov 13, 2022
  5. hpaulj commented on Mar 8, 2023

    @hpaulj

    It's not entirely clear where such a test or warning should be added. add_argument doesn't do a whole lot of testing itself. Based on the absence of prefixchars, it calls _get_positional_kwargs to tweak the args/kwargs mix. Same for optionals. For both, the action is actually created with action = action_class(**kwargs). The action class may be one of the registered subclasses, or a user defined subclass. No one checks whether that 'action_class' is appropriate for optionals or positionals.

    This _get_positional_kwargs is used before it looks for the action class.

    nargs=0, unless provided by the user, is set by the action_class. The 'store_const' subclasses are distinct in that they use nargs=0. Subclasses like this don't normally test whether they are creating a positional or optional, though I suppose it could test whether the option_strings argument is empty to not (that's how later code distinguishes between the two).

    Given this calling structure there isn't a whole lot checking of the kwargs. Wrong or unnecessary kwargs are ignored, or allowed to raise some sort of low level error (such as in the Action subclass). This keeps the overall code simple, without potentially large and complex "checking" routines. It also allows you, the developer, to write custom Action classes without being blocked by some master-parameter-checker.

    Also errors like this will arise normally during development and testing. That's when the programmer can easily correct things. What we don't want are un-caught errors during production use. There every error should result in a proper call to self.error, with a message aimed at the final user.

    Many of these complaints about add_argument parameters come from developers who are pushing the bounds of what's documented. They want 'store_true' that accept arguments, or use a type that isn't a callable, or use some unrealistic combination of kwargs. These problems can usually be tracked down by matching the error message with the argparse.py code (which is linked in the doc page). It's hard to anticipate this kind of error without imposing a straight-jacket on everyone.

  6. serhiy-storchaka commented on Oct 9, 2024

    @serhiy-storchaka
    Member

    This is a duplicate of #85935. #124839 provides a more general solution that does not depend on the name of the action or how the action is provided (by name or by class).

  7. moved this from Features to Doc issues in Argparse issueson Oct 12, 2024
  8. Gabrielcarvfer commented on Oct 9, 2025

    @Gabrielcarvfer

    How on earth am I supposed to do this now with Python 3.14?

        parser_help = sub_parser.add_parser("help", help="Print a summary of available commands")
        parser_help.add_argument(
            "help", help="Print a summary of available commands", action="store_true", default=False
        )
    

    To get a flag named help=False when anything but cmd help is passed?

  9. serhiy-storchaka commented on Oct 9, 2025

    @serhiy-storchaka
    Member

    Use the dest argument with add_subparsers().

  10. Gabrielcarvfer commented on Oct 9, 2025

    @Gabrielcarvfer

    Meh, looks terrible, but I guess it works. Thanks for the help.

        sub_parser = parser.add_subparsers(dest="subparser")
        ...
        sub_parser_actions = list(
            filter(lambda x: isinstance(x, argparse._SubParsersAction), parser._actions)
        )
        sub_parsers = list(sub_parser_actions[0].choices.keys())
        if args.subparser in sub_parsers:
            sub_parsers.remove(args.subparser)
    
        for parser in sub_parsers:
            setattr(args, parser, False if parser != "build" else [])
    
        if args.subparser not in [None, "build"]:
            setattr(args, args.subparser, "True")
    
  11. serhiy-storchaka commented on Oct 9, 2025

    @serhiy-storchaka
    Member

    Your old parser may produce weird result for cmd help --help (showing non-existent parameter "HELP").

  12. Gabrielcarvfer commented on Oct 9, 2025

    @Gabrielcarvfer

    It worked just fine. And now it is ok with Python 3.14 too. 🙂

    $ ./ns3 help --help
    usage: ns3 help [-h] 
    
    positional arguments:
      help        Print a summary of available commands
    
    options:
      -h, --help  show this help message and exit
    
    $ ./ns3 --help 
    or
    $ ./ns3 help
    ns-3 wrapper for the CMake build system
    
    usage: ns3 [-h] [--compile-or-die compile_or_die compile_or_die] [--dry-run] [-j JOBS] [--quiet] [-v] {help,build,configure,clean,distclean,install,uninstall,run,shell,docs,show} ...
    
       or: ns3 help [-h] 
       or: ns3 build [-h] [--dry-run] [-j BUILD_JOBS] [--quiet] [-v] [target ...]
       or: ns3 configure [-h] [-d {debug,default,release,optimized,minsizerel}] [-G G] [--cxx-standard {23,26}] [--enable-asserts] [--...
       or: ns3 clean [-h] [--dry-run] 
       or: ns3 distclean [-h] [--dry-run] 
       or: ns3 install [-h] 
       or: ns3 uninstall [-h] 
       or: ns3 run [-h] [--no-build] [--command-template COMMAND_TEMPLATE] [--cwd CWD] [--gdb] [--lldb] [-g] [--memray] [--heaptrack] [--perf] [--vis] [--enable-sudo] [--dry-run] [-j RUN_JOBS] [--quiet] [-v] [target]
       or: ns3 shell [-h] 
       or: ns3 docs [-h] [--dry-run] [-v] {contributing,installation,manual,models,tutorial,sphinx,doxygen-no-build,doxygen,all}
       or: ns3 show [-h] [--dry-run] [--quiet] {profile,version,config,targets,typeid,all} ...
    
    
    
    positional arguments:
      {help,build,configure,clean,distclean,install,uninstall,run,shell,docs,show}
        help                Print a summary of available commands
        build               Accepts a list of targets to build, or builds the entire project if no target is given
        configure           Try "./ns3 configure --help" for more configuration options
        clean               Removes files created by ns3
        distclean           Removes files created by ns3, tests and documentation
        install             Install ns-3
        uninstall           Uninstall ns-3
        run                 Try "./ns3 run --help" for more runtime options
        shell               Try "./ns3 shell --help" for more shell options
        docs                Try "./ns3 docs --help" for more documentation options
        show                Try "./ns3 show --help" for more show options
    
    options:
      -h, --help            Print a summary of available commands
      --compile-or-die compile_or_die compile_or_die
                            Build and test each individual commit between a base and an head commits. This is especially useful when preparing MRs or rewriting git history, and ensuring refactor-or-die principle is being followed
      --dry-run             Do not execute the commands.
      -j, --jobs JOBS       Set number of parallel jobs.
      --quiet               Don't print task lines, i.e. messages saying which tasks are being executed.
      -v, --verbose         Print which commands were executed
    
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

    stdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions