765

I am trying to pass a list as an argument to a command line program. Is there an argparse option to pass a list as option?

parser.add_argument('-l', '--list',
                      type=list, action='store',
                      dest='list',
                      help='<Required> Set flag',
                      required=True)

Script is called like below

python test.py -l "265340 268738 270774 270817"
nbro
  • 15,395
  • 32
  • 113
  • 196
carte blanche
  • 10,796
  • 14
  • 46
  • 65

13 Answers13

1518

SHORT ANSWER

Use the nargs option or the 'append' setting of the action option (depending on how you want the user interface to behave).

nargs

parser.add_argument('-l','--list', nargs='+', help='<Required> Set flag', required=True)
# Use like:
# python arg.py -l 1234 2345 3456 4567

nargs='+' takes 1 or more arguments, nargs='*' takes zero or more.

append

parser.add_argument('-l','--list', action='append', help='<Required> Set flag', required=True)
# Use like:
# python arg.py -l 1234 -l 2345 -l 3456 -l 4567

With append you provide the option multiple times to build up the list.

Don't use type=list!!! - There is probably no situation where you would want to use type=list with argparse. Ever.


LONG ANSWER

Let's take a look in more detail at some of the different ways one might try to do this, and the end result.

import argparse

parser = argparse.ArgumentParser()

# By default it will fail with multiple arguments.
parser.add_argument('--default')

# Telling the type to be a list will also fail for multiple arguments,
# but give incorrect results for a single argument.
parser.add_argument('--list-type', type=list)

# This will allow you to provide multiple arguments, but you will get
# a list of lists which is not desired.
parser.add_argument('--list-type-nargs', type=list, nargs='+')

# This is the correct way to handle accepting multiple arguments.
# '+' == 1 or more.
# '*' == 0 or more.
# '?' == 0 or 1.
# An int is an explicit number of arguments to accept.
parser.add_argument('--nargs', nargs='+')

# To make the input integers
parser.add_argument('--nargs-int-type', nargs='+', type=int)

# An alternate way to accept multiple inputs, but you must
# provide the flag once per input. Of course, you can use
# type=int here if you want.
parser.add_argument('--append-action', action='append')

# To show the results of the given option to screen.
for _, value in parser.parse_args()._get_kwargs():
    if value is not None:
        print(value)

Here is the output you can expect:

$ python arg.py --default 1234 2345 3456 4567
...
arg.py: error: unrecognized arguments: 2345 3456 4567

$ python arg.py --list-type 1234 2345 3456 4567
...
arg.py: error: unrecognized arguments: 2345 3456 4567

$ # Quotes won't help here... 
$ python arg.py --list-type "1234 2345 3456 4567"
['1', '2', '3', '4', ' ', '2', '3', '4', '5', ' ', '3', '4', '5', '6', ' ', '4', '5', '6', '7']

$ python arg.py --list-type-nargs 1234 2345 3456 4567
[['1', '2', '3', '4'], ['2', '3', '4', '5'], ['3', '4', '5', '6'], ['4', '5', '6', '7']]

$ python arg.py --nargs 1234 2345 3456 4567
['1234', '2345', '3456', '4567']

$ python arg.py --nargs-int-type 1234 2345 3456 4567
[1234, 2345, 3456, 4567]

$ # Negative numbers are handled perfectly fine out of the box.
$ python arg.py --nargs-int-type -1234 2345 -3456 4567
[-1234, 2345, -3456, 4567]

$ python arg.py --append-action 1234 --append-action 2345 --append-action 3456 --append-action 4567
['1234', '2345', '3456', '4567']

Takeaways:

  • Use nargs or action='append'
    • nargs can be more straightforward from a user perspective, but it can be unintuitive if there are positional arguments because argparse can't tell what should be a positional argument and what belongs to the nargs; if you have positional arguments then action='append' may end up being a better choice.
    • The above is only true if nargs is given '*', '+', or '?'. If you provide an integer number (such as 4) then there will be no problem mixing options with nargs and positional arguments because argparse will know exactly how many values to expect for the option.
  • Don't use quotes on the command line1
  • Don't use type=list, as it will return a list of lists
    • This happens because under the hood argparse uses the value of type to coerce each individual given argument you your chosen type, not the aggregate of all arguments.
    • You can use type=int (or whatever) to get a list of ints (or whatever)

1: I don't mean in general.. I mean using quotes to pass a list to argparse is not what you want.

SethMMorton
  • 45,752
  • 12
  • 65
  • 86
  • 4
    What about a list of strings? This turns multiple string arguments ("wassup", "something", and "else")into a list of lists that looks like this: [['w', 'a', 's', 's', 'u', 'p'], ['s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g'], ['e', 'l', 's', 'e']] – rd108 Sep 03 '13 at 20:04
  • 6
    @rd108 I see, I bet that you are using the `type=list` option. Don't use that. That turns a string into a list, and hence the lists of lists. – SethMMorton Sep 03 '13 at 20:34
  • @SethMMorton But what if I want to give a list of *strings* as the argument? – Dror Oct 30 '14 at 10:41
  • 1
    @Dror All input is assumed to be strings unless you set the `type` parameter to some other object. By default this method returns a list of strings. – SethMMorton Oct 30 '14 at 13:55
  • Take care with negative values. This can be quite tedious with nargs. Compare `-l 10 ' -20' ' -30'` with the more natural `--list='10 -20 -30'`. – user1346466 Sep 01 '15 at 13:34
  • @user1346466 One does not need the quotes for negative numbers, a space separated list works just as well for negative numbers as not. `argparse` seems to thing that numeric options would be illegal anyway and parses the `-` as part of the number, not as an argument (the merits of this assumption could be discussed elsewhere...). – SethMMorton Sep 01 '15 at 15:58
  • 1
    `--` could split options vs. positional arguments. `prog --opt1 par1 ... -- posp1 posp2 ...` – 0andriy Aug 15 '17 at 15:51
  • @0andriy Can you complete your thought? Right now this statement appears to be unrelated to the question or this answer, but I have a feeling that you had a good reason for mentioning it. How could the presence of `--` improve this answer? – SethMMorton Aug 16 '17 at 17:00
  • 1
    *it can be unintuitive if there are positional arguments because argparse can't tell what should be a positional argument and what belongs to the nargs*. `--` helps to figure this out as shown in example in my previous comment. IOW user supplies `--` followed by all positional arguments. – 0andriy Aug 16 '17 at 18:09
  • Thank you for your experiment. I wonder how to set the default value to method like `parser.add_argument('--nargs-int-type', nargs='+', type=int)` – Mata Fu Nov 16 '18 at 14:18
  • @MataFu You mean like `parser.add_argument('--nargs-int-type', nargs='+', type=int, default=[5])`, or `parser.add_argument('--nargs-int-type', nargs='+', type=int, default=[1, 2, 3, 4, 5])`? – SethMMorton Nov 17 '18 at 05:57
  • What if i want to pass multiple list? how can I seperate them? @SethMMorton – alper Mar 16 '20 at 16:49
  • That’s too open-ended without more details and probably worth asking as a separate question. – SethMMorton Mar 16 '20 at 16:51
  • can I somehow combine `nargs` with `action='append'` ? `parser.add_argument('-n', '--namespace', nargs='+', action="append")` This one does not work. – t7e Dec 09 '22 at 16:49
  • @t7e If it's not working, I would anticipate that means it is not supported. – SethMMorton Dec 09 '22 at 20:07
145

I prefer passing a delimited string which I parse later in the script. The reasons for this are; the list can be of any type int or str, and sometimes using nargs I run into problems if there are multiple optional arguments and positional arguments.

parser = ArgumentParser()
parser.add_argument('-l', '--list', help='delimited list input', type=str)
args = parser.parse_args()
my_list = [int(item) for item in args.list.split(',')]

Then,

python test.py -l "265340,268738,270774,270817" [other arguments]

or,

python test.py -l 265340,268738,270774,270817 [other arguments]

will work fine. The delimiter can be a space, too, which would though enforce quotes around the argument value like in the example in the question.

Or you can use a lambda type as suggested in the comments by Chepner:

parser.add_argument('-l', '--list', help='delimited list input', 
    type=lambda s: [int(item) for item in s.split(',')])
Roelant
  • 4,508
  • 1
  • 32
  • 62
dojuba
  • 2,199
  • 1
  • 18
  • 16
  • 78
    You can set the `type` argument to `lambda s: [int(time) for item in s.split(',')]` instead of post-processing `args.list`. – chepner Jul 21 '14 at 14:03
  • 18
    @chepner,yes you're absolutely right and it would be more pythonic - just a small typo: `int(time)` should be `int(item)`. My example was a simplified version of what I typically do, where I check many other things rather than a simple processing. But to simply answer the question, I too find your way more elegant.. – dojuba Jul 22 '14 at 14:27
  • 1
    this answer looks to be the most pythonic – Quetzalcoatl Nov 07 '18 at 05:53
  • 2
    The comment by @chepner is some serious ninja skillz +1 – Brian Wylie Nov 28 '18 at 17:48
  • 1
    `lambda items: list(csv.reader([items]))[0]` with the standard [csv](https://docs.python.org/3/library/csv.html#examples) library is a modified version of the comment from [@chepner](https://stackoverflow.com/users/1126841/chepner) for anyone worried about arbitrary CSV input (ref: [answer](https://stackoverflow.com/a/3305973) from [@adamk](https://stackoverflow.com/users/293929/adamk)). – Kevin Aug 28 '19 at 16:27
  • @chepner Can you please explain how does this `lambda` work? Is the function specified in the type simply called on the parsed argument? – St.Antario Oct 08 '19 at 05:48
  • 1
    @St.Antario Correct. The `type` argument doesn't actually have to be a type; it's just an arbitrary function which accepts a `str` as its argument and returns some other value. Typically, it's a type (`int`, `float`, `bool`, etc), but it can be arbitrarily complex. A common use is to parse the string and produce a value of some other type, raising `ArgumentTypeError` in the event of any parse errors. – chepner Oct 08 '19 at 11:33
  • Is it possible to pass multiple seperate lists? @dojuba – alper Mar 16 '20 at 16:51
  • This answer is very useful/generic such that we can utilize the concept of using a lambda function for the type argument to create whatever type we want. – Dane Lee May 16 '21 at 22:08
  • @chepner @kevin With the lambda as the type, the `parser.add_argument(...)` call should be wrapped with a try/except to rule out any unparseable data, e.g. chars for ints or unknown tokens, etc. So then argparse loses some of its luster -- code is bigger and code is no longer relying on very nice built-in error handling. – ingyhere Sep 02 '22 at 14:09
  • @ingyhere Typically, you won't try to use a bare lambda expression as the `type` argument; you'll define a more fleshed out function to handle conversion errors. `p.parse_args` itself catches (but surpasses) any exception raised by the function an calls `p.error` automatically. – chepner Sep 02 '22 at 14:17
  • @chepner Sure, except that's exactly what's written in the answer. – ingyhere Sep 02 '22 at 14:26
  • @ingyhere At my suggestion, but that was more of a quick-and-dirty approach to letting the parsing occur during the call to `parse_args` instead of after. – chepner Sep 02 '22 at 14:38
22

Additionally to nargs, you might want to use choices if you know the list in advance:

>>> parser = argparse.ArgumentParser(prog='game.py')
>>> parser.add_argument('move', choices=['rock', 'paper', 'scissors'])
>>> parser.parse_args(['rock'])
Namespace(move='rock')
>>> parser.parse_args(['fire'])
usage: game.py [-h] {rock,paper,scissors}
game.py: error: argument move: invalid choice: 'fire' (choose from 'rock',
'paper', 'scissors')
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
18

Using nargs parameter in argparse's add_argument method

I use nargs='*' as an add_argument parameter. I specifically used nargs='*' to the option to pick defaults if I am not passing any explicit arguments

Including a code snippet as example:

Example: temp_args1.py

Please Note: The below sample code is written in python3. By changing the print statement format, can run in python2

#!/usr/local/bin/python3.6

from argparse import ArgumentParser

description = 'testing for passing multiple arguments and to get list of args'
parser = ArgumentParser(description=description)
parser.add_argument('-i', '--item', action='store', dest='alist',
                    type=str, nargs='*', default=['item1', 'item2', 'item3'],
                    help="Examples: -i item1 item2, -i item3")
opts = parser.parse_args()

print("List of items: {}".format(opts.alist))

Note: I am collecting multiple string arguments that gets stored in the list - opts.alist If you want list of integers, change the type parameter on parser.add_argument to int

Execution Result:

python3.6 temp_agrs1.py -i item5 item6 item7
List of items: ['item5', 'item6', 'item7']

python3.6 temp_agrs1.py -i item10
List of items: ['item10']

python3.6 temp_agrs1.py
List of items: ['item1', 'item2', 'item3']
LudvigH
  • 3,662
  • 5
  • 31
  • 49
Py_minion
  • 2,027
  • 1
  • 16
  • 20
  • 1
    @Py_minion Is there a way to use a list as an argument, and have the output as list as well? `temp_args1.py -i [item5 ,item6, item7]` and have the output come out as a list as well (instead of nested list) – Moondra Oct 31 '17 at 02:22
  • @Moondra Yes. glad you asked. ``` parser.add_argument('-o', '--options', action='store', dest='opt_list', type=str, nargs='*', default=sample_list, help="String of databases seperated by white space. Examples: \ -o option1 option2, -o option3") ``` Here 'sample_list' is of type list with default options. Ex: sample_list = [option4, option5] – Py_minion Nov 01 '17 at 02:25
  • 1
    @Py_minion Thank you. Going to test it out later today. – Moondra Nov 01 '17 at 15:52
  • I used this, this is very useful for passing creating lists from the arguments. – siby May 28 '19 at 22:08
14

JSON List Solution

A nice way to handle passing lists (also dicts) in via the command line is by using json.

# parse_list.py
import argparse
import json

parser = argparse.ArgumentParser()
# note type arg, used to load json string
parser.add_argument('-l', '--list', type=json.loads)
args = parser.parse_args()
print(args.list)

Example Usage

$ python parse_list.py -l "[265340, 268738, 270774, 270817]"
[265340, 268738, 270774, 270817]

Edit: incorporated the improvement suggested by Katu to remove the separate parsing step.

lunguini
  • 896
  • 1
  • 9
  • 14
9

If you are intending to make a single switch take multiple parameters, then you use nargs='+'. If your example '-l' is actually taking integers:

a = argparse.ArgumentParser()
a.add_argument(
    '-l', '--list',  # either of this switches
    nargs='+',       # one or more parameters to this switch
    type=int,        # /parameters/ are ints
    dest='lst',      # store in 'lst'.
    default=[],      # since we're not specifying required.
)

print a.parse_args("-l 123 234 345 456".split(' '))
print a.parse_args("-l 123 -l=234 -l345 --list 456".split(' '))

Produces

Namespace(lst=[123, 234, 345, 456])
Namespace(lst=[456])  # Attention!

If you specify the same argument multiple times, the default action ('store') replaces the existing data.

The alternative is to use the append action:

a = argparse.ArgumentParser()
a.add_argument(
    '-l', '--list',  # either of this switches
    type=int,        # /parameters/ are ints
    dest='lst',      # store in 'lst'.
    default=[],      # since we're not specifying required.
    action='append', # add to the list instead of replacing it
)

print a.parse_args("-l 123 -l=234 -l345 --list 456".split(' '))

Which produces

Namespace(lst=[123, 234, 345, 456])

Or you can write a custom handler/action to parse comma-separated values so that you could do

-l 123,234,345 -l 456
qneill
  • 1,643
  • 14
  • 18
kfsone
  • 23,617
  • 2
  • 42
  • 74
6

In add_argument(), type is just a callable object that receives string and returns option value.

import ast

def arg_as_list(s):                                                            
    v = ast.literal_eval(s)                                                    
    if type(v) is not list:                                                    
        raise argparse.ArgumentTypeError("Argument \"%s\" is not a list" % (s))
    return v                                                                   


def foo():
    parser.add_argument("--list", type=arg_as_list, default=[],
                        help="List of values")

This will allow to:

$ ./tool --list "[1,2,3,4]"
wonder.mice
  • 7,227
  • 3
  • 36
  • 39
  • Note that if one needed to pass strings, this method would require they quote them appropriately on the command line. A user may find this unexpected. If only parsing integers this is fine. – SethMMorton Jan 09 '18 at 20:59
4

I think the most elegant solution is to pass a lambda function to "type", as mentioned by Chepner. In addition to this, if you do not know beforehand what the delimiter of your list will be, you can also pass multiple delimiters to re.split:

# python3 test.py -l "abc xyz, 123"

import re
import argparse

parser = argparse.ArgumentParser(description='Process a list.')
parser.add_argument('-l', '--list',
                    type=lambda s: re.split(' |, ', s),
                    required=True,
                    help='comma or space delimited list of characters')

args = parser.parse_args()
print(args.list)


# Output: ['abc', 'xyz', '123']
Cloudkollektiv
  • 11,852
  • 3
  • 44
  • 71
  • Did you mean `-l` in the example call? Where did `-n` come from? – Anthony May 19 '20 at 13:22
  • 1
    Also, the solution doesn't work for me in Python 3.8.2. Here is the code: `parser.add_argument('-l', '--list', type = lambda s: re.split('[ ,;]', s))`. Here is the input: `script.py -l abc xyz, abc\nxyz`. Finally, here is the result: `script.py: error: unrecognized arguments: xyz, abcnxyz` – Anthony May 19 '20 at 13:30
  • Fixed the solution according to the comments! – Cloudkollektiv Sep 23 '20 at 12:54
2

If you have a nested list where the inner lists have different types and lengths and you would like to preserve the type, e.g.,

[[1, 2], ["foo", "bar"], [3.14, "baz", 20]]

then you can use the solution proposed by @sam-mason to this question, shown below:

from argparse import ArgumentParser
import json

parser = ArgumentParser()
parser.add_argument('-l', type=json.loads)
parser.parse_args(['-l', '[[1,2],["foo","bar"],[3.14,"baz",20]]'])

which gives:

Namespace(l=[[1, 2], ['foo', 'bar'], [3.14, 'baz', 20]])
Meysam Sadeghi
  • 1,483
  • 2
  • 17
  • 23
1

You can parse the list as a string and use of the eval builtin function to read it as a list. In this case, you will have to put single quotes into double quote (or the way around) in order to ensure successful string parse.

# declare the list arg as a string
parser.add_argument('-l', '--list', type=str)

# parse
args = parser.parse()

# turn the 'list' string argument into a list object
args.list = eval(args.list)
print(list)
print(type(list))

Testing:

python list_arg.py --list "[1, 2, 3]"

[1, 2, 3]
<class 'list'>
Leonard
  • 2,510
  • 18
  • 37
1

Do be advised that if you pass action='append' along with the default argument, Argparse will attempt to append to supplied default values rather than replacing the default value, which you may or may not expect.

Here is one action='append example given in the Argparse Docs. In this case things will work as expected:

>> import argparse
>> parser = argparse.ArgumentParser()
>> parser.add_argument('--foo', action='append')
>> parser.parse_args('--foo 1 --foo 2'.split())

Out[2]: Namespace(foo=['1', '2'])

However, if you opt to provide a default value, Argparse's "append" action will attempt to append to the supplied defaults, rather than replacing the default values:

import argparse
REASONABLE_DEFAULTS = ['3', '4']
parser = argparse.ArgumentParser()
parser.add_argument('--foo', default=REASONABLE_DEFAULTS,action='append')
parser.parse_args('--foo 1 --foo 2'.split())

Out[6]: Namespace(foo=['3', '4', '1', '2'])

If you were expecting Argparse to replace the default values -- such as passing in a tuple as a default, rather than a list -- this can lead to some confusing errors:

import argparse
REASONABLE_DEFAULTS = ('3', '4')
parser = argparse.ArgumentParser()
parser.add_argument('--foo', default=REASONABLE_DEFAULTS,action='append')
parser.parse_args('--foo 1 --foo 2'.split())

AttributeError: 'tuple' object has no attribute 'append'

There is a bug tracking this unexpected behavior, but since it dates from 2012, it's not likely to get resolved.

JS.
  • 14,781
  • 13
  • 63
  • 75
1

Applying chepner's comment to Lunguini's answer:

import argparse, json                                                                                            
parser = argparse.ArgumentParser()                                                                               
parser.add_argument('-l', '--list', type=lambda a: json.loads('['+a.replace(" ",",")+']'), default="", help="List of values")                                              
args = parser.parse_args()                                                                                       
print(args.list)                                                                                                 

Usage:

$ python parse_list.py -l "265340 268738 270774 270817"
[265340, 268738, 270774, 270817]
Katu
  • 1,296
  • 1
  • 24
  • 38
0

I want to handle passing multiple lists, integer values and strings.

Helpful link => How to pass a Bash variable to Python?

def main(args):
    my_args = []
    for arg in args:
        if arg.startswith("[") and arg.endswith("]"):
            arg = arg.replace("[", "").replace("]", "")
            my_args.append(arg.split(","))
        else:
            my_args.append(arg)

    print(my_args)


if __name__ == "__main__":
    import sys
    main(sys.argv[1:])

Order is not important. If you want to pass a list just do as in between "[" and "] and seperate them using a comma.

Then,

python test.py my_string 3 "[1,2]" "[3,4,5]"

Output => ['my_string', '3', ['1', '2'], ['3', '4', '5']], my_args variable contains the arguments in order.

alper
  • 2,919
  • 9
  • 53
  • 102