Firstly, I apologize if my title is misleading/ unclear as I am really not sure what is the best way to put it.
I have 3 optional arguments, which uses action='store_true'. Let's keep the argument flags to -va, -vb, -vc
var_list = ['a', 'b', 'c']
if args.va:
run_this_func(var_list[0])
if args.vb:
run_this_func(var_list[1])
if args.vc:
run_this_func(var_list[2])
if not args.higha and not args.highb and not args.highmem:
for var in var_list:
run_this_func(var)
if args.va and args.vb:
run_this_func(var_list[:-1])
if args.vb and args.vc:
run_this_func(var_list[1:])
if args.vc and args.va:
run_this_func(var_list[0], var_list[2])
How can I code in more efficient way? The above method that I had utilized while it may work, seems more like a roundabout way to get things going...
Initially I am thinking of using tuple such that it will be something such as input = (args.va, args.vb, args.vc) so that it may return me eg. (True, False, False)... Not sure if that is ideal though.
Any advice?
I think you can use tuple too.
var_list = ['a', 'b', 'c']
input = (args.va, args.vb, args.vc)
vars = [item for index, item in enumerate(var_list) if args_tuple[index]]
run_this_func(*vars)
I think this is a case where argparse.add_argument's nargs and choices keywords can be used to get the list that you want (some subset of [a,b,c]) without having to filter based on multiple arguments.
I would recommend doing something like this:
parser = argparse.ArgumentParser()
parser.add_argument('-v', choices=['a','b','c'], nargs='+')
args = parser.parse_args()
This says: create an optional argument -v that can take one or multiple values but each of those values must be one of ['a','b','c']
Then you can pass it arguments from the command line like this (for example):
$ python my_file.py -v a c
which means that args will look like: Namespace(v=['a', 'c'])
and args.v looks like ['a', 'c'] which is the list you were looking for (and without any filtering!)
You can then call your function as:
run_this_func(args.v)
Related
I have an argparse argument example that accepts a variable number of strings:
parser.add_argument('--example', nargs='*', required=False, default='')
This works fine when using the CLI as usual with command-line input: script.py --example ab cd.
I also want this to work with arguments that are passed to parser.parse_args(args) through the args argument from within the program code.
But when I try to pass parser.parse_args(["--example", "ab cd"]), it's interpreted as single string "ab cd" and not as a list of ab and cd.
Similarly, passing parser.parse_args(["--example", "ab", "cd"]) is somehow interpreted as single argument, which is a list. So, when accessing args.example[0] I get the list and args.example[1] gets me an error.
As your link shows, you can give parse_args a list of strings.
In [210]: parser = argparse.ArgumentParser()
In [211]: parser.add_argument('-i','--image_types', nargs='*', default='');
To test parsing without any commandline arguments, by giving it an empty list:
In [212]: parser.parse_args([])
Out[212]: Namespace(image_types='')
With a list of strings:
In [213]: parser.parse_args(['-i','a','b'])
Out[213]: Namespace(image_types=['a', 'b'])
or split a string:
In [214]: parser.parse_args('-i a b'.split())
Out[214]: Namespace(image_types=['a', 'b'])
Answers to argparse questions often use one of these forms to illustrate their actions. The split is convenient.
It is also possible to create an args Namespace directly:
In [215]: argparse.Namespace(image_types=['a','b','c'])
Out[215]: Namespace(image_types=['a', 'b', 'c'])
I don't follow your interpretation of the result for
parser.parse_args(["--example", "ab", "cd"])
That should produce a args.example that is ["ab", "cd"].
This isn't a good test case:
parser.parse_args(["--example", "ab cd"])
to produce the same thing from the commandline you'd have to use
python --example "ab cd"
The quotes override the normal split on white space. You have to use shlex.split to emulate that behavior.
It is tricky for argparse to handle lists directly use a csv list instead:
parser.add_argument('--image_types', \
help='csv list of imagetypes', \
default='iff,gif,jpeg,png', default=None)
if args.image_types:
args.image_types = args.image_types.split(',')
I am trying to build a command line parser that will be able to share between arguments the values passed in order to avoid having to type them multiple times. Said otherwise, I would like the namespaces of both argument to be identical:
import argparse
class PrintAction(argparse.Action):
def __init__(self, option_strings, dest, **kwargs):
super(PrintAction, self).__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
for val in values:
print(val)
parser = argparse.ArgumentParser(description='A foo that foos and a bar that bars')
parser.add_argument('--foo', action=PrintAction)
parser.add_argument('bar', nargs='+')
args = parser.parse_args(['--foo', 'a', 'b', 'c']) # Case 1
args = parser.parse_args(['a', 'b', 'c']) # Case 2
I would then like a solution that stores in both cases ['a', 'b', 'c'] in bar but also that in the case that --foo is provided, then a, b and c would be printed.
For now, what I get is foo prints only a and bar stores only b and c in case 1 and the correct result in case 2.
You need to make --foo a boolean flag. Now it's a string parameter, because you did not state otherwise. Set action to store_true for the boolean flag effect.
The final solution would look like:
def print_args(args):
if args.foo:
for val in args.bar:
print(val)
parser = argparse.ArgumentParser(description='A foo that foos and a bar that bars')
parser.add_argument('--foo', action='store_true')
parser.add_argument('bar', nargs='+')
args = parser.parse_args(['--foo', 'a', 'b', 'c']) # Case 1
args = parser.parse_args(['a', 'b', 'c']) # Case 2
Then calling print_args(args) in the first case will print a, b and c and in the second case, it won't.
You can't (readily) trick the argparse into reusing argv strings. The parser allocates values to the Actions.
The default nargs is None, which means, use the the next string as an argument.
parser.add_argument('--foo')
would set foo='a', and bar=['b','c'].
In your Action, values will be ['a'], which you print. In optparse each option gets the remaining argv list, which it can consume as it wants. In argparse it only gets the values that its nargs demands.
You could specify in the __init__ that the nargs=0, and then print from sys.argv. Eqivalently, as #9000 suggests, make it a store_true and print after parsing. Look at the code for the store_true Action class.
Another option is to give both foo and bar a *, and have foo both print and save to the bar dest. Then foo would consume all following strings. But, if bar doesn't have anything to save, it might write [] to the namespace.
In any case, the best you can do is fake the repeated use.
Another idea is to use 2 different parsers with parse_known_args. Parsers don't mess with the sys.argv, so it can read and parsed multiple times.
I have an argparser, with two subsets of arguments, inputs and parameters. My command looks something like
program -input1 <input1> -input2 <input2> -p1 <param1> -p2 <param2>
I'd love to
args = parser.parse_args()
params = vars(args.params)
instead of
params = {"p1": args.p1, "p2": args.p2, etc...}
Is there an elegant way to do something like:
parser.add_argument("-p1", dest='p1', part_of=params)
Subparsers don't seem like they're made for this. Or are they?
Argparse has argument groups, but they seem like they're just for the help text.
Thanks!
This sounds like a variation on the - how do I accept arbitrary 'key=value' pairs? It's been asked in various ways over the years, with various answers.
In a recent one:
Parsing "python foo.py -DVAR1=9 -DVAR2=Off" with argparse
my solution was to split -DVAR1=9 into ('VAR1',9) and append that to the D attribute. That uses a custom type.
Using variable arg names with argparse - this processes the sys.argv before passing it to the parser.
python argparse store --foo=bar as args.key='foo', args.value='bar'
suggests a custom Action class.
I think we've also suggested a custom Namespace class.
The builtin mechanisms for grouping values are nargs and append action. Together you can get attributes which are lists of lists.
JSON strings can also be used to input complex data structures.
class MyAction(argparse._StoreAction):
def __call__(self, parser, namespace, values, option_string=None):
print('storing', option_string)
arg = getattr(namespace, self.dest)
if arg is None:
arg = {}
arg[option_string] = values
setattr(namespace, self.dest, arg)
In [135]: p=argparse.ArgumentParser()
In [136]: p.add_argument('--p1',action=MyAction,dest='p');
In [137]: p.add_argument('--p2',action=MyAction,dest='p');
In [138]: p.parse_args('--p1 one --p2 two'.split())
storing --p1
storing --p2
Out[138]: Namespace(p={'--p2': 'two', '--p1': 'one'})
In [139]: _.p
Out[139]: {'--p1': 'one', '--p2': 'two'}
Obviously this could be refined in various ways - trimming the keys to 'p1' or even '1', saving to a list, or nested Namespace or other custom structure, etc.
This approach still requires that you define an add_argument for each '--pn` variation.
An alternative is keep argparse simple, producing a namespace like:
In [141]: argparse.Namespace(p1='one', p2='two', input1=1, input2=3)
Out[141]: Namespace(input1=1, input2=3, p1='one', p2='two')
and then do your own grouping afterwards.
In [142]: args=argparse.Namespace(p1='one', p2='two', input1=1, input2=3)
In [143]: {key:getattr(args,key) for key in ['p1','p2']}
Out[143]: {'p1': 'one', 'p2': 'two'}
I'm trying to write a script that accepts multiple input sources and does something to each one. Something like this
./my_script.py \
-i input1_url input1_name input1_other_var \
-i input2_url input2_name input2_other_var \
-i input3_url input3_name
# notice inputX_other_var is optional
But I can't quite figure out how to do this using argparse. It seems that it's set up so that each option flag can only be used once. I know how to associate multiple arguments with a single option (nargs='*' or nargs='+'), but that still won't let me use the -i flag multiple times. How do I go about accomplishing this?
Just to be clear, what I would like in the end is a list of lists of strings. So
[["input1_url", "input1_name", "input1_other"],
["input2_url", "input2_name", "input2_other"],
["input3_url", "input3_name"]]
Here's a parser that handles a repeated 2 argument optional - with names defined in the metavar:
parser=argparse.ArgumentParser()
parser.add_argument('-i','--input',action='append',nargs=2,
metavar=('url','name'),help='help:')
In [295]: parser.print_help()
usage: ipython2.7 [-h] [-i url name]
optional arguments:
-h, --help show this help message and exit
-i url name, --input url name
help:
In [296]: parser.parse_args('-i one two -i three four'.split())
Out[296]: Namespace(input=[['one', 'two'], ['three', 'four']])
This does not handle the 2 or 3 argument case (though I wrote a patch some time ago for a Python bug/issue that would handle such a range).
How about a separate argument definition with nargs=3 and metavar=('url','name','other')?
The tuple metavar can also be used with nargs='+' and nargs='*'; the 2 strings are used as [-u A [B ...]] or [-u [A [B ...]]].
This is simple; just add both action='append' and nargs='*' (or '+').
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-i', action='append', nargs='+')
args = parser.parse_args()
Then when you run it, you get
In [32]: run test.py -i input1_url input1_name input1_other_var -i input2_url i
...: nput2_name input2_other_var -i input3_url input3_name
In [33]: args.i
Out[33]:
[['input1_url', 'input1_name', 'input1_other_var'],
['input2_url', 'input2_name', 'input2_other_var'],
['input3_url', 'input3_name']]
-i should be configured to accept 3 arguments and to use the append action.
>>> p = argparse.ArgumentParser()
>>> p.add_argument("-i", nargs=3, action='append')
_AppendAction(...)
>>> p.parse_args("-i a b c -i d e f -i g h i".split())
Namespace(i=[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']])
To handle an optional value, you might try using a simple custom type. In this case, the argument to -i is a single comma-delimited string, with the number of splits limited to 2. You would need to post-process the values to ensure there are at least two values specified.
>>> p.add_argument("-i", type=lambda x: x.split(",", 2), action='append')
>>> print p.parse_args("-i a,b,c -i d,e -i g,h,i,j".split())
Namespace(i=[['a', 'b', 'c'], ['d', 'e'], ['g', 'h', 'i,j']])
For more control, define a custom action. This one extends the built-in _AppendAction (used by action='append'), but just does some range checking on the number of arguments given to -i.
class TwoOrThree(argparse._AppendAction):
def __call__(self, parser, namespace, values, option_string=None):
if not (2 <= len(values) <= 3):
raise argparse.ArgumentError(self, "%s takes 2 or 3 values, %d given" % (option_string, len(values)))
super(TwoOrThree, self).__call__(parser, namespace, values, option_string)
p.add_argument("-i", nargs='+', action=TwoOrThree)
Adding with Other in this Thread.
If you use action='append' in add_argument() then you will get arguments in list(s) within a list every time you add the option.
As you liked:
[
["input1_url", "input1_name", "input1_other"],
["input2_url", "input2_name", "input2_other"],
["input3_url", "input3_name"]
]
But if anyone wants those arguments in the same list[], then use action='extend' instead of action='append' in your code. This will give you those arguments in a single list.
[
"input1_url",
"input1_name",
"input1_other",
"input2_url",
"input2_name",
"input2_other",
"input3_url",
"input3_name"
]
I'm looking for a nicer way to assign a set with the conent of a list if such list is not empty, otherwise another list should be used.
If it is possible I'd like a nicer way to write this (or an argument to why this is the nicest way):
if args.onlyTheseServers:
only = set(args.onlyTheseServers)
else:
only = set(availableServers)
only = set(args.onlyTheseServers or availableServers)
Looking at your previous question, I'd say that what you're really looking for is a way to assign a default value to a missing parameter using argparse. In that case you should just use default as follows:
parser.add_argument('-o', '--only', default=default_servers, ...)
This way, when the -o/--only option isn't passed, the namespace will have the default value correctly set.
args.onlyTheseServers seems a variable coming from argparse.
If that's your case you should check the default argument and the set_default() method.
Here's an example:
>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo', nargs='*', default=['1', '2', '3'])
>>> args = parser.parse_args()
>>> args.foo
['1', '2', '3']
>>> args = parser.parse_args(['--foo', 'a', 'b'])
>>> args.foo
['a', 'b']
Not really much better, but at least a bit shorter:
only = set(availableServers if args.onlyTheseServers is None
else args.onlyTheseServers)
You can also do
only = set(args.onlyTheseServers or availableServers)
It works slightly different as it does not test for None, but only if the argument is true-is - in this case it should work though.
Call me crazy buy I like this better
only = set(args.onlyTheseServers) if args.onlyTheseServers is not None else set(availableServers)