classification
Title: argparse: choices override metavar
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.5, Python 2.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: rhettinger Nosy List: bethard, cykerway, martin.panter, paul.j3, r.david.murray, rhettinger
Priority: normal Keywords:

Created on 2016-12-21 00:52 by cykerway, last changed 2019-08-30 03:31 by rhettinger.

Messages (5)
msg283713 - (view) Author: Cyker Way (cykerway) * Date: 2016-12-21 00:52
Using `choices` option in `argparse` module caused unwanted behavior.

# Without `choices`

    parser = argparse.ArgumentParser()
    parser.add_argument('length')
    parser.print_help()

## Output

    usage: demo.py [-h] length

    positional arguments:
      length

    optional arguments:
      -h, --help  show this help message and exit

# With choices

    parser = argparse.ArgumentParser()
    parser.add_argument('length', choices=[1,2,3])
    parser.print_help()

## Output

    usage: demo.py [-h] {1,2,3}

    positional arguments:
      {1,2,3}

    optional arguments:
      -h, --help  show this help message and exit

I think the doc says ArgumentParser objects use the `dest` value as the “name” of each object. And the doc doesn't mention anything about `dest` be changed by `choices`. The help text looks ugly when there is a long list of choices.

Currently the problem can be remedied by adding a `metavar` option. But I hope it will be fixed in the library itself.
msg283714 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2016-12-21 01:37
metavar is, I believe, the intended way to change the default behavior.  Whether or not this needs better documentation is a separate question :)
msg284858 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2017-01-06 21:44
Here's the method in HelpFormatter that creates the metavar:

    def _metavar_formatter(self, action, default_metavar):
        if action.metavar is not None:
            result = action.metavar
        elif action.choices is not None:
            choice_strs = [str(choice) for choice in action.choices]
            result = '{%s}' % ','.join(choice_strs)
        else:
            result = default_metavar

        def format(tuple_size):
            if isinstance(result, tuple):
                return result
            else:
                return (result, ) * tuple_size
        return format

So in order of priority it uses: 

    the explicit metavar parameter
    formatted choices
    a default metavar (normally derived from the dest)

The MetavarTypeHelpFormatter subclass changes how that default is derived.  In the same spirit, you could write your own Formatter subclass that changes the above method, and its priority order.

In your example, the use of choices in the 'usage' like looks good to me.  I agree that its use in the 'help' line does not look so good.  But don't forget that for your end user, the positional 'dest' (or name) has no value.  Only you as the programmer sees and uses it.

This is one of many implementation details that could be included in the argparse docs.  But for some users those docs are already too complex.  The existing docs are not a formal module reference.
msg284874 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2017-01-06 23:49
subparsers is an example of choices displaying as the metavar.  From the documentation example:

usage: PROG [-h] [--foo] {a,b} ...

positional arguments:
  {a,b}   sub-command help

-----------------

To the main parser, the 'subparser' is a positional argument, with an optional 'dest' parameter (default is SUPPRESS), and {a,b} are the choices derived from the define subparser names.

The 'title' and 'description' parameters are used to create an Argument_group.

So any attempt to change how the metavar is created from choices has the potential of changing the subparser display.

That does suggest another formatting option - put your positional argument (with choices) in a custom Argument_Group.

In [658]: p=argparse.ArgumentParser()
In [659]: g=p.add_argument_group(title='lengths')
In [660]: g.add_argument('length',choices=[1,2,3]);
In [661]: p.print_help()
usage: ipython3 [-h] {1,2,3}

optional arguments:
  -h, --help  show this help message and exit

lengths:
  {1,2,3}
msg291405 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2017-04-10 02:56
I think the documentation should be fixed to say choices overrides dest, and the implementation should be left alone. Even if the documentation is not a “formal module reference”, it should not be wrong or misleading.

Also, Issue 14039 is related, about “metavar” with “add_subparsers”.
History
Date User Action Args
2019-08-30 03:31:35rhettingersetassignee: bethard -> rhettinger

nosy: + rhettinger
2017-04-11 02:14:40rhettingersetassignee: bethard

nosy: + bethard
2017-04-10 02:56:09martin.pantersetnosy: + martin.panter

messages: + msg291405
versions: + Python 2.7
2017-01-06 23:49:16paul.j3setmessages: + msg284874
2017-01-06 21:44:59paul.j3setnosy: + paul.j3
messages: + msg284858
2016-12-21 01:37:08r.david.murraysetnosy: + r.david.murray
messages: + msg283714
2016-12-21 00:52:25cykerwaycreate