Python argparse: สร้างอาร์กิวเมนต์อย่างน้อยหนึ่งอาร์กิวเมนต์


96

ผมเคยใช้argparseสำหรับโปรแกรมหลามที่สามารถ-process, -uploadหรือทั้งสอง:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload',  action='store_true')
args = parser.parse_args()

โปรแกรมไม่มีความหมายหากไม่มีพารามิเตอร์อย่างน้อยหนึ่งตัว ฉันargparseจะกำหนดค่าเพื่อบังคับให้เลือกพารามิเตอร์อย่างน้อยหนึ่งพารามิเตอร์ได้อย่างไร

อัพเดท:

ตามความคิดเห็น: อะไรคือวิธี Pythonic ในการพารามิเตอร์โปรแกรมที่มีอย่างน้อยหนึ่งตัวเลือก?


9
-xเป็นธงสากลและเป็นทางเลือก ตัด-ถ้าจำเป็น

1
ไม่สามารถที่คุณทำprocessพฤติกรรมเริ่มต้น (โดยไม่จำเป็นต้องระบุตัวเลือกใด ๆ ) และให้ผู้ใช้มีการเปลี่ยนแปลงที่เป็นuploadถ้าที่ตั้งค่าตัวเลือก? โดยปกติตัวเลือกควรเป็นทางเลือกดังนั้นชื่อ ควรหลีกเลี่ยงตัวเลือกที่จำเป็น (มีอยู่ในargparse เอกสารด้วย)
Tim Pietzcker

@AdamMatan เป็นเวลาเกือบสามปีแล้วที่คุณถามคำถามของคุณ แต่ฉันชอบความท้าทายที่ซ่อนอยู่ในนั้นและใช้ประโยชน์จากโซลูชันใหม่ที่มีให้สำหรับงานประเภทนี้
ม.ค. Vlcinsky

คำตอบ:


113
if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')

1
นั่นอาจเป็นวิธีเดียวหากargparseไม่มีตัวเลือกในตัวสำหรับสิ่งนี้
Adam Matan

32
args = vars(parser.parse_args())
if not any(args.values()):
    parser.error('No arguments provided.')

3
+1 สำหรับโซลูชันทั่วไป เช่นเดียวกับการใช้งานvars()ซึ่งมีประโยชน์สำหรับการส่งผ่านตัวเลือกที่มีชื่ออย่างรอบคอบไปยังตัวสร้างด้วย **
Lenna

ซึ่งเป็นสิ่งที่ฉันทำกับมัน ขอบคุณ!
brentlance

1
varsแดงผมเช่นนั้น ฉันเพิ่งทำ.__dict__และรู้สึกโง่มาก่อน
Theo Belaire

1
คำตอบที่ดี ทั้ง "vars" และ "any" เป็นเรื่องใหม่สำหรับฉัน :-)
Vivek Jha

21

ถ้าไม่ใช่ส่วน 'หรือทั้งสอง' (ตอนแรกฉันพลาดไปแล้ว) คุณสามารถใช้สิ่งนี้:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
    parser.error("One of --process or --upload must be given")

แม้ว่าอาจเป็นความคิดที่ดีกว่าที่จะใช้คำสั่งย่อยแทน


4
ฉันคิดว่าเขาต้องการอนุญาต--processOR --uploadไม่ใช่ XOR วิธีนี้จะป้องกันไม่ให้ตั้งค่าตัวเลือกทั้งสองพร้อมกัน
phihag

+1 เนื่องจากคุณกล่าวถึงคำสั่งย่อย ยัง - ตามที่ใครบางคนชี้ไว้ในความคิดเห็น-xและ--xxxโดยทั่วไปแล้วจะเป็นพารามิเตอร์ที่ไม่บังคับ
แมค

20

ฉันรู้ว่ามันเก่าเหมือนดิน แต่วิธีที่ต้องใช้ตัวเลือกเดียว แต่ห้ามมากกว่าหนึ่ง (XOR) มีดังนี้:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()
print args

เอาท์พุต:

>opt.py  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: one of the arguments -process -upload is required  

>opt.py -upload  
Namespace(process=False, upload=True)  

>opt.py -process  
Namespace(process=True, upload=False)  

>opt.py -upload -process  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: argument -process: not allowed with argument -upload  

4
น่าเสียดายที่ OP ไม่ต้องการ XOR เป็นอย่างใดอย่างหนึ่งหรือทั้งสองอย่าง แต่ไม่ใช่เลยดังนั้นกรณีทดสอบสุดท้ายของคุณจึงไม่เป็นไปตามข้อกำหนด
kdopen

3
@kdopen: ผู้ตอบชี้แจงว่านี่เป็นรูปแบบของคำถามเดิมซึ่งฉันพบว่ามีประโยชน์: "วิธีที่ต้องการตัวเลือกเดียว แต่ห้ามมากกว่าหนึ่งตัว" บางทีมารยาทของ Stack Exchange อาจเรียกคำถามใหม่แทน . แต่การมีคำตอบอยู่ที่นี่ช่วยฉันได้ ...
erik.weathers

2
โพสต์นี้ไม่ตอบคำถามเริ่มต้น
Marc

2
คำถามนี้ตอบคำถาม "อย่างน้อยหนึ่งข้อ" อย่างไร
xaxxon

2
น่าเสียดายที่ OP ไม่ต้องการ XOR
duckman_1991

8

ทบทวนข้อกำหนด

  • ใช้argparse(ฉันจะไม่สนใจอันนี้)
  • อนุญาตให้เรียกหนึ่งหรือสองการดำเนินการ (จำเป็นอย่างน้อยหนึ่งรายการ)
  • พยายามโดย Pythonic (ฉันอยากจะเรียกว่า "POSIX" เหมือน)

นอกจากนี้ยังมีข้อกำหนดโดยนัยบางประการเมื่ออยู่ในบรรทัดคำสั่ง:

  • อธิบายการใช้งานให้ผู้ใช้เข้าใจได้ง่าย
  • ตัวเลือกจะเป็นทางเลือก
  • อนุญาตให้ระบุแฟล็กและอ็อพชัน
  • อนุญาตให้รวมกับพารามิเตอร์อื่น ๆ (เช่นชื่อไฟล์หรือชื่อ)

ตัวอย่างการแก้ปัญหาโดยใช้docopt(ไฟล์managelog.py):

"""Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

ลองเรียกใช้:

$ python managelog.py
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

แสดงความช่วยเหลือ:

$ python managelog.py -h
Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>

และใช้มัน:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
 '--pswd': 'secret',
 '--user': 'user',
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': False,
 'upload': True}

ทางเลือกสั้น ๆ short.py

อาจมีตัวแปรที่สั้นกว่านี้ได้:

"""Manage logfiles
Usage:
    short.py [options] (process|upload)... -- <logfile>...
    short.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

การใช้งานมีลักษณะดังนี้:

$ python short.py -V process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 1,
 'upload': 1}

โปรดทราบว่าแทนที่จะเป็นค่าบูลีนสำหรับคีย์ "กระบวนการ" และ "อัปโหลด" จะมีตัวนับ

ปรากฎว่าเราไม่สามารถป้องกันการซ้ำซ้อนของคำเหล่านี้:

$ python short.py -V process process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 2,
 'upload': 1}

ข้อสรุป

การออกแบบอินเทอร์เฟซบรรทัดคำสั่งที่ดีอาจเป็นเรื่องท้าทายในบางครั้ง

มีหลายแง่มุมของโปรแกรมที่ใช้บรรทัดคำสั่ง:

  • การออกแบบบรรทัดคำสั่งที่ดี
  • การเลือก / ใช้โปรแกรมแยกวิเคราะห์ที่เหมาะสม

argparse มีข้อเสนอมากมาย แต่ จำกัด สถานการณ์ที่เป็นไปได้และอาจซับซ้อนมาก

เมื่อdocoptสิ่งต่างๆสั้นลงมากในขณะที่ยังคงรักษาความสามารถในการอ่านและให้ความยืดหยุ่นในระดับสูง หากคุณจัดการการรับอาร์กิวเมนต์ที่แยกวิเคราะห์จากพจนานุกรมและทำการแปลงบางส่วน (เป็นจำนวนเต็มการเปิดไฟล์ .. ) ด้วยตนเอง (หรือโดยไลบรารีอื่นที่เรียกว่าschema) คุณอาจพบว่าdocoptเหมาะสมสำหรับการแยกวิเคราะห์บรรทัดคำสั่ง


ไม่เคยได้ยิน docopt ข้อเสนอแนะที่ดี!
Ton van den Heuvel

@TonvandenHeuvel ดี. ฉันแค่ต้องการยืนยันว่าฉันยังคงใช้มันเป็นโซลูชันที่ฉันต้องการสำหรับอินเทอร์เฟซบรรทัดคำสั่ง
ม.ค. Vlcinsky

คำตอบที่ดีที่สุดขอขอบคุณสำหรับตัวอย่างโดยละเอียด
jnovack

5

หากคุณต้องการให้โปรแกรม python ทำงานด้วยพารามิเตอร์อย่างน้อยหนึ่งตัวให้เพิ่มอาร์กิวเมนต์ที่ไม่มีคำนำหน้าตัวเลือก (- หรือ - โดยค่าเริ่มต้น) และตั้งค่าnargs=+(ขั้นต่ำของหนึ่งอาร์กิวเมนต์ที่จำเป็น) ปัญหาเกี่ยวกับวิธีนี้ที่ฉันพบคือถ้าคุณไม่ระบุอาร์กิวเมนต์ argparse จะสร้างข้อผิดพลาด "อาร์กิวเมนต์น้อยเกินไป" และไม่พิมพ์เมนูวิธีใช้ หากคุณไม่ต้องการฟังก์ชั่นดังกล่าวนี่คือวิธีการทำในโค้ด:

import argparse

parser = argparse.ArgumentParser(description='Your program description')
parser.add_argument('command', nargs="+", help='describe what a command is')
args = parser.parse_args()

ฉันคิดว่าเมื่อคุณเพิ่มอาร์กิวเมนต์ด้วยคำนำหน้าตัวเลือก nargs จะควบคุมตัวแยกวิเคราะห์อาร์กิวเมนต์ทั้งหมดไม่ใช่แค่ตัวเลือกเท่านั้น (สิ่งที่ผมหมายถึงคือถ้าคุณมี--optionธงnargs="+"แล้ว--optionคาดว่าธงอย่างน้อยหนึ่งอาร์กิวเมนต์. ถ้าคุณมีoptionด้วยnargs="+"ก็คาดว่าอย่างน้อยหนึ่งข้อโต้แย้งโดยรวม.)


คุณสามารถเพิ่มchoices=['process','upload']อาร์กิวเมนต์นั้นได้
hpaulj

5

สำหรับhttp://bugs.python.org/issue11588ฉันกำลังสำรวจวิธีการสรุปmutually_exclusive_groupแนวคิดเพื่อจัดการกรณีเช่นนี้

กับการพัฒนานี้argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py ฉันสามารถเขียน:

parser = argparse.ArgumentParser(prog='PROG', 
    description='Log archiver arguments.')
group = parser.add_usage_group(kind='any', required=True,
    title='possible actions (at least one is required)')
group.add_argument('-p', '--process', action='store_true')
group.add_argument('-u', '--upload',  action='store_true')
args = parser.parse_args()
print(args)

ซึ่งก่อให้เกิดสิ่งต่อไปนี้help:

usage: PROG [-h] (-p | -u)

Log archiver arguments.

optional arguments:
  -h, --help     show this help message and exit

possible actions (at least one is required):
  -p, --process
  -u, --upload

สิ่งนี้รับอินพุตเช่น '-u', '-up', '--proc --up' เป็นต้น

สิ้นสุดการทดสอบที่คล้ายกับhttps://stackoverflow.com/a/6723066/901925แม้ว่าข้อความแสดงข้อผิดพลาดจะต้องชัดเจนกว่านี้:

usage: PROG [-h] (-p | -u)
PROG: error: some of the arguments process upload is required

ฉันสงสัยว่า:

  • พารามิเตอร์มีkind='any', required=Trueความชัดเจนเพียงพอหรือไม่ (ยอมรับกลุ่มใดกลุ่มหนึ่งจำเป็นต้องมีอย่างน้อยหนึ่งตัว)

  • การใช้งาน(-p | -u)ชัดเจนไหม mutually_exclusive_group ที่จำเป็นต้องสร้างสิ่งเดียวกัน มีสัญกรณ์อื่นหรือไม่?

  • ใช้กลุ่มแบบนี้ง่ายกว่าphihag'sการทดสอบแบบธรรมดาหรือไม่


ไม่พบการกล่าวถึงใด ๆadd_usage_groupในหน้านี้: docs.python.org/2/library/argparse.html ; คุณช่วยส่งลิงค์ไปยังเอกสารประกอบได้ไหม
P. Myer Nore

@ P.MyerNore ฉันได้ให้ลิงค์ - ที่จุดเริ่มต้นของคำตอบนี้ สิ่งนี้ไม่ได้ถูกนำไปผลิต
hpaulj

5

วิธีที่ดีที่สุดที่จะทำเช่นนี้คือการใช้หลาม inbuilt โมดูลadd_mutually_exclusive_group

parser = argparse.ArgumentParser(description='Log archiver arguments.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()

หากคุณต้องการให้เลือกเพียงหนึ่งอาร์กิวเมนต์โดยบรรทัดคำสั่งให้ใช้ required = True เป็นอาร์กิวเมนต์สำหรับกลุ่ม

group = parser.add_mutually_exclusive_group(required=True)

2
สิ่งนี้ทำให้คุณได้ "อย่างน้อยหนึ่ง" ได้อย่างไร - ไม่ทำให้คุณเป็น "หนึ่งเดียว" ใช่หรือไม่?
xaxxon

3
น่าเสียดายที่ OP ไม่ต้องการ XOR OP กำลังมองหา OR
duckman_1991

สิ่งนี้ไม่ได้ตอบคำถามของ OP แต่ก็ตอบฉันขอบคุณอยู่ดี¯_ (ツ) _ / ¯
rosstex

2

อาจใช้ตัวแยกวิเคราะห์ย่อย?

import argparse

parser = argparse.ArgumentParser(description='Log archiver arguments.')
subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
parser_process = subparsers.add_parser('process', help='Process logs')
parser_upload = subparsers.add_parser('upload', help='Upload logs')
args = parser.parse_args()

print("Subparser: ", args.subparser_name)

ตอนนี้--helpแสดง:

$ python /tmp/aaa.py --help
usage: aaa.py [-h] {process,upload} ...

Log archiver arguments.

positional arguments:
  {process,upload}  sub-command help
    process         Process logs
    upload          Upload logs

optional arguments:
  -h, --help        show this help message and exit
$ python /tmp/aaa.py
usage: aaa.py [-h] {process,upload} ...
aaa.py: error: too few arguments
$ python3 /tmp/aaa.py upload
Subparser:  upload

คุณสามารถเพิ่มตัวเลือกเพิ่มเติมให้กับตัวแยกวิเคราะห์ย่อยเหล่านี้ได้เช่นกัน นอกจากนี้แทนที่จะใช้dest='subparser_name'คุณยังสามารถผูกฟังก์ชันที่จะเรียกโดยตรงกับคำสั่งย่อยที่กำหนด (ดูเอกสาร)


2

สิ่งนี้บรรลุวัตถุประสงค์และสิ่งนี้จะถูกตรวจสอบซ้ำใน--helpเอาต์พุตที่สร้างขึ้นโดยอัตโนมัติของอาร์กิวเมนต์ซึ่งเป็นสิ่งที่โปรแกรมเมอร์ที่มีสติส่วนใหญ่ต้องการ (ใช้งานได้กับอาร์กิวเมนต์ที่เป็นทางเลือก):

parser.add_argument(
    'commands',
    nargs='+',                      # require at least 1
    choices=['process', 'upload'],  # restrict the choice
    help='commands to execute'
)

เอกสารอย่างเป็นทางการเกี่ยวกับเรื่องนี้: https://docs.python.org/3/library/argparse.html#choices


1

ใช้ append_const กับรายการการดำเนินการจากนั้นตรวจสอบว่ามีการเติมข้อมูลในรายการ:

parser.add_argument('-process', dest=actions, const="process", action='append_const')
parser.add_argument('-upload',  dest=actions, const="upload", action='append_const')

args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

คุณสามารถระบุวิธีการได้โดยตรงภายในค่าคงที่

def upload:
    ...

parser.add_argument('-upload',  dest=actions, const=upload, action='append_const')
args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

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