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

เสียบ แต่ผมอยากจะพูดถึงห้องสมุดของฉันJoffrey ให้คุณทำในสิ่งที่คำถามนี้ต้องการตัวอย่างเช่นโดยไม่ต้องทำให้คุณตรวจสอบทุกอย่างด้วยตัวเอง (เช่นเดียวกับคำตอบที่ยอมรับ) หรืออาศัยการแฮ็กช่องโหว่ (เช่นเดียวกับการตอบกลับที่ได้รับการโหวตสูงสุดเป็นอันดับสอง)
MI Wright

สำหรับทุกคนที่มาถึงที่นี่ทางออกที่น่าทึ่งอีกอย่าง: stackoverflow.com/a/44210638/6045800
Tomerikoo

คำตอบ:


121

ไม่มีมีไม่ได้ตัวเลือกใด ๆ ใน argparse เพื่อให้ร่วมกันรวมชุดของตัวเลือก

วิธีที่ง่ายที่สุดในการจัดการกับปัญหานี้คือ:

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


20
ขอบคุณสำหรับparser.errorวิธีการนี่คือสิ่งที่ฉันกำลังมองหา!
MarSoft

7
คุณไม่ควรใช้ "หรือ"? ท้ายที่สุดคุณต้องใช้ทั้งสอง args if args.prox and (args.lport is None or args.rport is None):
yossiz74

1
แต่args.lport is Noneคุณสามารถใช้not args.lportไฟล์. ฉันคิดว่ามันเป็น pythonic เล็กน้อย
CGFoX

7
นั่นจะทำให้คุณหยุดการตั้งค่า--lportหรือ--rportถึง0ซึ่งอาจเป็นอินพุตที่ถูกต้องของโปรแกรม
borntyping

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และ--rportargs เป็น args ที่ต้องการถ้า--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ไม่มีอยู่สิ่งที่เกิดขึ้นกับตัวเลือกอื่น ๆ ก็ไม่เกี่ยวข้อง
  • ต้องแก้ไขข้อความการใช้งานเนื่องจาก parser ไม่ทราบโครงสร้างทั้งหมด
  • --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
เพื่อความสมบูรณ์ของตัวอย่างเมื่อ 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"])

กับ 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"])

แรงบันดาลใจจาก: ใช้ unittest เพื่อทดสอบข้อผิดพลาด argparse - exit

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.