python argparse-необязательный аргумент добавления с выбором

у меня есть скрипт, где я прошу пользователя выполнить список заранее определенных действий. Я также хочу иметь возможность принимать определенный список действий, когда пользователь ничего не определяет. тем не менее, похоже, что попытка сделать оба из них вместе невозможна.

когда пользователь дает никаких аргументов, они получают сообщение об ошибке, что выбор по умолчанию является недействительным

acts = ['clear','copy','dump','lock']
p = argparse.ArgumentParser()
p.add_argument('action', nargs='*', action='append', choices=acts, default=[['dump', 'clear']])
args = p.parse_args([])
>>> usage: [-h] [{clear,copy,dump,lock} [{clear,copy,dump,lock} ...]]
: error: argument action: invalid choice: [['dump', 'clear']] (choose from 'clear', 'copy', 'dump', 'lock')

и когда они определяют набор действий, результирующее пространство имен действия пользователя добавлено по умолчанию, а не заменено по умолчанию

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

5 ответов


что вам нужно можно сделать используя подгонянное argparse.Action как в следующем примере:

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'])

вывод скрипта:

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

в документации (http://docs.python.org/dev/library/argparse.html#default), сказано:

для позиционных аргументов с nargs равны ? или *, значение по умолчанию используется при отсутствии аргумента командной строки.

тогда, если мы делаем :

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

мы получаем то, что мы ожидаем

Namespace(action='clear')

проблема в том, когда вы помещаете список по умолчанию. Но я видел это в доке,

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

Итак, я не знаю :-(

во всяком случае, здесь есть обходной путь, который делает работу, которую вы хотите :

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

вы можете проверить, предоставляет ли пользователь действия (в этом случае разберите его как обязательный аргумент позиции) или не предоставляет никаких действий (в этом случае разберите его как необязательный аргумент по умолчанию):

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)

при запуске, дает следующие результаты:

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

вы, вероятно, есть и другие варианты для анализа. В этом случае вы можете использовать parse_known_args для анализа других параметров, а затем обрабатывать unknown аргументы в секунду пас:

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)

при запуске, дает следующие результаты:

% 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')

действие добавлялось из-за параметра" action='append'", который вы передали argparse.

после удаления этого параметра аргументы, переданные пользователем, будут отображаться самостоятельно, но программа выдаст ошибку, если аргументы не были переданы.

добавление префикса ' -- ' к первому параметру разрешает это самым ленивым способом.

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

недостатком этого подхода является то, что параметры, переданные пользователем, теперь должны быть предшествует '--действие', например:

app.py --action clear dump copy

Я закончил следующим образом:

  • нет append
  • добавьте пустой список к возможным вариантам или же пустые входные разрывы
  • без значения по умолчанию
  • проверка на пустой список, а затем установить фактический дефолт в таком случае

пример:

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.