wiki:python/argparse

Python Doc

argparse – Command line option and argument parsing.

type or action

The function which are passed via type receives each argument one by one.
In contrast to this the action-class passed via action becomes the whole args-list as through the values-parameter.

Positional arguments with single dash

argparse Doc: Arguments containing -
argparse Doc: Partial parsing
Problem argparse positional argument, das mit '-' beginnt
re.match-object als Bedingung verwenden?

argparse works among others with this rules:

  • If an argument starts with a dash it will be interpreted as a potential optional argument such as '-a', '-f', '-4' and so on.
  • If an argument starts with a dash and a following digit argparse use a bit magic:
    • If this argument is defined as a optional argument everything is all right.
    • If this argument is not found as a defined optional argument it will be interpreted as a potential positional argument which is a negative number. Should the argument can't convert to a number cause it trailing by non-digit characters (except by a dot!) the argument will be interpreted as an unknown argument.
def parse(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('positional', nargs='*')
    parser.add_argument('-f', nargs='*')
    return parser.parse_args(args)

>>> parse('-4.0 -f foo'.split())
Namespace(f=['foo'], positional=['-4.0'])
>>> parse('-3d -f foo'.split())
usage: [-h] [-f [F [F ...]]] [positional [positional ...]]
error: unrecognized arguments: -3d

As if that were not enough, the behaviour above don't work if one of the optional arguments is a digit (not starts with a digit):

def parse(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('positional', nargs='*')
    parser.add_argument('-f', nargs='*')
    parser.add_argument('-2', nargs='*')
    return parser.parse_args(args)

>>> parse('4 -f foo'.split())
Namespace(2=None, f=['foo'], positional=['4'])
>>> parse('-4 -f foo'.split())
usage: [-h] [-f [F [F ...]]] [-2 [2 [2 ...]]] [positional [positional ...]]
error: unrecognized arguments: -4

Ok, if I had no optional digit arguments and apply negative numbers to calculate a datetime the following happened:

$ denkdran -2
  #results in current datetime minus 2 days
$ denkdran -2:
  #should result in current datetime two hours ago
  #but '-2:' is recognized as an unknown argument

A first solution was to use the 'parse_known_args' instead of the common 'parse_args' method:

def parse(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('positional', nargs='*')
    parser.add_argument('-f', nargs='*')
    parser.add_argument('-2', nargs='*')
    return parser.parse_known_args(args)

>>> known, unknown = parse('1 2 3 -4 -4d -2 bar -f foo -a'.split())
>>> print known
(Namespace(2=['bar'], f=['foo'], positional=['1', '2', '3'])
>>> print unknown
['-4', '-4d', '-a'])

The problem here is that unknown args which are no deliberate positional arguments (such as '-a' in this example) are added to the unknown list and raise no error as they should. Furthermore the order of the arguments are broken.

A solution could be this 'hack':

def hide_unknowns(args):
    unknowns = []
    result = []
    while args:
        arg = args.pop(0)
        match = re.match(r'(-\d+\D.*)', arg)
        if match:
            result.append('_hidden_')
            unknowns.append(match.group(0))
        else:
            result.append(arg)
    return result, unknowns

def unhide_unknowns(args, unknowns):
    result = []
    while args:
        arg = args.pop(0)
        if arg == '_hidden_':
            result.append(unknowns.pop(0))
        else:
            result.append(arg)
    return result

def parse(args):
    args, unknowns = hide_unknowns(args)
    parser = argparse.ArgumentParser()
    parser.add_argument('positional', nargs='*')
    parser.add_argument('-f', nargs='*')
    args = parser.parse_args(args)
    args.positional = unhide_unknowns(args.positional, unknowns)
    return args

>>> args = parse('1 2 -3 -4d -f foo'.split())
>>> print args.f
['foo']
>>> print args.positional
['1', '2', '-3', '-4d']
>>> args = parse('1 2 -3 -4d -f foo'.split())
usage: [-h] [-f [F [F ...]]] [positional [positional ...]]
error: unrecognized arguments: -a

This is what happens:

  1. hide_unknowns() replaced each argument which matched with r'(-\d+\D.*)' with the string '_hidden_' which will recognized as a positional argument later in the parsing process.
  2. Start the parsing process.
  3. unhide_unknowns() take the list with the positional arguments (includes both unrecognizable arguments and '_hidden_' strings) and replaced the '_hidden_' strings with the real arguments stored in the unknowns list.

Internationalization

argparse i18n

Last modified 6 years ago Last modified on Jan 30, 2013, 5:14:09 PM