Skip to content Skip to sidebar Skip to footer

Python Argparse Requiring Option, Depending On The Defined Flags

I have a small python script, which uses argparse to let the user define options. It uses two flags for different modes and an argument to let the user define a file. See the simpl

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 handles aFile.
  • make sure aFile is valid so check_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 right default.

  • 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"