5

I'm trying to pass a parameter which is a list of values:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--cb_ticks', required=False, default='')              
args = vars(parser.parse_args())
print(args['cb_ticks'])

For most cases, this code works as expected:

  • python test.py --cb_ticks "1" -> 1
  • python test.py --cb_ticks "1,2" -> 1,2
  • python test.py --cb_ticks "-1" -> -1

But when I'm trying to pass more than one value, where the first is negative:

  • python test.py --cb_ticks "-1,2"

I'm getting the following error: test.py:

error: argument --cb_ticks: expected one argument

Noam Peled
  • 4,484
  • 5
  • 43
  • 48
  • 1
    Remove the comma and separate each argument with a space(s) between them. Try this `python test.py --cb_ticks "-1 2"` – R.R.C. Mar 19 '18 at 20:31
  • 3
    I think `argparse` is confusing this `-1,2` with a optionals flag. `-1` can be a short optional, and the code to distinguish that from real numeric negatives is not robust. `--cb_ticks=1,2' should work. – hpaulj Mar 19 '18 at 20:33
  • 1
    Related, but not an answer: https://docs.python.org/3/library/argparse.html#arguments-containing For positional arguments, the rule is expressed "*positional arguments may only begin with - if they look like negative numbers and there are no options in the parser that look like negative numbers*". I wonder if the same rule applies to parameters to optional arguments. – Robᵩ Mar 19 '18 at 20:37
  • Like @Doug suggested, --cb_ticks "-1 2" does work. Thanks! – Noam Peled Mar 19 '18 at 20:41
  • 1
    @Robᵩ, yes the code that identifies tries to identify negative numbers operates early in the parsing, when it is distinguishing between flags and values. – hpaulj Mar 19 '18 at 20:43

3 Answers3

5

The add_argument method allows you to tell the argument parser to expect multiple (or no) values:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--cb_ticks', nargs='*')  

args = vars(parser.parse_args())
print(args['cb_ticks'])

but the values are expected to be space separated, so you'll have to execute your script as:

python test.py --cb_ticks -1 2

See reference.

Yuri Lifanov
  • 347
  • 1
  • 2
  • 11
1

To accept the inputs as specified in the question, you must pre-process the arguments. I tried many things, from where the negative number issue appears in the documentation, from here and from Python Argparse: Issue with optional arguments which are negative numbers, but those methods didn't work in this specific kind of case (my case was https://github.com/poikilos/minetestmapper-python/blob/master/minetestmapper-numpy.py). I solved the issue as follows.

Step 1: Before using argparse, do the following:

cb_ticks_str = None
i = 0
while i < len(sys.argv):
    if sys.argv[i] == "--cb_ticks":
        del sys.argv[i]
        cb_ticks_str = ''
    elif cb_ticks_str == '':
        cb_ticks_str = sys.argv[i]
        del sys.argv[i]
        break
    else:
        i += 1
i = None

Step 2: Use argpase as normal except don't use it for any non-numerical argument that starts with a hyphen:

parser = argparse.ArgumentParser()            
# parser.add_argument('--cb_ticks', required=False, default='')
args = vars(parser.parse_args())

Step 3: Split your argument manually then add it to your args dict:

if cb_ticks_str is not None:
    args['cb_ticks'] = [int(v) for v in cb_ticks_str.split(",")]
    # ^ raises ValueError if a non-int is in the list
    if len(args['cb_ticks']) != 2:
        raise ValueError("cb_ticks must have 2 values separated by a comma.")

Alternatively:

If you were using the parser directly instead of using vars like in the question, (do #1 as I described) then in #2 change args = vars(parser.parse_args()) to args = parser.parse_args(), then for #3 instead do:

Step 3: Split your argument manually then add it to an args object:

if cb_ticks_str is not None:
    args.cb_ticks = [int(v) for v in cb_ticks_str.split(",")]
    # ^ raises ValueError if a non-int is in the list
    if len(args.cb_ticks) != 2:
        raise ValueError("cb_ticks must have 2 values separated by a comma.")
Poikilos
  • 1,050
  • 8
  • 11
0

-1,2 is allowed as a optionals flag:

In [39]: parser.add_argument('-1,2')
...
In [40]: parser.print_help()
usage: ipython3 [-h] [--cb_ticks CB_TICKS] [-1,2 1,2]

optional arguments:
  -h, --help           show this help message and exit
  --cb_ticks CB_TICKS
  -1,2 1,2

In [44]: args=parser.parse_args(['--cb_ticks','foo','-1,2','bar'])
In [45]: args
Out[45]: Namespace(cb_ticks='foo', **{'1,2': 'bar'}) # curious display
In [46]: vars(args)
Out[46]: {'1,2': 'bar', 'cb_ticks': 'foo'}
In [47]: getattr(args, '1,2')
Out[47]: 'bar'

This is an edge case, a consequence of code that tries not to constrain what flags (and/or dest) the user can define.

hpaulj
  • 221,503
  • 14
  • 230
  • 353