0

I have an argparser, with two subsets of arguments, inputs and parameters. My command looks something like

program -input1 <input1> -input2 <input2> -p1 <param1> -p2 <param2>

I'd love to

args = parser.parse_args()
params = vars(args.params)

instead of

params = {"p1": args.p1, "p2": args.p2, etc...}

Is there an elegant way to do something like:

parser.add_argument("-p1", dest='p1', part_of=params)

Subparsers don't seem like they're made for this. Or are they?

Argparse has argument groups, but they seem like they're just for the help text.

Thanks!

Alex Lenail
  • 12,992
  • 10
  • 47
  • 79
  • 1
    I'm not clear on what you're trying to do. Do you mean that `-p1` and `-p2` can only occur with/as substructures/"suboptions" of `-input2`? – jwodder Feb 22 '17 at 20:13
  • 1
    Argument groups are mostly useful for mutually exclusive options. What is the nature of your groups? E.g. could you just prefix `dest` to help group parameters? – 9000 Feb 22 '17 at 20:14
  • how do you go about prefixing dest? @9000 – Alex Lenail Feb 22 '17 at 20:22
  • @jwodder, no, all the args are independent and can be specified in any combination. But since some of the args are "parameters" I'd like to be able to have them as a group just by calling `params = vars(args.params)` – Alex Lenail Feb 22 '17 at 20:24
  • @AlexLenail: So you just want the option values to be stored on a subobject of `args`? I don't think `argparse` supports that. – jwodder Feb 22 '17 at 20:28
  • @jwodder That's right – Alex Lenail Feb 22 '17 at 20:43
  • 1
    @AlexLenail: my idea was `parser.add_argument('-p1', ... dest='output_p1')`, etc. Then you could group parsing results yourself by `.strartswith('output_')`, cutting off prefixes if you want. – 9000 Feb 22 '17 at 21:05
  • @9000 not a bad idea... I feel like argument groups should just be available in the namespace though... – Alex Lenail Feb 22 '17 at 21:33
  • Argument Groups control the help lines; Mutually exclusive groups control tests during parsing and usage line. Do not confuse them. There's no built-in grouping mechanism for the `namespace`, except that provided by `nargs` and `append`. – hpaulj Feb 22 '17 at 22:01

1 Answers1

0

This sounds like a variation on the - how do I accept arbitrary 'key=value' pairs? It's been asked in various ways over the years, with various answers.

In a recent one:

Parsing "python foo.py -DVAR1=9 -DVAR2=Off" with argparse

my solution was to split -DVAR1=9 into ('VAR1',9) and append that to the D attribute. That uses a custom type.

Using variable arg names with argparse - this processes the sys.argv before passing it to the parser.

python argparse store --foo=bar as args.key='foo', args.value='bar'

suggests a custom Action class.

I think we've also suggested a custom Namespace class.

The builtin mechanisms for grouping values are nargs and append action. Together you can get attributes which are lists of lists.

JSON strings can also be used to input complex data structures.


class MyAction(argparse._StoreAction):
    def __call__(self, parser, namespace, values, option_string=None):
        print('storing', option_string)
        arg = getattr(namespace, self.dest)
        if arg is None:
            arg = {}
        arg[option_string] = values
        setattr(namespace, self.dest, arg)

In [135]: p=argparse.ArgumentParser()
In [136]: p.add_argument('--p1',action=MyAction,dest='p');
In [137]: p.add_argument('--p2',action=MyAction,dest='p');
In [138]: p.parse_args('--p1 one --p2 two'.split())
storing --p1
storing --p2
Out[138]: Namespace(p={'--p2': 'two', '--p1': 'one'})
In [139]: _.p
Out[139]: {'--p1': 'one', '--p2': 'two'}

Obviously this could be refined in various ways - trimming the keys to 'p1' or even '1', saving to a list, or nested Namespace or other custom structure, etc.

This approach still requires that you define an add_argument for each '--pn` variation.

An alternative is keep argparse simple, producing a namespace like:

In [141]: argparse.Namespace(p1='one', p2='two', input1=1, input2=3)
Out[141]: Namespace(input1=1, input2=3, p1='one', p2='two')

and then do your own grouping afterwards.

In [142]: args=argparse.Namespace(p1='one', p2='two', input1=1, input2=3)
In [143]: {key:getattr(args,key) for key in ['p1','p2']}
Out[143]: {'p1': 'one', 'p2': 'two'}
Community
  • 1
  • 1
hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • can you do better? Re-reading my question -- which has become relevant again -- I feel like this functionality should be provided by python. I don't know much about the governance of python, but I'd love to contribute this. In my mind, this would build off the [argument groups](https://docs.python.org/3.6/library/argparse.html#argument-groups) which is already there, but only used for the help text... – Alex Lenail Apr 21 '17 at 20:14
  • You are welcomed to search for `argparse` issues on `http://bugs.python.org/`. Study some of those, both open and closed ones, to see what's involved in making a change. There are two challenges 1) making the case that this is an urgent and general problem, and 2) writing a complete patch (with tests and documentation). – hpaulj Apr 21 '17 at 23:13