Argparse: обязательный аргумент 'y', если присутствует 'x'

118

У меня есть следующие требования:

./xyifier --prox --lport lport --rport rport

для аргумента prox я использую action = 'store_true', чтобы проверить, присутствует он или нет. Я не нуждаюсь ни в каких аргументах. Но, если установлен --prox, мне также нужны rport и lport. Есть ли простой способ сделать это с помощью argparse без написания пользовательского условного кодирования.

Больше кода:

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', type=int, help='Listen Port.')
non_int.add_argument('--rport', type=int, help='Proxy port.')
asudhak
источник
Заткнул, но я хотел упомянуть свою библиотеку joffrey . Позволяет вам делать то, что требует этот вопрос, например, не заставляя вас проверять все самостоятельно (как в принятом ответе) или полагаться на хитрость (как в ответе, получившем второе место по количеству голосов).
MI Wright
Для всех, кто приезжает сюда, есть еще одно удивительное решение: stackoverflow.com/a/44210638/6045800
Tomerikoo

Ответы:

122

Нет, в argparse нет возможности создавать взаимно включающие наборы параметров.

Самый простой способ справиться с этим:

if args.prox and (args.lport is None or args.rport is None):
    parser.error("--prox requires --lport and --rport.")
borntyping
источник
2
Вот чем я закончил
asudhak
20
Спасибо за parser.errorметод, это то, что я искал!
MarSoft
7
не следует использовать "или"? в конце концов, вам нужны оба аргумента if args.prox and (args.lport is None or args.rport is None):
yossiz74
1
Вместо этого args.lport is Noneвы можете просто использовать not args.lport. Я думаю, это немного более питоническое.
CGFoX 01
7
Это остановит вас от установки --lportили --rportк 0, который может быть действительным вкладом в программу.
borntyping 01
53

Вы говорите об условно обязательных аргументах. Как сказал @borntyping, вы можете проверить наличие ошибки и сделать это parser.error(), или вы можете просто применить требование, связанное с --proxдобавлением нового аргумента.

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

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', required='--prox' in sys.argv, type=int)
non_int.add_argument('--rport', required='--prox' in sys.argv, type=int)

Этот способ requiredполучает либо, Trueлибо в Falseзависимости от того, используется ли пользователь --prox. Это также гарантирует, что -lportи -rportнезависимое поведение друг с другом.

Мир
источник
8
Обратите внимание, что ArgumentParserэто можно использовать для анализа аргументов из другого списка, кроме того sys.argv, в этом случае это не сработает.
BallpointBen
Также это не удастся, если использовать --prox=<value>синтаксис.
fnkr
11

Как об использовании parser.parse_known_args()метода , а затем добавляя --lportи --rportарг , как это требуется , если арг --proxприсутствует.

# just add --prox arg now
non_int = argparse.ArgumentParser(description="stackoverflow question", 
                                  usage="%(prog)s [-h] [--prox --lport port --rport port]")
non_int.add_argument('--prox', action='store_true', 
                     help='Flag to turn on proxy, requires additional args lport and rport')
opts, rem_args = non_int.parse_known_args()
if opts.prox:
    non_int.add_argument('--lport', required=True, type=int, help='Listen Port.')
    non_int.add_argument('--rport', required=True, type=int, help='Proxy port.')
    # use options and namespace from first parsing
    non_int.parse_args(rem_args, namespace = opts)

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

Недостатки:

  • Если --proxотсутствует, две другие зависимые опции даже не присутствуют в пространстве имен. --proxТо, что происходит с другими параметрами, не имеет значения, хотя и зависит от вашего варианта использования.
  • Необходимо изменить сообщение об использовании, так как парсер не знает полной структуры
  • --lportи --rportне отображаются в справочном сообщении
Адитья Шрирам
источник
5

Вы используете, lportкогда proxне установлен. Если нет, то почему бы не сделать lportи rportаргументы prox? например

parser.add_argument('--prox', nargs=2, type=int, help='Prox: listen and proxy ports')

Это избавляет ваших пользователей от набора текста. Его так же легко проверить, if args.prox is not None:как if args.prox:.

hpaulj
источник
1
Для полноты примера, когда ваш nargs> 1, вы получите список в проанализированном аргументе и т. Д., К которому вы можете обращаться обычным образом. Например, a,b = args.prox, a = args.prox[0]и т.д.
Dannid
1

Принятый ответ отлично сработал для меня! Поскольку весь код сломан без тестов, вот как я проверил принятый ответ. parser.error()не вызывает argparse.ArgumentErrorошибку, а выходит из процесса. Вы должны проверить SystemExit.

с pytest

import pytest
from . import parse_arguments  # code that rasises parse.error()


def test_args_parsed_raises_error():
    with pytest.raises(SystemExit):
        parse_arguments(["argument that raises error"])

с юнит-тестами

from unittest import TestCase
from . import parse_arguments  # code that rasises parse.error()

class TestArgs(TestCase):

    def test_args_parsed_raises_error():
        with self.assertRaises(SystemExit) as cm:
            parse_arguments(["argument that raises error"])

Вдохновленный: Использование unittest для тестирования argparse - ошибки выхода

Дэниел Батлер
источник