When following the official documentation for upgrading from optparse
to argparse
the following simple parser
import optparse
def parse_with_optparser(args):
opt_parser = optparse.OptionParser()
opt_parser.add_option('-a', action="store_true")
return opt_parser.parse_args(args)
becomes:
def parse_with_argparser(args):
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-a', action="store_true")
arg_parser.add_argument("sources", nargs='*')
return arg_parser.parse_args(args)
i.e. an additional positional argument sources
is added.
However, optparse
supports interspersed (or intermixed in argparse
-parlance) arguments per default, i.e. we can call successful for
args = ['file1', '-a', 'file2']
parse_with_optparser(args)
# ({'a': True}, ['file1', 'file2'])
but argparse
doesn't support intermixed arguments and using it results in an error:
parse_with_argparser(args)
# error: unrecognized arguments: file2
Since Python3.7 there is parse_intermixed_args
(instead of parse_args
), which handles interspersed/intermixed arguments the same way as optparse
. However, the framework targets Python2.7 and Pyton>=3.3 and thus using parse_intermixed_args
doesn't cut it.
How interspersed/intermixed arguments should be handled in argparse
in versions prior to Python3.7?
Some test cases:
Input Output
['file1', 'file2', '-a'] Namespace(a=True, sources=['file1', 'file2'])
['-a', 'file1', 'file2'] Namespace(a=True, sources=['file1', 'file2'])
['file1', '-a', 'file2'] Namespace(a=True, sources=['file1', 'file2'])
['file1', '-a', '-b'] error (-b is unknown option)
I've followed @hpaulj's advise and used parse_known_args
to be able to handle intermixed options manually in a post processing step:
import argparse
def parse_with_argparser(args):
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-a', action="store_true")
# thus, "sources" is also a part of the help-message:
arg_parser.add_argument("sources", nargs='*')
# collecting unknown-options for post processing,
# rather than exiting directly:
result, unknown = arg_parser.parse_known_args(args)
# post processing:
for x in unknown:
# filter out unknown options (like -b)
# exit with error
if x.startswith('-'):
arg_parser.error("unknown argument "+x)
# that must be one of the remaining sources:
getattr(result, 'sources').append(x)
return result
It seems to be easier, than copying-and-pasting code for parse_intermixed_args
, because arparse
module cannot handle narg==SUPPRESS
in Python<3.7 and it is needed for the patch.