Подтвердить что ты не робот

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

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

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

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

Ответ 1

Что вам нужно, можно сделать с помощью настраиваемого 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'])

Выходной сигнал script:

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

Ответ 2

В документации (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

Ответ 3

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

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

Ответ 4

Действие добавляется из-за параметра 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()

Недостатком этого подхода является то, что перед параметрами, передаваемыми пользователем, теперь должно предшествовать "-action", например:

app.py --action clear dump copy

Ответ 5

В итоге я сделал следующее:

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