I have a simple argparse script that takes two arguments; --encode and --decode. I want to make the --decode default if no argument is given. How can I do so?
I want this:
myscript.py --decode "some encoded string here"
to happen when I do:
myscript.py "some encoded string here"
by default.
Have a look at the 'store_true' action on the python documentation, or even the default keyword on the add argument method
You'll need to implement some logic, but here the idea:
parser.add_argument('--decode', rest_of_options..., default=True)
parser.add_argument('--encode', rest_of_options..., default=False)
values = parser.parse_args()
if values.decode:
do_some_stuff
elif values.encode:
do_some_other_stuff
In argparse.ArgumentParser() object when you use method: add_argument is a flag default="some value" for example:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--test', nargs='?', const=1, type=int)
args = parser.parse_args()
print(args)
output:
>python test.py
Namespace(test=None)
add the default flag:
parser.add_argument('--test', nargs='?', const=1, type=int, default=2)
after add_argument change:
>python test.py
Namespace(test=2)
You can use the default parameter to specify a default, along with dest to control the variable name of the option:
import argparse
p = argparse.ArgumentParser()
# --decode and --encode are usually considered mutually exclusive; this enforces that constraint
g = p.add_mutually_exclusive_group()
# Order matters: the first default for a given dest is used
g.add_argument('--decode', dest='action', action='store_const', const='decode', default='decode')
g.add_argument('--encode', dest='action', action='store_const', const='encode')
This approximates what you want, except for the missing '--'
p.add_argument('action',choices=['decode','encode'],default='decode',nargs='?')
p.add_argument('astring')
In [8]: p.parse_args(["a string"])
Out[8]: Namespace(action='decode', astring='a string')
In [9]: p.parse_args(['decode',"a string"])
Out[9]: Namespace(action='decode', astring='a string')
In [10]: p.parse_args(['encode',"a string"])
Out[10]: Namespace(action='encode', astring='a string')
If you must have the '--', nneonneo's solution is fine, producing the same namespaces. Both arguments write to the same destination attribute, and that attribute by default is 'decode'.
p.add_argument('--decode', dest='action', action='store_const', const='decode', default='decode')
p.add_argument('--encode', dest='action', action='store_const', const='encode')
If you don't use a mutually exclusive group, the last argument will have final say ('--decode --encode "a string to be encoded"')
Related
It seems that setting a tuple as a metavar for positional argument and requesting help does not work:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('size', type=int, nargs=2, help='size', metavar=('w', 'h'))
args = parser.parse_args()
print(args)
This produces an error when called as prog.py --help. The error differs between Python3 versions (I tried 3.5, 3.6, 3.8) and includes ValueError: too many values to unpack (expected 1) or TypeError: sequence item 0: expected str instance, tuple found. See live example on Wandbox.
For optional arguments, all is good:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--size', type=int, nargs=2, help='size', metavar=('w', 'h'))
args = parser.parse_args()
print(args)
Live example on Wandbox.
Is my code invalid, or did I find a bug in Python implementation?
Please note that simply parsing the arguments works as expected.
Last part of the traceback is
/usr/lib/python3.6/argparse.py in _format_action_invocation(self, action)
550 if not action.option_strings:
551 default = self._get_default_metavar_for_positional(action)
--> 552 metavar, = self._metavar_formatter(action, default)(1)
553 return metavar
554
So yes, it's specifically occurring with positionals (empty option_strings). The metavar, = ... assignment only works with the RHS returns one item. With your metavar it's returning a 2.
Usage displays ok
In [36]: parser.print_usage()
usage: ipython3 [-h] w h
It does look like a bug.
The (1) argument tells the function is should return a 1 element tuple:
metavar, = self._metavar_formatter(action, default)(1)
I suspect the issue has been raised already in Python bug/issues. I'll look for it later.
Instead of metavar, you could just as well use two positional arguments:
parser = argparse.ArgumentParser()
parser.add_argument('w', type=int)
parser.add_argument('h', type=int)
This has been a known bug for a long time - but so far no action:
https://bugs.python.org/issue14074
argparse allows nargs>1 for positional arguments but doesn't allow metavar to be a tuple
Following hpaulj's answer, here's another workaround using action='append':
for name in 'width', 'height':
parser.add_argument('size', type=int, help=name, metavar=name[0], action='append')
args = parser.parse_args(['4', '3'])
print(args)
parser.print_help()
Output:
Namespace(size=[4, 3])
usage: test.py [-h] w h
positional arguments:
w width
h height
I'm using argparse and I know that for a particular argument I can set action='append' and then every time I set that argument the value gets appended, e.g.
parser = argparse.ArgumentParser()
parser.add_argument("-a", type=int, action="append")
args = parser.parse_args(["-a", "5", "-a", "6"])
print(args)
the output to this is:
Namespace(a=[5, 6])
For my application I would like to type in an argument name -img followed by sub-arguments, path a positional sub-argument of type string and val an optional sub-argument of type int which defaults to zero.
So, corresponding to code:
args = parser.parse_args(["-img", "/path-to-img-0", "5",
"-img", "/path-to-img-1"])
I would like to get:
args = Namespace(path=["/path-to-img-0", "/path-to-img-1"],
val=[5, 0])
How can i achieve this?
I thought that nargs='*' was enough to handle a variable number of arguments. Apparently it's not, and I don't understand the cause of this error.
The code:
p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest='spam')
p.add_argument('vars', nargs='*')
p.parse_args('1 2 --spam 8 8 9'.split())
I think the resulting namespace should be Namespace(pos='1', foo='2', spam='8', vars=['8', '9']). Instead, argparse gives this error:
usage: prog.py [-h] [--spam SPAM] pos foo [vars [vars ...]]
error: unrecognized arguments: 9 8
Basically, argparse doesn't know where to put those additional arguments... Why is that?
For anyone who doesn't know what is nargs:
nargs stands for Number Of Arguments
3: 3 values, can be any number you want
?: a single value, which can be optional
*: a flexible number of values, which will be gathered into a list
+: like *, but requiring at least one value
argparse.REMAINDER: all the values that are remaining in the command
line
Example:
Python
import argparse
my_parser = argparse.ArgumentParser()
my_parser.add_argument('--input', action='store', type=int, nargs=3)
args = my_parser.parse_args()
print(args.input)
Console
$ python nargs_example.py --input 42
usage: nargs_example.py [-h] [--input INPUT INPUT INPUT]
nargs_example.py: error: argument --input: expected 3 arguments
$ python nargs_example.py --input 42 42 42
[42, 42, 42]
See more
The relevant Python bug is Issue 15112.
argparse: nargs='*' positional argument doesn't accept any items if preceded by an option and another positional
When argparse parses ['1', '2', '--spam', '8', '8', '9'] it first tries to match ['1','2'] with as many of the positional arguments as possible. With your arguments the pattern matching string is AAA*: 1 argument each for pos and foo, and zero arguments for vars (remember * means ZERO_OR_MORE).
['--spam','8'] are handled by your --spam argument. Since vars has already been set to [], there is nothing left to handle ['8','9'].
The programming change to argparse checks for the case where 0 argument strings is satisfying the pattern, but there are still optionals to be parsed. It then defers the handling of that * argument.
You might be able to get around this by first parsing the input with parse_known_args, and then handling the remainder with another call to parse_args.
To have complete freedom in interspersing optionals among positionals, in issue 14191, I propose using parse_known_args with just the optionals, followed by a parse_args that only knows about the positionals. The parse_intermixed_args function that I posted there could be implemented in an ArgumentParser subclass, without modifying the argparse.py code itself.
Here's a way of handling subparsers. I've taken the parse_known_intermixed_args function, simplified it for presentation sake, and then made it the parse_known_args function of a Parser subclass. I had to take an extra step to avoid recursion.
Finally I changed the _parser_class of the subparsers Action, so each subparser uses this alternative parse_known_args. An alternative would be to subclass _SubParsersAction, possibly modifying its __call__.
from argparse import ArgumentParser
def parse_known_intermixed_args(self, args=None, namespace=None):
# self - argparse parser
# simplified from http://bugs.python.org/file30204/test_intermixed.py
parsefn = super(SubParser, self).parse_known_args # avoid recursion
positionals = self._get_positional_actions()
for action in positionals:
# deactivate positionals
action.save_nargs = action.nargs
action.nargs = 0
namespace, remaining_args = parsefn(args, namespace)
for action in positionals:
# remove the empty positional values from namespace
if hasattr(namespace, action.dest):
delattr(namespace, action.dest)
for action in positionals:
action.nargs = action.save_nargs
# parse positionals
namespace, extras = parsefn(remaining_args, namespace)
return namespace, extras
class SubParser(ArgumentParser):
parse_known_args = parse_known_intermixed_args
parser = ArgumentParser()
parser.add_argument('foo')
sp = parser.add_subparsers(dest='cmd')
sp._parser_class = SubParser # use different parser class for subparsers
spp1 = sp.add_parser('cmd1')
spp1.add_argument('-x')
spp1.add_argument('bar')
spp1.add_argument('vars',nargs='*')
print parser.parse_args('foo cmd1 bar -x one 8 9'.split())
# Namespace(bar='bar', cmd='cmd1', foo='foo', vars=['8', '9'], x='one')
Simple solution: Specify the --spam flag before specifying pos and foo:
p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest='spam')
p.add_argument('vars', nargs='*')
p.parse_args('--spam 8 1 2 8 9'.split())
The same works if you place the --spam flag after specifying your variable arguments.
p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest='spam')
p.add_argument('vars', nargs='*')
p.parse_args('1 2 8 9 --spam 8'.split())
EDIT: For what it's worth, it seems that changing the * to a + will also fix the error.
p = argparse.ArgumentParser()
p.add_argument('pos')
p.add_argument('foo')
p.add_argument('--spam', default=24, type=int, dest='spam')
p.add_argument('vars', nargs='+')
p.parse_args('1 2 --spam 8 8 9'.split())
If you expect to have at least one optional argument then p.add_argument('vars', nargs='+') will work in your specific case
I spent some times on the argparse documentation, but I'm still struggling with this module for one option in my program:
parser.add_argument("-r", "--rmsd", dest="rmsd", nargs=2,
help="extract the poses that are close from a ref according RMSD",
metavar=("ref","rmsd"))
I'd like to the first argument to be a string (type str) and mandatory, while the second argument should have type int, and if no value is given have a default one (let's say default=50). I know how to do that when there is only one argument expected, but I have no idea how to proceed when nargs=2... Is that even possible?
You can do the following. The required keyword sets the field mandatory and the default=50 sets the default value of the option to 50 if not specified:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-s", "--string", type=str, required=True)
parser.add_argument("-i", "--integer", type=int, default=50)
args = parser.parse_args()
print args.string
print args.integer
Output:
$ python arg_parser.py -s test_string
test_string
50
$ python arg_parser.py -s test_string -i 100
test_string
100
$ python arg_parser.py -i 100
usage: arg_parser.py [-h] -s STRING [-i INTEGER]
arg_parser.py: error: argument -s/--string is required
I tend to agree with Mike's solution, but here's another way. It's not ideal, since the usage/help string tells the user to use 1 or more arguments.
import argparse
def string_integer(int_default):
"""Action for argparse that allows a mandatory and optional
argument, a string and integer, with a default for the integer.
This factory function returns an Action subclass that is
configured with the integer default.
"""
class StringInteger(argparse.Action):
"""Action to assign a string and optional integer"""
def __call__(self, parser, namespace, values, option_string=None):
message = ''
if len(values) not in [1, 2]:
message = 'argument "{}" requires 1 or 2 arguments'.format(
self.dest)
if len(values) == 2:
try:
values[1] = int(values[1])
except ValueError:
message = ('second argument to "{}" requires '
'an integer'.format(self.dest))
else:
values.append(int_default)
if message:
raise argparse.ArgumentError(self, message)
setattr(namespace, self.dest, values)
return StringInteger
And with that, you get:
>>> import argparse
>>> parser = argparse.ArgumentParser(description="")
parser.add_argument('-r', '--rmsd', dest='rmsd', nargs='+',
... action=string_integer(50),
... help="extract the poses that are close from a ref "
... "according RMSD")
>>> parser.parse_args('-r reference'.split())
Namespace(rmsd=['reference', 50])
>>> parser.parse_args('-r reference 30'.split())
Namespace(rmsd=['reference', 30])
>>> parser.parse_args('-r reference 30 3'.split())
usage: [-h] [-r RMSD [RMSD ...]]
: error: argument -r/--rmsd: argument "rmsd" requires 1 or 2 arguments
>>> parser.parse_args('-r reference 30.3'.split())
usage: [-h] [-r RMSD [RMSD ...]]
: error: argument -r/--rmsd: second argument to "rmsd" requires an integer
Sorry for jumping in way late. I'd use a function for type to call.
def two_args_str_int(x):
try:
return int(x)
except:
return x
parser.add_argument("-r", "--rmsd", dest="rmsd", nargs=2, type=two_args_str_int
help="extract the poses that are close from a ref according RMSD",
metavar=("ref","rmsd"))
I would recommend using two arguments:
import argparse
parser = argparse.ArgumentParser(description='Example with to arguments.')
parser.add_argument('-r', '--ref', dest='reference', required=True,
help='be helpful')
parser.add_argument('-m', '--rmsd', type=int, dest='reference_msd',
default=50, help='be helpful')
args = parser.parse_args()
print args.reference
print args.reference_msd
I had a similar problem, but "use two arguments" approach didn't work for me because I need a list of pairs: parser.add_argument('--replace', nargs=2, action='append') and if I use separate arguments then I would have to validate lengths of lists etc.
Here is what I did:
Use tuple for metavar to properly show help: tuple=('OLD', 'NEW') results in the help string being displayed as --replace OLD NEW. It is documented but I could not find it until tried different options.
Use custom validation: after parse_args, validate the resulting list's items and call parser.error() if something is wrong. That's because they have different data types.
The title pretty much says it all. If I have nargs greater than 1, is there any way I can set restrictions (such as choice/type) on the individual args parsed?
This is some example code:
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--credits', nargs=2,
help='number of credits required for a subject')
For the -c argument I need to specify a subject and how many credits are required. The subject should be limited to a predefined list of subjects, and the number of credits required should be a float.
I could probably do this with a subparser, but as it is this is already part of a sub-command so I don't really want things to get any more complicated.
You can validate it with a custom action:
import argparse
import collections
class ValidateCredits(argparse.Action):
def __call__(self, parser, args, values, option_string=None):
# print '{n} {v} {o}'.format(n=args, v=values, o=option_string)
valid_subjects = ('foo', 'bar')
subject, credits = values
if subject not in valid_subjects:
raise ValueError('invalid subject {s!r}'.format(s=subject))
credits = float(credits)
Credits = collections.namedtuple('Credits', 'subject required')
setattr(args, self.dest, Credits(subject, credits))
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--credits', nargs=2, action=ValidateCredits,
help='subject followed by number of credits required',
metavar=('SUBJECT', 'CREDITS')
)
args = parser.parse_args()
print(args)
print(args.credits.subject)
print(args.credits.required)
For example,
% test.py -c foo 2
Namespace(credits=Credits(subject='foo', required=2.0))
foo
2.0
% test.py -c baz 2
ValueError: invalid subject 'baz'
% test.py -c foo bar
ValueError: could not convert string to float: bar
Side note, because this question turns up when searching for "argparse nargs choices":
A custom action is only needed if the nargs arguments require a heterogenous type validation, i.e., the argument at index 0 should be a different type (here: limited type of subjects) than the argument at index 1 (here: float) etc.
If a homogenous type validation is desired, it is sufficient to combine nargs with choices directly. For instance:
parser.add_argument(
"--list-of-xs-or-ys",
nargs="*",
choices=["x", "y"],
)
would allow anything like --list-of-xs-or-ys x y x y, but would complain if the user specifies anything else than x or y.
A caller of a Action class only catch a ArgumentError.
https://github.com/python/cpython/blob/3.8/Lib/argparse.py#L1805
For expecting to catch an exception by a caller, You should raise as following in your custom action.
raise ArgumentError(self, 'invalid subject {s!r}'.format(s=subject))
i suppose you could try this - in add_argument(), you can specify a limited set of inputs with choice='xyz' or choice=[this, that]
as described here:
http://docs.python.org/library/argparse.html#choices
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--credits', choice='abcde', nargs=2,
help='number of credits required for a subject')