pythonargparseoptparse

How interspersed/intermixed arguments should be handled in argparse-module in versions prior to Python3.7?


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)

Solution

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