python argparse - optional append argument with choices

喜欢而已 提交于 2019-11-28 21:22:31

What you need can be done using a customized argparse.Action as in the following example:

import argparse

parser = argparse.ArgumentParser()

class DefaultListAction(argparse.Action):
    CHOICES = ['clear','copy','dump','lock']
    def __call__(self, parser, namespace, values, option_string=None):
        if values:
            for value in values:
                if value not in self.CHOICES:
                    message = ("invalid choice: {0!r} (choose from {1})"
                               .format(value,
                                       ', '.join([repr(action)
                                                  for action in self.CHOICES])))

                    raise argparse.ArgumentError(self, message)
            setattr(namespace, self.dest, values)

parser.add_argument('actions', nargs='*', action=DefaultListAction,
                    default = ['dump', 'clear'],
                    metavar='ACTION')

print parser.parse_args([])
print parser.parse_args(['lock'])

The output of the script is:

$ python test.py 
Namespace(actions=['dump', 'clear'])
Namespace(actions=['lock'])

In the documentation (http://docs.python.org/dev/library/argparse.html#default), it is said :

For positional arguments with nargs equal to ? or *, the default value is used when no command-line argument was present.

Then, if we do :

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts, default='clear')    
print p.parse_args([])

We get what we expect

Namespace(action='clear')

The problem is when you put a list as a default. But I've seen it in the doc,

parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!')

So, I don't know :-(

Anyhow, here is a workaround that does the job you want :

import sys, argparse
acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', choices=acts)
args = ['dump', 'clear'] # I set the default here ... 
if sys.argv[1:]:
    args = p.parse_args()
print args

You could test whether the user is supplying actions (in which case parse it as a required, position argument), or is supplying no actions (in which case parse it as an optional argument with default):

import argparse
import sys

acts = ['clear', 'copy', 'dump', 'lock']
p = argparse.ArgumentParser()
if sys.argv[1:]:
    p.add_argument('action', nargs = '*', choices = acts)
else:
    p.add_argument('--action', default = ['dump', 'clear'])

args = p.parse_args()
print(args)

when run, yields these results:

% test.py 
Namespace(action=['dump', 'clear'])
% test.py lock
Namespace(action=['lock'])
% test.py lock dump
Namespace(action=['lock', 'dump'])

You probably have other options to parse as well. In that case, you could use parse_known_args to parse the other options, and then handle the unknown arguments in a second pass:

import argparse

acts = ['clear', 'copy', 'dump', 'lock']
p = argparse.ArgumentParser()
p.add_argument('--foo')
args, unknown = p.parse_known_args()
if unknown:
    p.add_argument('action', nargs = '*', choices = acts)
else:
    p.add_argument('--action', default = ['dump', 'clear'])

p.parse_args(unknown, namespace = args)
print(args)

when run, yields these results:

% test.py 
Namespace(action=['dump', 'clear'], foo=None)
% test.py --foo bar
Namespace(action=['dump', 'clear'], foo='bar')
% test.py lock dump
Namespace(action=['lock', 'dump'], foo=None)
% test.py lock dump --foo bar
Namespace(action=['lock', 'dump'], foo='bar')

I ended up doing the following:

  • no append
  • add the empty list to the possible choices or else the empty input breaks
  • without default
  • check for an empty list afterwards and set the actual default in that case

Example:

parser = argparse.ArgumentParser()
parser.add_argument(
    'is',
    type=int,
    choices=[[], 1, 2, 3],
    nargs='*',
)

args = parser.parse_args(['1', '3'])
assert args.a == [1, 3]

args = parser.parse_args([])
assert args.a == []
if args.a == []:
    args.a = [1, 2]

args = parser.parse_args(['1', '4'])
# Error: '4' is not valid.

The action was being appended because of the "action='append'" parameter you passed to argparse.

After removing this parameter, the arguments passed by a user would be displayed on their own, but the program would throw an error when no arguments were passed.

Adding a '--' prefix to the first parameter resolves this in the laziest way.

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('--action', nargs='*', choices=acts, default=[['dump', 'clear']])
args = p.parse_args()

The downside to this approach is that the options passed by the user must now be preceded by '--action', like:

app.py --action clear dump copy
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!