Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Argparse: two positional arguments with nargs='+'

I'm trying to do mathematical operations between images. I have defined (simplified version of my real code):

parser = argparse.ArgumentParser(description='Arithmetic operations on images')
parser.add_argument("input1", metavar='input1', action='store',
      help='list of input images to operate with', nargs="+", type=str)
parser.add_argument("operation", metavar='operation', action='store', type=str, 
      help='type of operation (+,-,*,/) to be done', nargs=1)
parser.add_argument("input2",metavar='input2', action='store', nargs="+", 
      type=str, help='image (or value) with which to perform the operation on input1')

This code, produces:

arith.py -h
usage: arith.py [-h] input1 [input1 ...] operation input2 [input2 ...]

so it does understand that input1 could contain one or more elements, operation will be a single one, and input2 could be any number of elements.

The problem, of course, is that having two positional arguments with an undetermined number of elements, argparse confuses what is what. I have tried adding choices=["+", "-", "*", "/"] to 'operation', so that it would know where to do the separation, but it seems argparse is not able to do it. Actually, in the argparse documentation, talking about nargs='*' you can read:

Note that it generally doesn’t make much sense to have more than one positional argument with nargs='*'

I have thought that I could add together args.input1, args.operation and args.input2 and separate myself looking for "+", "-", "/", "*", but before doing something so ugly I thought about tapping the collective mind.

like image 373
Jblasco Avatar asked Nov 10 '22 13:11

Jblasco


1 Answers

When allocating strings to positionals, the parser only distinguishes between ones that start with a prefix char (e.g. '-') and the rest. It cannot distinguish between strings that represent 'numbers' and ones that represent 'operations'. In effect it performs this regex operation:

re.match('(A+)(A)(A+)','AAAAAAAAA')

which would produce (AAAAAA),(A),(A). It allocates enough strings to the the last 2 groups to satisfy their specs, and allocates the rest to the first.

So you need some sort of 'flag' to mark the end of the first list.

This is, I think, the closest you'll get with argparse:

parser.add_argument("input1", nargs="+", type=int)
parser.add_argument("-o", "--operation", choices=['+','minus','*','/'] )
parser.add_argument("input2", nargs="+", type=int)

which should turn

PROG 1 3 4 -o + 5 6 7
PROG 1 3 4 -o+ 5 6 7
PROG 1 3 4 --operation=+ 5 6 7

into (I think)

namespace(input1=[1,3,4], operation='+', input2=[5,6,7])

Notice that the list of choices does not include '-'. That's because the parser treats that as a prefix_char. There may be a way of sneaking it in as an argument value, but I'm not going to take the time to find it.

I converted the input1 values to integers in the parser. You could do that after. And of course make the floats instead.

I omitted default parameters like type=str, action='store'.


But perhaps a better solution is to take all the values as 1 list, and split it up yourself. At least with these 3 arguments you aren't making much use of argparse power.

alist = ['1','2','3','+','4','5','6']
i = <find index of '+-/*'>
input1 = alist[:i]
operations = alist[i]
input2 = alsits[i+1:]
like image 138
hpaulj Avatar answered Nov 14 '22 23:11

hpaulj