Skip to content

argparse: Allow nargs=0 for positional arguments with custom actions #157922

Description

@alpernebbi

Bug report

Bug description:

Python 3.14 raises an error when nargs=0 is given for a positional argument, as implemented in #124839 (gh-85935: Check for nargs=0 for positional arguments in argparse). Before it became an error, I could use such a construction with a custom action (for other reasons) to extend the help message for a multi-purpose positional argument like <a|b|c>. A small reproducer for what I mean:

#!/usr/bin/env python3

import sys
import argparse

parser = argparse.ArgumentParser(
    prog="bless",
    usage="%(prog)s <DISK|PARTITION|IMAGE>",
    add_help=True,
)

# Actual argument, but I want to break help into different lines
parser.add_argument(
    dest="target",
    help=argparse.SUPPRESS,
    metavar="<DISK|PARTITION|IMAGE>",
    nargs=1,
)

# These don't consume command line arguments, but show up in --help
class SomeAction(argparse.Action):
    def __init__(self, option_strings=None, dest=None, nargs=None,
                 const=None, default=None, type=None, choices=None,
                 required=False, help=None, metavar=None):
        return super().__init__(option_strings, dest=dest, nargs=nargs,
                                const=const, default=default, type=type,
                                choices=choices, required=required,
                                help=help, metavar=metavar)

    def __call__(self, parser, namespace, values, option_string=None):
        print(f"SomeAction ({self.metavar}):\t",
              f"{namespace=}, {values=}, {option_string=}")
        # Can even validate/manipulate namespace here

parser.add_argument(action=SomeAction, nargs=0, metavar="DISK",
                    dest=argparse.SUPPRESS, help="Path to disk device")
parser.add_argument(action=SomeAction, nargs=0, metavar="PARTITION",
                    dest=argparse.SUPPRESS, help="Path to parititon device")
parser.add_argument(action=SomeAction, nargs=0, metavar="IMAGE",
                    dest=argparse.SUPPRESS, help="Path to image file")

if __name__ == "__main__":
    print(parser.parse_args(sys.argv[1:]))

Running this with python3.13 shows the following help message, which I like better than one entry trying to explain three different things:

$ python3.13 test.py --help
usage: bless <DISK|PARTITION|IMAGE>

positional arguments:
  DISK        Path to disk device
  PARTITION   Path to parititon device
  IMAGE       Path to image file

options:
  -h, --help  show this help message and exit

Coincidentally, the custom action was always being run, which could be useful for validating or post-processing the namespace as part of the parse_args() call, although I expect it will not be that important to you since those can usually be handled afterwards anyway:

$ python3.13 test.py a
SomeAction (DISK):	 namespace=Namespace(target=['a']), values=[], option_string=None
SomeAction (PARTITION):	 namespace=Namespace(target=['a']), values=[], option_string=None
SomeAction (IMAGE):	 namespace=Namespace(target=['a']), values=[], option_string=None
Namespace(target=['a'])

I don't see how these can be achieved with optional arguments. For the help message, I think I might have to write a custom help formatter (bad idea), which might not even be enough. Advice welcome. Anyway, I'm rather late reporting this, but I wanted to mention this use case at least as a side note.

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Activity

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