Несколько позиционных аргументов с Python и argparse
Я пытаюсь использовать argparse для анализа аргументов командной строки для программы, над которой я работаю. По сути, мне нужно поддерживать множественные позиционные аргументы, распространенные в необязательных аргументах, но не может заставить argparse работать в этой ситуации. В реальной программе я использую настраиваемое действие (мне нужно сохранить моментальный снимок пространства имен каждый раз, когда найден позиционный аргумент), но проблема, которую я имею, может быть реплицирована с помощью действия append
:
>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-a', action='store_true')
>>> parser.add_argument('-b', action='store_true')
>>> parser.add_argument('input', action='append')
>>> parser.parse_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
usage: ipython [-h] [-a] [-b] input
ipython: error: unrecognized arguments: filetwo filethree
Мне бы хотелось, чтобы это привело к пространству имен (a=True, b=True, input=['fileone', 'filetwo', 'filethree'])
, но не может понять, как это сделать - если это действительно возможно. Я не вижу ничего в документах или Google, которые говорят так или иначе, если это возможно, хотя вполне возможно (вероятно?) Я что-то упустил. У кого-нибудь есть предложения?
Ответы
Ответ 1
srgerg был прав насчет определения позиционных аргументов. Чтобы получить желаемый результат, вы должны принять их как необязательные аргументы и изменить приведенное пространство имен в соответствии с вашими потребностями.
Вы можете использовать настраиваемое действие:
class MyAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
# Set optional arguments to True or False
if option_string:
attr = True if values else False
setattr(namespace, self.dest, attr)
# Modify value of "input" in the namespace
if hasattr(namespace, 'input'):
current_values = getattr(namespace, 'input')
try:
current_values.extend(values)
except AttributeError:
current_values = values
finally:
setattr(namespace, 'input', current_values)
else:
setattr(namespace, 'input', values)
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='+', action=MyAction)
parser.add_argument('-b', nargs='+', action=MyAction)
parser.add_argument('input', nargs='+', action=MyAction)
И вот что вы получаете:
>>> parser.parse_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
Namespace(a=True, b=True, input=['fileone', 'filetwo', 'filethree'])
Или вы можете изменить приведенное пространство имен следующим образом:
>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-a', nargs='+')
>>> parser.add_argument('-b', nargs='+')
>>> parser.add_argument('input', nargs='+')
>>> result = parser.parse_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
>>> inputs = []
>>> inputs.extend(result.a)
>>> inputs.extend(result.b)
>>> inputs.extend(result.input)
>>> modified = argparse.Namespace(
a = result.a != [],
b = result.b != [],
input = inputs)
И вот что вы получаете:
>>> modified
Namespace(a=True, b=True, input=['filetwo', 'filethree', 'fileone'])
Однако оба метода приводят к менее читабельному и менее обслуживаемому коду. Может быть, лучше изменить логику программы и сделать это по-другому.
Ответ 2
Вы не можете чередовать переключатели (т.е. -a
и -b
) с помощью позиционных аргументов (т.е. fileone, filetwo и filethree) таким образом. Коммутаторы должны отображаться до или после позиционных аргументов, а не между ними.
Кроме того, чтобы иметь несколько позиционных аргументов, вам нужно указать параметр nargs
на add_argument
. Например:
parser.add_argument('input', nargs='+')
Это говорит argparse
использовать один или несколько позиционных аргументов и добавлять их в список. Дополнительную информацию см. В документации argparse. С этой строкой код:
parser.parse_args(['-a', '-b', 'fileone', 'filetwo', 'filethree'])
приводит к:
Namespace(a=True, b=True, input=['fileone', 'filetwo', 'filethree'])
Ответ 3
Действие "append" имеет больше смысла с опцией:
parser.add_argument('-i', '--input',action='append')
parser.parse_args(['-i','fileone', '-a', '-i','filetwo', '-b', '-i','filethree'])
Вы можете чередовать опции с отдельными позициями ( "input1 -a input2 -b input3" ), но вы не можете чередовать опции в одном многопозиционном позиционном. Но вы можете выполнить это с помощью двухэтапного анализа.
import argparse
parser1 = argparse.ArgumentParser()
parser1.add_argument('-a', action='store_true')
parser1.add_argument('-b', action='store_true')
parser2 = argparse.ArgumentParser()
parser2.add_argument('input', nargs='*')
ns, rest = parser1.parse_known_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
# Namespace(a=True, b=True), ['fileone', 'filetwo', 'filethree']
ns = parser2.parse_args(rest, ns)
# Namespace(a=True, b=True, input=['fileone', 'filetwo', 'filethree'])
http://bugs.python.org/issue14191 - предлагаемый патч, который будет делать это с помощью одного вызова:
parser.parse_intermixed_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
Ответ 4
Мне кажется, что hpaulj на правильном пути, но делает вещи немного сложнее, чем необходимо. Я подозреваю, что Блэр ищет нечто похожее на поведение старого модуля optparse и на самом деле не нуждается в списке входных аргументов в поле ввода объекта args. Он просто хочет
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='store_true')
parser.add_argument('-b', action='store_true')
opts, args = parser.parse_known_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
# Namespace(a=True, b=True), ['fileone', 'filetwo', 'filethree']
В языке optparse "опции" доступны в опциях, а список возможных вкрапленных других "аргументов" - в args.