How to Pass a List as a Command-Line Argument with Argparse

How can I pass a list as a command-line argument with argparse?

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.

python argh/argparse: How can I pass a list as a command-line argument?

With argparse, you just use type=int

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-a', '--arg', nargs='+', type=int)
print parser.parse_args()

Example output:

$ python test.py -a 1 2 3
Namespace(arg=[1, 2, 3])

Edit: I'm not familiar with argh, but it seems to be just a wrapper around argparse and this worked for me:

import argh

@argh.arg('-a', '--arg', nargs='+', type=int)
def main(args):
print args

parser = argh.ArghParser()
parser.add_commands([main])
parser.dispatch()

Example output:

$ python test.py main -a 1 2 3
Namespace(arg=[1, 2, 3], function=<function main at 0x.......>)

How to pass and parse a list of strings from command line with argparse.ArgumentParser in Python?

You need to define --names-list to take an arbitrary number of arguments.

parser.add_argument('-n', '--names-list', nargs='+', default=[])

Note that options with arbitrary number of arguments don't typically play well with positional arguments, though:

# Is this 4 arguments to -n, or
# 3 arguments and a single positional argument, or ...
myprog.py -n a b c d

How can I pass command line arguments contained in a file and retain the name of that file?

From my testing, using fromfile_prefix_chars means that argparse will not actually pass the argument to your program. Instead, argparse sees the @args.txt, intercepts it, reads from it, and passes the arguments without @args.txt to your program. This is presumably because most people don't really need the filename, just need the arguments within, so argparse saves you the trouble of creating another argument to store something you don't need.

Unfortunately, all of the arguments are stored as local variables in argparse.py, so we cannot access them. I suppose that you could override some of argparse's functions. Keep in mind that this is a horrible, disgusting, hacky solution and I feel that parsing sys.argv is 100% better.

from argparse import ArgumentParser

# Most of the following is copied from argparse.py
def customReadArgs(self, arg_strings):
# expand arguments referencing files
new_arg_strings = []
for arg_string in arg_strings:

# for regular arguments, just add them back into the list
if not arg_string or arg_string[0] not in self.fromfile_prefix_chars:
new_arg_strings.append(arg_string)

# replace arguments referencing files with the file content
else:
try:
fn = arg_string[1:]
with open(fn) as args_file:

# What was changed: before was []
arg_strings = [fn]

for arg_line in args_file.read().splitlines():
for arg in self.convert_arg_line_to_args(arg_line):
arg_strings.append(arg)
arg_strings = self._read_args_from_files(arg_strings)
new_arg_strings.extend(arg_strings)
except OSError:
err = _sys.exc_info()[1]
self.error(str(err))

# return the modified argument list
return new_arg_strings
ArgumentParser._read_args_from_files = customReadArgs
parser = ArgumentParser(fromfile_prefix_chars='@')
parser.add_argument('filename', nargs='?')
parser.add_argument('--foo', nargs='?', default=1)
parser.add_argument('--bar', nargs='?', default=1)
args = parser.parse_args()
print(args)

How to pass an entire list as command line argument in Python when the list comes from a json file

In short: don't join.

More specifically:

$ python app_parsing_lists.py --lista $(echo '{"field": [6, 7, 8]}' | jq -r .field[])
lista: ['6', '7', '8']

In long: consider these examples, all using %r --

$ inlist=("4" "5" "6")
$ python app_parsing_lists.py --lista ${inlist[@]}
lista: ['4', '5', '6']
$ python app_parsing_lists.py --lista "${inlist[@]}"
lista: ['4', '5', '6']
$ python app_parsing_lists.py --lista $(jq -n '5,6,7')
lista: ['5', '6', '7']
$ inlist=($(jq -n '5,6,7'))
$ python app_parsing_lists.py --lista $inlist
lista: ['5']
$ python app_parsing_lists.py --lista "$inlist"
lista: ['5']
$ python app_parsing_lists.py --lista ${inlist[@]}
lista: ['5', '6', '7']
$

Caveat

If any of the list items contains a space, a different approach will be needed, as illustrated by:

$ lista=("a b" 2 3)

$ for i in "${lista[@]}" ; do echo $i ; done
a b
2
3

$ python app_parsing_lists.py --lista "${lista[@]}"
lista: ['a b', '2', '3']

$ lista=($(echo '{"field": ["a b", 7, 8]}' | jq .field[]))
$ python app_parsing_lists.py --lista "${lista[@]}"
lista: ['"a', 'b"', '7', '8']
Solution
unset lista
while IFS= read -r line ; do lista+=($line)
done < <(printf "%s\n" '{"field": ["a b", 7, 8]}' | jq -r .field[])

$ python app_parsing_lists.py --lista ${lista[@]}
lista: ['a b', '7', '8']
$

How to pass an entire list as command line argument in Python?

Command line arguments are always passed as strings. You will need to parse them into your required data type yourself.

>>> input = "[2,3,4,5]"
>>> map(float, input.strip('[]').split(','))
[2.0, 3.0, 4.0, 5.0]
>>> A = map(float, input.strip('[]').split(','))
>>> print(A, type(A))
([2.0, 3.0, 4.0, 5.0], <type 'list'>)

There are libraries like argparse and click that let you define your own argument type conversion but argparse treats "[2,3,4]" the same as [ 2 , 3 , 4 ] so I doubt it will be useful.

edit Jan 2019 This answer seems to get a bit of action still so I'll add another option taken directly from the argparse docs.

You can use action=append to allow repeated arguments to be collected into a single list.

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

In this case you would pass --foo ? once for each list item. Using OPs example: python filename.py --foo 2 --foo 3 --foo 4 --foo 5 would result in foo=[2,3,4,5]

Is there a way to pass list as one of the arguments in argparse

nargs lets you do something close:

parser.add_argument('--list', action='append', nargs='+')

will produce [['currency'], ['RSD', 'EUR']]. One drawback is that no positional arguments can be used immediate after --list, as they will be consumed as arguments of --list itself.

How to support List/Range of arguments in argparse?

You could try something like this:

parser.add_argument('IP_Address', nargs='+', action='store', type=str)

This would only solve your first question.

nargs='*' might also be possible although does not seem like it from your use case

For the second, you need to specify the rules of the range creation.

Getting TypeError when passing and setting command line argument in python

you are missing the nargs parameter from the add_argument call.

something like the following should give you optional positional command line arguments.

def parse_opt(known=False):
parser = argparse.ArgumentParser()
parser.add_argument('c', nargs='?', default='courses.csv', help='path to course.csv file')
parser.add_argument('s', nargs='?', default='students.csv', help='path to students.csv file')
parser.add_argument('t', nargs='?', default='tests.csv', help='path to tests.csv file')
parser.add_argument('m', nargs='?', default='marks.csv', help='path to marks.csv file')
parser.add_argument('o', nargs='?', default='output.json', help='path to output.json file')

return parser

secondly, when making the command line call, you should not use commas to separate the command line arguments, instead:

python readFile.py /c/courses.tsv

the output for printing the args after making a call t parser.parse_args() is:

Namespace(c='C:/courses.tsv', m='marks.csv', o='output.json', s='students.csv', t='tests.csv')

notice how it only took my first supplied argument, and defaulted the rest to values provided in the add_argument call.



Related Topics



Leave a reply



Submit