Noam Peled
Noam Peled

Reputation: 4622

argparse: expected one argument when passing a negative value

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:

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

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

error: argument --cb_ticks: expected one argument

Upvotes: 6

Views: 9856

Answers (3)

Poikilos
Poikilos

Reputation: 1145

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.")

Upvotes: 1

hpaulj
hpaulj

Reputation: 231395

-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.

Upvotes: 0

Yuri Lifanov
Yuri Lifanov

Reputation: 347

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.

Upvotes: 5

Related Questions