Argparse: Argumen wajib 'y' jika 'x' ada

118

Saya memiliki persyaratan sebagai berikut:

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

untuk argumen prox, saya menggunakan action = 'store_true' untuk memeriksa apakah ada atau tidak. Saya tidak membutuhkan argumen apapun. Tapi, jika --prox di set, saya membutuhkan rport dan lport juga. Apakah ada cara mudah untuk melakukan ini dengan argparse tanpa menulis pengkodean bersyarat khusus.

Kode Lainnya:

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
sumber
Menancapkan, tetapi saya ingin menyebutkan perpustakaan saya joffrey . Memungkinkan Anda melakukan apa yang diinginkan pertanyaan ini, misalnya, tanpa membuat Anda memvalidasi semuanya sendiri (seperti dalam jawaban yang diterima) atau mengandalkan peretasan celah (seperti dalam respons dengan suara terbanyak kedua).
MI Wright
Bagi siapa pun yang tiba di sini, solusi luar biasa lainnya: stackoverflow.com/a/44210638/6045800
Tomerikoo

Jawaban:

120

Tidak, tidak ada opsi di argparse untuk membuat kumpulan opsi yang saling inklusif .

Cara termudah untuk mengatasinya adalah:

if args.prox and (args.lport is None or args.rport is None):
    parser.error("--prox requires --lport and --rport.")
borntyping
sumber
2
Itulah yang akhirnya saya lakukan
asudhak
20
Terima kasih untuk parser.errormetodenya, inilah yang saya cari!
MarSoft
7
bukankah seharusnya Anda menggunakan 'atau'? setelah semua Anda membutuhkan kedua argumen if args.prox and (args.lport is None or args.rport is None):
yossiz74
1
Alih-alih args.lport is None, Anda cukup menggunakan not args.lport. Saya pikir itu sedikit lebih pythonic.
CGFoX
7
Itu akan menghentikan Anda dari pengaturan --lportatau --rportke 0, yang mungkin merupakan masukan yang valid untuk program.
borntyping
53

Anda sedang berbicara tentang memiliki argumen yang diwajibkan secara bersyarat. Seperti yang dikatakan @borntyping, Anda dapat memeriksa kesalahan dan melakukan parser.error(), atau Anda dapat menerapkan persyaratan yang terkait dengan --proxsaat Anda menambahkan argumen baru.

Solusi sederhana untuk contoh Anda adalah:

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)

Cara ini requiredmenerima salah satu Trueatau Falsetergantung pada apakah pengguna digunakan --prox. Ini juga menjamin -lportdan -rportmemiliki perilaku independen antara satu sama lain.

Mira
sumber
8
Perhatikan bahwa ArgumentParserdapat digunakan untuk mengurai argumen dari daftar selain sys.argv, dalam hal ini akan gagal.
BallpointBen
Ini juga akan gagal jika --prox=<value>sintaks digunakan.
fnkr
11

Bagaimana kalau menggunakan parser.parse_known_args()metode dan kemudian menambahkan --lportdan --rportargs sebagai diperlukan args jika --proxada.

# 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)

Juga perlu diingat bahwa Anda dapat menyediakan namespace yang optsdihasilkan setelah penguraian pertama sambil mengurai argumen yang tersisa untuk kedua kalinya. Dengan begitu, pada akhirnya, setelah semua penguraian selesai, Anda akan memiliki satu namespace dengan semua opsi.

Kekurangan:

  • Jika --proxtidak ada, dua opsi dependen lainnya bahkan tidak ada di namespace. Meskipun berdasarkan kasus penggunaan Anda, jika --proxtidak ada, apa yang terjadi pada opsi lain tidak relevan.
  • Perlu mengubah pesan penggunaan karena parser tidak mengetahui struktur lengkapnya
  • --lportdan --rporttidak muncul di pesan bantuan
Aditya Sriram
sumber
5

Apakah Anda menggunakan lportsaat proxtidak diatur. Jika tidak, mengapa tidak membuat lportdan rportmemperdebatkan prox? misalnya

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

Pengguna Anda tidak perlu mengetik. Hal ini hanya sebagai mudah untuk tes if args.prox is not None:sebagai if args.prox:.

hpaulj
sumber
1
Untuk kelengkapan contoh, jika nilai Anda> 1, Anda akan mendapatkan daftar di argumen parsing, dll., Yang dapat Anda atasi dengan cara biasa. Misalnya, a,b = args.prox, a = args.prox[0], dll
Dannid
1

Jawaban yang diterima bekerja dengan baik untuk saya! Karena semua kode rusak tanpa tes, inilah cara saya menguji jawaban yang diterima. parser.error()tidak menimbulkan argparse.ArgumentErrorkesalahan melainkan keluar dari proses. Anda harus mengujinya SystemExit.

dengan 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"])

dengan unittests

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"])

terinspirasi dari: Menggunakan unittest untuk menguji argparse - kesalahan keluar

Daniel Butler
sumber