Argparse: 'x'が存在する場合、必須の引数 'y'


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

差し込みますが、ライブラリjoffreyについて言及したいと思います。たとえば、(承認された回答のように)自分ですべてを検証したり、(2番目に投票された応答のように)抜け穴ハックに依存したりせずに、この質問が望むことを実行できます。
MIライト、

ここに到着した人のために、別の素晴らしいソリューション: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
それが私がやったことです
asudhak 2013年

20
parser.errorメソッドをありがとう、これは私が探していたものです!
MarSoft、2015

7
「または」を使用すべきではありませんか?結局のところ、両方の引数が必要です if args.prox and (args.lport is None or args.rport is None):
yossiz74

1
の代わりにargs.lport is None、単純にを使用できますnot args.lport。私はそれがもう少しpythonicだと思います。
CGFoX 2018年

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 2018

また、--prox=<value>構文が使用されている場合、これは失敗します。
fnkr

11

parser.parse_known_args()メソッドを使用し、存在する場合は必要なargs --lportとしてと--rportargsを追加するのはどうですか--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、最初の解析後に生成された名前空間を指定し、残りの引数を2回目に解析できることにも注意してください。そうすれば、最終的に、すべての解析が完了した後、すべてのオプションを備えた単一の名前空間ができます。

欠点:

  • --proxが存在しない場合、他の2つの依存オプションは名前空間にも存在しません。あなたのユースケースに基づいています--proxが、存在しない場合、他のオプションに何が起こるかは関係ありません。
  • パーサーは完全な構造を知らないため、使用法メッセージを変更する必要があります
  • --lportそして、--rportヘルプメッセージに表示されません。

5

あなたが使用していlportたときにprox設定されていません。そうでない場合、なぜ作ってlportrport議論しないの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
例を完全にするために、nargsが> 1の場合、解析された引数などのリストを取得します。これは、通常の方法でアドレス指定できます。例えば、a,b = args.proxa = 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のテスト-終了エラー

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.