How to disable an argument if another is present? - python

In my script argparse there are some arguments that will conflict if another argument of opposite kind is passed. I want to disable the --arg2 if --arg1 is already present. Currently I haven't found any way to do so.

Use a mutually exclusive group:
parser = argparse.ArgumentParser(prog='PROG')
group = parser.add_mutually_exclusive_group()
group.add_argument('--arg1')
group.add_argument('--arg2')
Only one argument in the group is allowed to be used.
Demo:
>>> import argparse
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> group = parser.add_mutually_exclusive_group()
>>> group.add_argument('--arg1')
_StoreAction(option_strings=['--arg1'], dest='arg1', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
>>> group.add_argument('--arg2')
_StoreAction(option_strings=['--arg2'], dest='arg2', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args(['--arg1', 'foo'])
Namespace(arg1='foo', arg2=None)
>>> parser.parse_args(['--arg2', 'bar'])
Namespace(arg1=None, arg2='bar')
>>> parser.parse_args(['--arg1', 'foo', '--arg2', 'bar'])
usage: PROG [-h] [--arg1 ARG1 | --arg2 ARG2]
PROG: error: argument --arg2: not allowed with argument --arg1

Related

Get argument names from a parser, without parsing arguments in argparse python

I have the following argument parser in a python script make_parser.py :
import argparse
parser = argparse.ArgumentParser(
description='Multiplies the input <n> by <m>'
)
parser.add_argument(
'n', type=float, help='Input number to be multiplied'
)
parser.add_argument(
'-m', '--optional-multiplier',
type=float, default=1,
help='multiplier for <n> : (default: 1)'
)
How do I get the positional and optional argument names and their details, possibly as some object, or just as a dict.
For e.g., some method like parser.get_positional_args
>>> from make_parser import parser
>>> parser.get_positional_args()
<argparse.ArgumentClass object at 0x7f3871721e80>
And the same for optional arguments, something like parser.get_optional_args
Add to your code:
for action in parser._actions:
print(action)
print([(action.dest, action.option_strings) for action in parser._actions])
print([action.dest for action in parser._actions if not action.option_strings])
print([action.dest for action in parser._actions if action.option_strings])
result (edited for clarity):
0848:~/mypy$ python3 stack57450629.py
_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None)
_StoreAction(option_strings=[], dest='n', nargs=None, const=None, default=None, type=<class 'float'>, choices=None, help='Input number to be multiplied', metavar=None)
_StoreAction(option_strings=['-m', '--optional-multiplier'], dest='optional_multiplier', nargs=None, const=None, default=1, type=<class 'float'>, choices=None, help='multiplier for <n> : (default: 1)', metavar=None)
[('help', ['-h', '--help']), ('n', []), ('optional_multiplier', ['-m', '--optional-multiplier'])]
['n']
['help', 'optional_multiplier']

python argparse different parameters with different number of arguments

How do I use a different number of parameters for each option?
ex) a.py
parser.add_argument('--opt', type=str,choices=['a', 'b', 'c'],help='blah~~~')
choice : a / parameter : 1
ex)
$ python a.py --opt a param
choice : c / parameter :2
ex)
$ python a.py --opt b param1 param2
You need to add sub-commands, ArgumentParser.add_subparsers() method will help you
Check this example
>>> # create the top-level parser
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('--foo', action='store_true', help='foo help')
>>> subparsers = parser.add_subparsers(help='sub-command help')
>>>
>>> # create the parser for the "a" command
>>> parser_a = subparsers.add_parser('a', help='a help')
>>> parser_a.add_argument('bar', type=int, help='bar help')
>>>
>>> # create the parser for the "b" command
>>> parser_b = subparsers.add_parser('b', help='b help')
>>> parser_b.add_argument('--baz', choices='XYZ', help='baz help')
>>>
>>> # parse some argument lists
>>> parser.parse_args(['a', '12'])
Namespace(bar=12, foo=False)
>>> parser.parse_args(['--foo', 'b', '--baz', 'Z'])
Namespace(baz='Z', foo=True)
You may add more parameters, one for each a, b, and c and also optional arguments for your params. By using the named parameter nargs='?' you can specify that they are optional and with the default="some value" you ensure it rises no errors. Finally, based on the selected option, a,b or c you will be able to capture the ones you need.
Here's a short usage example:
parser.add_argument('x1', type=float, nargs='?', default=0, help='Circ. 1 X-coord')
parser.add_argument('y1', type=float, nargs='?', default=0, help='Circ. 1 Y-coord')
parser.add_argument('r1', type=float, nargs='?', default=70, help='Circ. 1 radius')
parser.add_argument('x2', type=float, nargs='?', default=-6.57, help='Circ. 2 X-coord')
parser.add_argument('y2', type=float, nargs='?', default=7, help='Circ. 2 Y-coord')
parser.add_argument('r2', type=float, nargs='?', default=70, help='Circ. 2 radius')
args = parser.parse_args()
circCoverage(args.x1, args.y1, args.r1, args.x2, args.y2, args.r2)
here, if no values are selected, the default ones are used. You can play with this to get what you want.
Cheers

Check for certain arguments and assign values to an arbitrary variable

I'm writing a program and need to look for one of two arguments set on the command line, and save a value to a single variable based on which one is set.
If I call the program like this:
myprogram -a --foo 123
I want the variable action set to 'a value'. Call it like this:
myprogram -b --foo 123
And action should be set to 'another value'. Call it with neither:
myprogram -c --foo 123
And it should exit with usage info.
Obviously I can do this with some if statements after the fact:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='count')
parser.add_argument('-b', action='count')
parser.add_argument('--foo')
....
args = parser.parse_args()
if args.a == 0 and args.b == 0:
parser.print_usage()
sys.exit(1)
if args.a > 0:
action = 'a value'
elif args.b > 0:
action = 'another value'
But I'm wondering if argparse can do it for me with less code. From what I've seen in the documentation it's not possible, but Python is very very new to me. Thanks.
Look at action='store_const'.
In [240]: parser=argparse.ArgumentParser()
In [241]: parser.add_argument('-a', dest='action',
action='store_const', const='a value')
In [242]: parser.add_argument('-b', dest='action',
action='store_const', const='another value')
In [243]: parser.add_argument('--foo')
In [244]: parser.parse_args('-a --foo 123'.split())
Out[244]: Namespace(action='a value', foo='123')
In [245]: parser.parse_args('-b --foo 123'.split())
Out[245]: Namespace(action='another value', foo='123')
In [246]: parser.parse_args('-c --foo 123'.split())
usage: ipython3 [-h] [-a] [-b] [--foo FOO]
ipython3: error: unrecognized arguments: -c
SystemExit: 2
So args.action will have a value' orb value' depending on the argument. Note both -a and -b store to the same dest.
I left -c undefined, so it uses the normal undefined exit with usage. that could be refined.
Defining a c like this would let you do your own exit:
In [247]: parser.add_argument('-c', dest='action', action='store_const', const='exit')
In [248]: args=parser.parse_args('-c --foo 123'.split())
In [249]: if args.action=='exit':parser.print_usage()
usage: ipython3 [-h] [-a] [-b] [--foo FOO] [-c]
If you used action='store_true' instead of 'count' for your -a and -b, you could simplify the if tree to:
if args.a:
action = 'a value'
elif args.b:
action = 'another value'
else:
parser.print_usage()
sys.exit(1)
I used hapulj's answer, but still ended up with an if statement to check if neither was set. Then I found the ArgumentParser.add_mutually_exclusive_group() function, and ended up with this, which works perfectly.
import argparse
parser = argparse.ArgumentParser()
actiongroup = parser.add_mutually_exclusive_group(required=True)
actiongroup.add_argument('-a', action='store_const', dest='action', const='a value')
actiongroup.add_argument('-b', action='store_const', dest='action', const='another value')
parser.add_argument('--foo')
....
args = parser.parse_args()
Now, arguments -a and -b can't be omitted, and both can't be specified at the same time.

argparse -- requiring either 2 values or none for an optional argument

I'm trying to make an optional argument for a script that can either take no values or 2 values, nothing else. Can you accomplish this using argparse?
# desired output:
# ./script.py -a --> works
# ./script.py -a val1 --> error
# ./script.py -a val1 val2 --> works
version 1 -- accepts 0 or 1 values:
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--action", nargs="?", const=True, action="store", help="do some action")
args = parser.parse_args()
# output:
# ./script.py -a --> works
# ./script.py -a val1 --> works
# ./script.py -a val1 val2 --> error
version 2 - accepts exactly 2 values:
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--action", nargs=2, action="store", help="do some action")
args = parser.parse_args()
# output:
# ./script.py -a --> error
# ./script.py -a val1 --> error
# ./script.py -a val1 val2 --> works
How do you combine these 2 different versions so that the script accepts 0 or 2 values for the argument, but rejects it when it only has 1 value?
You'll have to do your own error checking here. Accept 0 or more value, and reject anything other than 0 or 2:
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--action", nargs='*', action="store", help="do some action")
args = parser.parse_args()
if args.action is not None and len(args.action) not in (0, 2):
parser.error('Either give no values for action, or two, not {}.'.format(len(args.action)))
Note that args.action is set to None when no -a switch was used:
>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument("-a", "--action", nargs='*', action="store", help="do some action")
_StoreAction(option_strings=['-a', '--action'], dest='action', nargs='*', const=None, default=None, type=None, choices=None, help='do some action', metavar=None)
>>> args = parser.parse_args([])
>>> args.action is None
True
>>> args = parser.parse_args(['-a'])
>>> args.action
[]
Just handle that case yourself:
parser.add_argument("-a", "--action", nargs='*', action="store", help="do some action")
args = parser.parse_args()
if args.action is not None:
if len(args.action) not in (0, 2):
parser.error('Specify no or two actions')
# action was specified but either there were two actions or no action
else:
# action was not specified
Of course you should update the help text in that case so that the user has a chance to know this before running into the error.
Have your option take a single, optional comma-separated string. You'll use a custom type to convert that string to a list and verify that it has exactly two items.
def pair(value):
rv = value.split(',')
if len(rv) != 2:
raise argparse.ArgumentParser()
return rv
parser.add_argument("-a", "--action", nargs='?',
type=pair, metavar='val1,val2',
help="do some action")
print parser.parse_args()
Then you'd use it like
$ ./script.py -a
Namespace(action=None)
$ ./script.py -a val1,val2
Namespace(action=['val1','val2'])
$ ./script.py -a val1
usage: tmp.py [-h] [-a [ACTION]]
script.py: error: argument -a/--action: invalid pair value: 'val1'
$ ./script.py -a val1,val2,val3
usage: tmp.py [-h] [-a [ACTION]]
script.py: error: argument -a/--action: invalid pair value: 'val1,val2,val3'
You can adjust the definition of pair to use a different separator and to return something other than a list (a tuple, for example).
The metavar provides a better indication that the argument to action is a pair of values, rather than just one.
$ ./script.py -h
usage: script.py [-h] [-a [val1,val2]]
optional arguments:
-h, --help show this help message and exit
-a [val1,val2], --action [val1,val2]
do some action
How about the required argument:parser.add_argument("-a", "--action", nargs=2, action="store", help="do some action", required=False)

Option accepted with and without value

I have a small script and I need it to be able to accept parameter with value and withou value.
./cha.py --pretty-xml
./cha.py --pretty-xml=5
I have this.
parser.add_argument('--pretty-xml', nargs='?', dest='xml_space', default=4)
But when I use --pretty-xml in xml_space will be 'none'. If I dont write this parameter in xml_space is stored the default value. I would need the exact opposite.
Use the const keyword:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--pretty-xml", nargs="?", type=int, dest="xml_space", const=4)
print(parser.parse_args([]))
print(parser.parse_args(['--pretty-xml']))
print(parser.parse_args(['--pretty-xml=5']))
results in
Namespace(xml_space=None)
Namespace(xml_space=4)
Namespace(xml_space=5)
Leave out the default parameter and use a custom Action instead:
class PrettyXMLAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if not values:
values = 4
setattr(namespace, self.dest, values)
parser.add_argument('--pretty-xml', nargs='?', type=int, dest='xml_space', action=PrettyXMLAction)
Demo:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--pretty-xml', nargs='?', type=int, dest='xml_space', action=PrettyXMLAction)
PrettyXMLAction(option_strings=['--pretty-xml'], dest='xml_space', nargs='?', const=None, default=None, type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args('--pretty-xml'.split())
Namespace(xml_space=4)
>>> parser.parse_args('--pretty-xml=5'.split())
Namespace(xml_space=5)
>>> parser.parse_args(''.split())
Namespace(xml_space=None)

Categories