Argparse: обов'язковий аргумент 'y', якщо 'x' присутній


118

У мене є така вимога:

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

для аргументу prox я використовую action = 'store_true', щоб перевірити, чи він присутній чи ні. Я не вимагаю жодного аргументу. Але якщо встановлено --prox, мені потрібні також rport та lport. Чи існує простий спосіб зробити це з аргументом без написання спеціального умовного кодування.

Більше коду:

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.')

Підключився, але я хотів згадати мою бібліотеку joffrey . Дозволяє вам робити те, що хочеться на це запитання, наприклад, не змушуючи себе все перевіряти (як у прийнятій відповіді) або покладатися на лайфхак (як у відповіді другого за висотою голосування).
МІ Райт

Для тих , хто прибуває сюди, ще одне дивовижне рішення: stackoverflow.com/a/44210638/6045800
Tomerikoo

Відповіді:


120

Ні, не існує якоїсь - або параметр в argparse , щоб взаємно інклюзивних набори параметрів.

Найпростіший спосіб вирішити це:

if args.prox and (args.lport is None or args.rport is None):
    parser.error("--prox requires --lport and --rport.")

2
Ось що я закінчила робити
асудхак

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

7
Це не дозволить вам встановити --lportабо --rportдо 0, що може бути коректним входом у програму.
борнтіпінг

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:.


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

1

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

при піесті

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 - помилок виходу

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.