Tuple metavar value for positional argument with nargs > 1

Viewed 619

It seems that setting a tuple as a metavar for positional argument and requesting help does not work:

import argparse


parser = argparse.ArgumentParser()
parser.add_argument('size', type=int, nargs=2, help='size', metavar=('w', 'h'))

args = parser.parse_args()
print(args)

This produces an error when called as prog.py --help. The error differs between Python3 versions (I tried 3.5, 3.6, 3.8) and includes ValueError: too many values to unpack (expected 1) or TypeError: sequence item 0: expected str instance, tuple found. See live example on Wandbox.

For optional arguments, all is good:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--size', type=int, nargs=2, help='size', metavar=('w', 'h'))

args = parser.parse_args()
print(args)

Live example on Wandbox.

Is my code invalid, or did I find a bug in Python implementation?

Please note that simply parsing the arguments works as expected.

2 Answers

Last part of the traceback is

/usr/lib/python3.6/argparse.py in _format_action_invocation(self, action)
    550         if not action.option_strings:
    551             default = self._get_default_metavar_for_positional(action)
--> 552             metavar, = self._metavar_formatter(action, default)(1)
    553             return metavar
    554 

So yes, it's specifically occurring with positionals (empty option_strings). The metavar, = ... assignment only works with the RHS returns one item. With your metavar it's returning a 2.

Usage displays ok

In [36]: parser.print_usage()                                                   
usage: ipython3 [-h] w h

It does look like a bug.

The (1) argument tells the function is should return a 1 element tuple:

metavar, = self._metavar_formatter(action, default)(1)

I suspect the issue has been raised already in Python bug/issues. I'll look for it later.


Instead of metavar, you could just as well use two positional arguments:

parser = argparse.ArgumentParser()
parser.add_argument('w', type=int)
parser.add_argument('h', type=int)

This has been a known bug for a long time - but so far no action:

https://bugs.python.org/issue14074

argparse allows nargs>1 for positional arguments but doesn't allow metavar to be a tuple

Following hpaulj's answer, here's another workaround using action='append':

for name in 'width', 'height':
    parser.add_argument('size', type=int, help=name, metavar=name[0], action='append')
args = parser.parse_args(['4', '3'])
print(args)
parser.print_help()

Output:

Namespace(size=[4, 3])
usage: test.py [-h] w h

positional arguments:
  w           width
  h           height
Related