Несколько позиционных аргументов с 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.