Python Argparse Requiring Option, Depending On The Defined Flags
Solution 1:
You can defined subparser with ay/be as subcommand or alternatively declare a second parser instance for a. Something like:
parser = argparse.ArgumentParser(
description="An example",
formatter_class=argparse.RawTextHelpFormatter
)
# ensure either option -a or -b only
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-a", "--ay", help="Method A, requires file.",
action='store_true')
group.add_argument("-b", "--be", help="Method B, no file required.",
action='store_true')
# define a parser for option -a
parser_a = argparse.ArgumentParser()
parser_a.add_argument("-f", "--file", help="A file, used with method A.",
type=check_file, required=True)
parser_a.add_argument("-a", "--ay", help="Method A, requires file.",
action='store_true')
# first parse - get either -a/-b
args = parser.parse_known_args(sys.argv[1:])
# if -a, use the second parser to ensure -f is in argument# note parse_known_args return tuple, the first one is the populated namespaceif args[0].ay:
args = parser_a.parse_args(sys.argv[1:])
Solution 2:
Your problem lies with how argparse
handles defaults. You'd get this behavior even if -f
was the only argument. If the default is a string value, it will be 'evaluated' if the Action isn't seen.
parser.add_argument("-f", "--file", help="A file, used with method A.", default=aFile, type=check_file)
At the start of parsing defaults are put into the args
namespace. During parsing it keeps track of whether Actions have been seen. At the end of parsing it checks Namespace values for Actions which haven't been seen. If they match the default (the usual case) and are strings, it passes the default through the type
function.
In your -f
case, the default is probably a file name, a string. So it will be 'evaluated' if the user doesn't provide an alternative. In earlier argparse
versions defaults were evaluate regardless of whether they were used or not. For something like a int
or float
type that wasn't a problem, but for FileType
it could result in unneeded file opening/creation.
Ways around this?
- write
check_file
so it gracefully handlesaFile
. - make sure
aFile
is valid socheck_file
runs without error. This the usual case. - use a non-string default, e.g. an already open file.
use the default default None, and add the default value after parsing.
if args.file is None: args.file = aFile
Combining this with -a
and -b
actions you have to decide whether:
if
-a
, is a-f
value required? If-f
isn't provided, what's the rightdefault
.if
-b
, does it matter whether-f
has a default or whether the user provides this argument? Could you just ignore it?
If -f
is useful only when -a
is True, why not combine them?
parser.add_argument('-a', nargs='?', default=None, const='valid_file', type=check_file)
With ?
, this works in 3 ways. (docs on const
)
- no
-a
,args.a = default
- bare
-a
,args.a = const
- -a afile
,
args.a =afile
An even simpler example of this behavior
In [956]: p = argparse.ArgumentParser()
In [957]: p.add_argument('-f',type=int, default='astring')
...
In [958]: p.parse_args('-f 1'.split())
Out[958]: Namespace(f=1)
In [959]: p.parse_args(''.split())
usage: ipython3 [-h] [-f F]
ipython3: error: argument -f: invalid int value: 'astring'
The string default is passed through int
resulting in an error. If I'd set default to something else like a list, default=[1,2,3]
, it would have run even though int
would have choked on the default.
Post a Comment for "Python Argparse Requiring Option, Depending On The Defined Flags"