简体   繁体   中英

Python argparse: Get name of subparser program in help string

I'm writing an argument parser for a python module with various subparsers. My objective is to have an argument that's shared whose Argument constructor is passed to multiple children:

from argparse import ArgumentParser
parser = ArgumentParser(prog = 'master')
parser1 = ArgumentParser(help = None)
parser1.add_argument('foo', type = int, help = 'Number of times to process %(prog)s') # Line of interest
parser2 = ArgumentParser(help = None)
parser2.add_argument('--bar', type = int, default = 0, help = 'Start at this number')
parser3 = ArgumentParser(help = None)
parser3.add_argument('--baz', type = str, default = 'DEFAULT', help = 'Init file with this text')
subparsers = parser.add_subparsers()
sp1 = subparsers.add_parser('prog1', parents = [parser1, parser2])
sp2 = subparsers.add_parser('prog2', parents = [parser1, parser3])
parser.parse_args('prog1 -h'.split())

The desired output would be something like

usage: master prog1 [-h] [--bar BAR] foo

positional arguments:
  foo            Number of times to process prog1

optional arguments:
  -h, --help     show this message and exit
  --bar          Start at this number

When I use this exact setup, i get master prog1 instead of prog1 in the help string for foo . What should i change in the line marked #Line of interest to get the desired result?

This is not a direct answer to your question, but I would use Click_ for what you are trying to do.

Click_ in three points:

  1. arbitrary nesting of commands
  2. automatic help page generation
  3. supports lazy loading of subcommands at runtime

I can explain what is going on, but may not be able offer a solution.

The short answer is that the sp1.prog is used both in its usage format and as the %(prog)s value in the help lines. And it is constructed with that usage line in mind.


sp1 = subparsers.add_parser('prog1', parents = [parser1, parser2])

creates a parser, and adds arguments from the parents . add_parser is a method of the class _SubParsersAction (the subparser Action class). And the prog attribute for that parser is created with:

     if kwargs.get('prog') is None:
        kwargs['prog'] = '%s %s' % (self._prog_prefix, name)

You should be able to see this attribute with print(sp1.prog) (I expect 'master prog1'). That's the value that is used in the usage line, and in any of the help lines with %(prog)s .

subparsers._prog_prefix is derived from parser.prog (see the add_subparsers code for details). But you can also specify a prog parameter:

sp1 = subparsers.add_parser('prog1', parents = [parser1, parser2], prog='prog1')

That should correct the string in the help line. But it will also change the string the usage .

You could also give the subparser an explicit usage :

sp1 = subparsers.add_parser('prog1', parents = [parser1, parser2], prog='prog1', usage='master prog1 ...')

Without doing surgery to the HelpFormatter I don't think you can change prog in the help lines without also changing it in the usage.

And given the way parents works you can't change the help line for prog1 foo without also changing it for prog2 foo . parents copies Action objects by reference, so the two subparsers share the foo Action object.

You many have to abandon the parents approach, at least for this argument, and hardcode the name. If you need to add the argument to multiple subparsers, write a little utility function to facilitate that. The parents mechanism is just (usually) a convenience, something that saves some typing/editing.


This modified script will illustrate my points

parser = ArgumentParser(prog = 'master')

parser1 = ArgumentParser(add_help = False)
fooarg=parser1.add_argument('foo', type = int, help = 'foo prog: %(prog)s') # Line of interest
parser2 = ArgumentParser(add_help = False)
parser2.add_argument('--bar', type = int, default = 0, help = 'Start at this number')
parser3 = ArgumentParser(add_help = False)
parser3.add_argument('--baz', type = str, default = 'DEFAULT', help = 'Init file with this text')

subparsers = parser.add_subparsers(prog='subparsers')
sp1 = subparsers.add_parser('prog1', parents = [parser1, parser2], prog='name1')
sp2 = subparsers.add_parser('prog2', parents = [parser1, parser3])

#parser.print_help()

# fooarg is an Action for both subparsers
# print(fooarg.help)
# fooarg.help = 'FOO HELP'

print('==>sp1 prog:', sp1.prog)
sp1.print_help()
print('==>sp2 prog:', sp2.prog)
sp2.print_help()

sp1.prog = 'custom'
sp1.print_help()

# addition
fooarg.default = 'default'
fooarg.metavar = 'META'
fooarg.help = 'prog: %(prog)s, dest=%(dest)s, nargs=%(nargs)s, type=%(type)s, default=%(default)s'
sp1.print_help()

This last bit adds a bunch of Action attributes to the help. But prog is the only one that comes from the parser :

positional arguments:
  META        prog: custom, dest=foo, nargs=None, type=int, default=default

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM