Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Python's argparse permute argument order like gnu getopt?

GNU getopt, and command line tools that use it, allow options and arguments to be interleaved, known as permuting options (see http://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html#Using-Getopt). Perl's Getopt::Long module also supports this (with qw(:config gnu_getopt)). argparse seems to not support (or even mention) permuting options.

There are many SO questions related to arg/opt order, but none seem answer this question: Can argparse be made to permute argument order like getopt?

The use case is a prototypical command line signature like GNU sort:

sort [opts] [files]

in which 1) options and files are permuted, and 2) the file list may contain zero or more arguments.

For example:

import argparse
p = argparse.ArgumentParser();
p.add_argument('files',nargs='*',default=['-']);
p.add_argument('-z',action='store_true')

p.parse_args(['-z','bar','foo']) # ok
p.parse_args(['bar','foo','-z']) # ok
p.parse_args(['bar','-z','foo']) # not okay
usage: ipython [-h] [-z] [files [files ...]]

I've tried:

  • p.parse_known_args -- doesn't complain, but doesn't actually permute either and it doesn't balk about arguments that look like invalid options (e.g., --bogus or -b above).
  • p.add_argument('files',nargs=argparse.REMAINDER) -- option -z is included in files unless before positional args
  • p.add_argument('files',nargs='*',action='append');

I want to implement something close to the GNU sort prototype above. I am not interested in a flag that can be specified for each file (e.g., -f file1 -f file2).

like image 461
Reece Avatar asked Mar 02 '12 17:03

Reece


People also ask

Does order matter in Argparse?

Combining Positional and Optional arguments Note that the order does not matter.

What is getopt in Python?

The getopt module is a parser for command-line options based on the convention established by the Unix getopt() function. It is in general used for parsing an argument sequence such as sys. argv. In other words, this module helps scripts to parse command-line arguments in sys. argv.

What does Nargs do in Argparse?

Using the nargs parameter in add_argument() , you can specify the number (or arbitrary number) of inputs the argument should expect. In this example named sum.py , the --value argument takes in 3 integers and will print the sum.

When was Argparse added to Python?

The Python argparse library was released as part of the standard library with Python 3.2 on February the 20th, 2011. It was introduced with Python Enhancement Proposal 389 and is now the standard way to create a CLI in Python, both in 2.7 and 3.2+ versions.


1 Answers

Here's a quick solution which decodes the argument list one (options, positional arguments) pair at a time.

import argparse

class ExtendAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        items = getattr(namespace, self.dest, None)
        if items is None:
            items = []
        items.extend(values)
        setattr(namespace, self.dest, items)

parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*', action=ExtendAction)
parser.add_argument('-z', action='store_true')
parser.add_argument('-v', action='count')
parser.add_argument('args_tail', nargs=argparse.REMAINDER)

def interleaved_parse(argv=None):
    opts = parser.parse_args(argv)
    optargs = opts.args_tail
    while optargs:
        opts = parser.parse_args(optargs, opts)
        optargs = opts.args_tail
    return opts

print(interleaved_parse('-z bar foo'.split()))
print(interleaved_parse('bar foo -z'.split()))
print(interleaved_parse('bar -z foo'.split()))
print(interleaved_parse('-v a -zv b -z c -vz d -v'.split()))

Output:

Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
Namespace(args_tail=[], files=['bar', 'foo'], v=None, z=True)
Namespace(args_tail=[], files=['a', 'b', 'c', 'd'], v=4, z=True)

Note: Don't try to use this with other non-flag arguments (besides a single nargs='*' argument and the args_tail argument). The parser won't know about previous invocations of parse_args so it will store the wrong value for these non-flag arguments. As a workaround, you can parse the nargs='*' argument manually after using interleaved_parse.

like image 186
nneonneo Avatar answered Sep 20 '22 12:09

nneonneo