41

My requirement is to pass a tuple as command line argument like

--data (1,2,3,4)

I tried to use the argparse module, but if I pass like this it is receiving as the string '(1,2,3,4)'. I tried by giving type=tuple for argparse.add_argument, but is of no use here.

Do I have to add a new type class and pass that to type argument of add_argument?

Update

I tried the ast.literal_eval based on answers. Thanks for that. But it is giving spaces in the result as shown below.

(1,2,3,4)
<type 'str'>
(1, 2, 3, 4)
<type 'tuple'>
user1423015
  • 593
  • 1
  • 4
  • 12
  • 1
    Per the duplicate, `ast.literal_eval` would be an appropriate `type` parameter – jonrsharpe Nov 06 '15 at 10:36
  • Acording to `argparse` docs, `type` must be a **function** (`callable`) that takes a simple string and converts it to the desired object. `tuple('(1,2)')` takes a string, but splits into characters, e.g. `('(', '1', ',', '2', ')')`. Also beware of your users giving you `--data (1, 2, 3,4)`. The shell splits on whitespace. – hpaulj Nov 06 '15 at 19:17
  • 5
    The duplicate link has to do with parsing a string like `'(1,2)'`, but does not address the `argparse` side of the question. – hpaulj Nov 06 '15 at 19:18
  • http://stackoverflow.com/a/18003926/901925 suggests using `json.loads` to parse strings that look like dicts and lists (but not tuples). – hpaulj Nov 06 '15 at 19:33
  • Why not just store the argument as a string and pass it to `eval()` to convert it to a tuple. – muman Jul 30 '20 at 01:32

3 Answers3

61

Set nargs of the data argument to nargs="+" (meaning one or more) and type to int, you can then set the arguments like this on the command line:

--data 1 2 3 4

args.data will now be a list of [1, 2, 3, 4].

If you must have a tuple, you can do:

my_tuple = tuple(args.data)

Putting it all together:

parser = argparse.ArgumentParser()
parser.add_argument('--data', nargs='+', type=int)
args = parser.parse_args()
my_tuple = tuple(args.data)
Alastair McCormack
  • 26,573
  • 8
  • 77
  • 100
  • 3
    Sorry this is not my requirement. Please see my questions once. I know this approach. – user1423015 Nov 06 '15 at 10:22
  • 7
    Change your requirement then ;) Users don't expect to have to format their arguments in a specific way. Using multiple arguments is normal, however. – Alastair McCormack Nov 06 '15 at 10:33
  • 2
    Alastair, You are correct. Finally I changed my requirement :-). – user1423015 Nov 09 '15 at 06:57
  • and the greatest of all, in my case, is that you can specify lists of lists with nargs="+" or nargs= combined with action="append", thus allowing multiple occurences of `--data` in the list of arguments. sweet! – resi Mar 17 '21 at 14:37
  • @AlastairMcCormack and how do you set defaults like that? – KansaiRobot Nov 22 '22 at 00:45
6

If you want to scale this, not deal with syntax trees, and provide the tuple conversion within the argument, you'll need a custom argparse type. (I needed this since my parser is in a package, and I didn't want to require my users to all write my_tuple = tuple(args.data). With an argument type, you can also receive a string with parenthesis like the original question. Also, I wouldn't include nargs, because that will wrap your custom type output into a list, which basically defeats the purpose of your custom type. I mapped a float because my input was something like "0.9, 0.99", but your int works the same:

def tuple_type(strings):
    strings = strings.replace("(", "").replace(")", "")
    mapped_int = map(int, strings.split(","))
    return tuple(mapped_int)

parser = argparse.ArgumentParser()
parser.add_argument('--data', type=tuple_type)
args = parser.parse_args()

This way you can send in --data "(1,2,3,4)" or --data "1,2,3,4"

Don Chesworth
  • 91
  • 1
  • 6
1

I had same requirement. Could not find in standard packages. So, rolled this. Python 3x. Put inside simple argv parser, called one arg at a time:

  • var is passed in default, of correct type.
  • argval is found command line string. Comma separated,no spaces. i.e. 1,2,3 or one,two,three.

Works with bash shell, may need quotes, other adjustments for other shells, OS's. Same technique works non list types. This version creates a homogeneous list or tuple, based on var[0].

example:

./foo.py   --mytuple 1,2,3,4,5

code:

def chk_arg( var, argstr, dbg=False): 
    ### loop on sys.argv here .....
    # if list/tuple type, use list comprehension
    # converts csv list to list or tuple 'type(var)' of 'type(var[0])' items. 
    if type(var) is list or type(var) is tuple:  
        val = type(var)([type(var[0])(i) for i in argval.split(',')]) 
miken
  • 11
  • 2