Несколько позиционных аргументов с 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, который говорит так или иначе, если это возможно, хотя это вполне возможно (вероятно? Я кое-что упустил. У кого-нибудь есть предложения?
4 ответов
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'])
однако, оба метода результат в более менее читаемом и менее ремонтопригодном коде. Возможно, лучше изменить логику программы и сделать это по-другому.
вы не можете чередовать переключатели (т. е. -a
и -b
) с позиционными аргументами (т. е. fileone, filetwo и filethree) таким образом. Переключатели должны отображаться до или после позиционных аргументов, а не между ними.
кроме того, чтобы иметь несколько позиционных аргументов, вам нужно указать до add_argument
. Например:
parser.add_argument('input', nargs='+')
это говорит argparse
использовать один или несколько позиционных аргументов и добавлять их в список. Видеть the документация argparse для получения дополнительной информации. С помощью этой строки кода:
parser.parse_args(['-a', '-b', 'fileone', 'filetwo', 'filethree'])
результаты:
Namespace(a=True, b=True, input=['fileone', 'filetwo', 'filethree'])
действие "добавить" имеет больше смысла с дополнительным:
parser.add_argument('-i', '--input',action='append')
parser.parse_args(['-i','fileone', '-a', '-i','filetwo', '-b', '-i','filethree'])
вы можете чередовать optionals с отдельными positionals ('input1-a input2-b input3'), но вы не можете чередовать optionals в пределах одного многоэлементного позиционного. Но вы можете сделать это с помощью двухэтапного анализа.
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'])
Мне кажется, что 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 "параметры "доступны в opts, а список возможных чередующихся других" аргументов " - в args.