Поддерживает ли argparse (python) взаимоисключающие группы аргументов?

Если у меня есть аргументы '-a', '-b', '-c', '-d', с функцией add_mutually_exclusive_group() моя программа должна будет использовать только один из них. Есть ли способ объединить это, чтобы программа принимала только '-a 999 -b 999' или '-c 999 -d 999'?

Изменить: добавление простой программы для большей ясности:

>>> parser = argparse.ArgumentParser()
>>> group = parser.add_mutually_exclusive_group()
>>> group.add_argument('-a')
>>> group.add_argument('-b')
>>> group.add_argument('-c')
>>> group.add_argument('-d')

Тогда можно вызвать только ./app.py -a | ./app.py -b | ./app.py -c | ./app.py -d. Возможно ли, чтобы argparse группировал группы исключения, чтобы вызывались только ./app.py -a .. -b .. | ./app.py -c .. -d ..?


person aeter    schedule 22.01.2011    source источник


Ответы (4)


EDIT: неважно. Потому что argparse делает ужасный выбор, создавая опцию при вызове group.add_argument. Это не было бы моим выбором дизайна. Если вам очень нужна эта функция, вы можете попробовать сделать это с помощью ConflictsOptionParser:

# exclusivegroups.py
import conflictsparse

parser = conflictsparse.ConflictsOptionParser()
a_opt = parser.add_option('-a')
b_opt = parser.add_option('-b')
c_opt = parser.add_option('-c')
d_opt = parser.add_option('-d')

import itertools
compatible_opts1 = (a_opt, b_opt)
compatible_opts2 = (c_opt, d_opt)
exclusives = itertools.product(compatible_opts1, compatible_opts2)
for exclusive_grp in exclusives:
    parser.register_conflict(exclusive_grp)


opts, args = parser.parse_args()
print "opts: ", opts
print "args: ", args

Таким образом, когда мы вызываем его, мы видим, что получаем желаемый эффект.

$ python exclusivegroups.py -a 1 -b 2
opts:  {'a': '1', 'c': None, 'b': '2', 'd': None}
args:  []
$ python exclusivegroups.py -c 3 -d 2
opts:  {'a': None, 'c': '3', 'b': None, 'd': '2'}
args:  []
$ python exclusivegroups.py -a 1 -b 2 -c 3
Usage: exclusivegroups.py [options]

exclusivegroups.py: error: -b, -c are incompatible options.

Предупреждающее сообщение не информирует вас о том, что '-a' и '-b' несовместимы с '-c', однако можно создать более подходящее сообщение об ошибке. Старый, неправильный ответ ниже.

СТАРАЯ ПРАВКА: [Это редактирование неверно, хотя разве это не был бы просто идеальный мир, если бы argparse работало таким образом?] Мой предыдущий ответ на самом деле был неправильным, вы должны быть можно сделать это с помощью argparse, указав одну группу на взаимоисключающие параметры. Мы даже можем использовать itertools для обобщения процесса. И сделайте так, чтобы нам не нужно было вводить все комбинации явно:

import itertools
compatible_opts1 = ('-a', '-b')
compatible_opts2 = ('-c', '-d')
exclusives = itertools.product(compatible_opts1, compatible_opts2)
for exclusive_grp in exclusives:
    group = parser.add_mutually_exclusive_group()
    group.add_argument(exclusive_grp[0])
    group.add_argument(exclusive_grp[1])
person gotgenes    schedule 22.01.2011
comment
bugs.python.org/issue10984 содержит исправление, позволяющее поместить аргумент в несколько взаимоисключающих группа. Сделать это легко. Создание значимого использования с перекрывающимися группами более сложно. - person hpaulj; 09.08.2013

Просто сам наткнулся на эту проблему. Судя по моему чтению документов argparse, простого способа добиться этого в argparse не существует. Я думал об использовании parse_known_args, но вскоре это будет означать написание специальной версии argparse ;-)

Возможно, отчет об ошибке в порядке. В то же время, если вы хотите, чтобы ваш пользователь немного печатал больше, вы можете подделать это с помощью подгрупп (например, как работают аргументы git и svn), например.

    subparsers = parser.add_subparsers()
    p_ab = subparsers.add_parser('ab')
    p_ab.add_argument(...)

    p_cd = subparsers.add_parser('cd')
    p_cd.add_argument(...)

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

person unhammer    schedule 20.11.2012

Запрос на улучшение argparse, упомянутый в комментарии @hpaulj, все еще остается открытым спустя более девяти лет, поэтому я решил, что другие люди могут извлечь выгоду из обходного пути, который я только что обнаружил. Основываясь на этом комментарии в запросе на улучшение, я обнаружил, что могу добавить параметр к двум различные взаимоисключающие группы, использующие этот синтаксис:

#!/usr/bin/env python                                                                                                                                                                     
import argparse
import os
import sys

def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )

    parser.add_argument("-d", "--device", help="Path to UART device", default="./ttyS0")

    mutex_group1 = parser.add_mutually_exclusive_group()
    mutex_group2 = parser.add_mutually_exclusive_group()

    mutex_group1.add_argument(
        "-o",
        "--output-file",
        help="Name of output CSV file",
        default="sensor_data_sent.csv",
    )

    input_file_action = mutex_group1.add_argument(
        "-i", "--input-file", type=argparse.FileType("r"), help="Name of input CSV file"
    )

    # See: https://bugs.python.org/issue10984#msg219660
    mutex_group2._group_actions.append(input_file_action)

    mutex_group2.add_argument(
        "-t",
        "--time",
        type=int,
        help="How long to run, in seconds (-1 = loop forever)",
        default=-1,
    )

    # Add missing ']' to usage message
    usage = parser.format_usage()
    usage = usage.replace('usage: ', '')
    usage = usage.replace(']\n', ']]\n')
    parser.usage = usage

    return parser.parse_args()


if __name__ == "__main__":
    args = parse_args()
    print("Args parsed successfully...")
    sys.exit(0)

Это работает достаточно хорошо для моих целей:

$ ./fake_sensor.py -i input.csv -o output.csv                                                                                                                                             
usage: fake_sensor.py [-h] [-d DEVICE] [-o OUTPUT_FILE | [-i INPUT_FILE | -t TIME]]
fake_sensor.py: error: argument -o/--output-file: not allowed with argument -i/--input-file

$ ./fake_sensor.py -i input.csv -t 30         
usage: fake_sensor.py [-h] [-d DEVICE] [-o OUTPUT_FILE | [-i INPUT_FILE | -t TIME]]
fake_sensor.py: error: argument -t/--time: not allowed with argument -i/--input-file

$ ./fake_sensor.py -i input.csv
Args parsed successfully...

$ ./fake_sensor.py -o output.csv
Args parsed successfully...

$ ./fake_sensor.py -o output.csv -t 30
Args parsed successfully...

Доступ к закрытым членам argparse, конечно, довольно ненадежен, поэтому я, вероятно, не стал бы использовать этот подход в рабочем коде. Кроме того, проницательный читатель может заметить, что сообщение об использовании вводит в заблуждение, поскольку оно подразумевает, что -o и -i можно использовать вместе, когда они не могут (!) Однако я использую этот скрипт только для тестирования, поэтому я не слишком обеспокоен . (Я думаю, что исправление сообщения об использовании «по-настоящему» потребует намного больше времени, чем я могу выделить для этой задачи, но, пожалуйста, прокомментируйте, если вы знаете хитрый хак для этого.)

person evadeflow    schedule 31.03.2020

Подпарсеры?

Аналогично ответу unhammer, но с большим контролем пользователя. Примечание. На самом деле я не проверял этот метод, но в теории он должен работать и с возможностями Python.

Вы можете создать два парсера, по одному для каждой из двух групп, и использовать условные операторы для выполнения взаимоисключающей части. По сути, используя argparse только для части разбора аргумента. Используя этот метод, вы также можете выйти за пределы ограничений ответа unhammer.

# Python 3
import argparse

try:
    parser = argparse.ArgumentParser()
    parser.add_argument('-a')
    parser.add_argument('-b')
    args = parser.parse_args
except argparse.ArgumentError:
    parser = argparse.ArgumentParser()
    parser.add_argument('-c')
    parser.add_argument('-d')
    args = parser.parse_args
person gagarwa    schedule 20.07.2018