Python argparse multiple metavar names

Question:

I am working with the argparse library in python. At some time, I use an argument called param that takes 2 args: a key and a value. The line of code I use is the following:

parser.add_argument("-p", "--param", nargs=2, action="append", 
                    help="key and value for query", 
                    type=str, metavar="key value"
                    )

What’s wrong here is when I call the help, it displays like this:

optional arguments:
    -h, --help            show this help message and exit
    -p key value key value, --param key value key value
                          key and value for query parameters

The name ‘key value’ is repeated twice. I tried with lists, and generators but the only way i found was creating a little class containing the different values and yielding them when ask to __str__ like this:

class Meta:
    def __init__(self, iterable):
        self.gene = itertools.cycle(iterable)

    def __str__(self):
        return self.gene.__next__()

and I call add_argument like this:

parser.add_argument("-p", "--param", nargs=2, action="append", 
                    help="key and value for query parameters",
                    type=str, metavar=Meta(["key", "value"])
                    )

And it displays correctly:

-p key value, --param key value
                    key and value for query parameters

But I find pretty ugly using a temporary class like Meta, and I feel like there must be another (better) way of doing this. Am I doing it right ?

Asked By: Thibault D.

||

Answers:

From scrolling the doc deeply, I’ve found my answer

Different values of nargs may cause the metavar to be used multiple
times. Providing a tuple to metavar specifies a different display for
each of the arguments:

indeed, this works perfectly fine:

parser.add_argument("-p", "--param", nargs=2, action="append", 
                    help="key and value for query parameters",
                    type=str, metavar=("key", "value")
                    )
Answered By: Thibault D.

The metavar handler isn’t particularly sophisticated, but does take advantage of the information that tuple class provides.

It wasn’t obvious as to why your class worked, so I dug into the code.

Metavar is handled in the Formatter class with:

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

and

def _format_args(self, action, default_metavar):
    get_metavar = self._metavar_formatter(action, default_metavar)
    ....
    else:
        # for numeric nargs
        formats = ['%s' for _ in range(action.nargs)]
        result = ' '.join(formats) % get_metavar(action.nargs)
    return result

So with your Meta:

In [261]: x = Meta(['one', 'two'])
In [262]: x
Out[262]: <__main__.Meta at 0x7f36980f65c0>
In [263]: x = (x,)*2
In [264]: x
Out[264]: (<__main__.Meta at 0x7f36980f65c0>, <__main__.Meta at 0x7f36980f65c0>)
In [265]: '%s %s'%x
Out[265]: 'one two'

With the tuple metavar:

In [266]: '%s %s'%('one','two')
Out[266]: 'one two'

and with a single string

In [267]: '%s %s'%(('one two',)*2)
Out[267]: 'one two one two'
Answered By: hpaulj
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.