แสดงข้อความช่วยเหลือด้วย python argparse เมื่อสคริปต์ถูกเรียกโดยไม่มีข้อโต้แย้งใด ๆ


226

นี่อาจเป็นแบบง่าย ๆ สมมติว่าฉันมีโปรแกรมที่ใช้ argparse เพื่อประมวลผลอาร์กิวเมนต์บรรทัด / ตัวเลือก ต่อไปนี้จะพิมพ์ข้อความ 'ช่วยเหลือ':

./myprogram -h

หรือ:

./myprogram --help

แต่ถ้าฉันรันสคริปต์โดยไม่มีข้อโต้แย้งใด ๆ มันก็ไม่ทำอะไรเลย สิ่งที่ฉันต้องการให้ทำคือการแสดงข้อความการใช้งานเมื่อมันถูกเรียกโดยไม่มีข้อโต้แย้ง นั่นเป็นวิธีที่ทำ?

คำตอบ:


273

คำตอบนี้มาจากสตีเว่น Bethard กลุ่ม Google ฉันจะโพสต์ที่นี่อีกครั้งเพื่อให้ผู้ที่ไม่มีบัญชี Google เข้าถึงได้ง่ายขึ้น

คุณสามารถแทนที่พฤติกรรมเริ่มต้นของerrorวิธีการ:

import argparse
import sys

class MyParser(argparse.ArgumentParser):
    def error(self, message):
        sys.stderr.write('error: %s\n' % message)
        self.print_help()
        sys.exit(2)

parser = MyParser()
parser.add_argument('foo', nargs='+')
args = parser.parse_args()

โปรดทราบว่าการแก้ปัญหาข้างต้นจะพิมพ์ข้อความช่วยเหลือเมื่อใดก็ตามที่error วิธีการถูกเรียก ตัวอย่างเช่นtest.py --blahจะพิมพ์ข้อความช่วยเหลือด้วยหาก--blahไม่มีตัวเลือกที่ถูกต้อง

หากคุณต้องการพิมพ์ข้อความช่วยเหลือเฉพาะในกรณีที่ไม่มีการระบุอาร์กิวเมนต์ในบรรทัดคำสั่งบางทีนี่อาจเป็นวิธีที่ง่ายที่สุด:

import argparse
import sys

parser=argparse.ArgumentParser()
parser.add_argument('foo', nargs='+')
if len(sys.argv)==1:
    parser.print_help(sys.stderr)
    sys.exit(1)
args=parser.parse_args()

โปรดทราบว่าparser.print_help()พิมพ์เพื่อ stdout โดยค่าเริ่มต้น ตามที่init_js แนะนำให้ใช้parser.print_help(sys.stderr)เพื่อพิมพ์ไปยัง stderr


ใช่ .. นั่นคือสิ่งที่ฉันสงสัยว่าจะมีวิธีใดบ้างที่จะจัดการกับสถานการณ์นี้ ขอบคุณ!
musashiXXX

6
ในโซลูชันที่สองที่ฉันใช้parser.print_usage()แทนparser.print_help()ข้อความช่วยเหลือรวมถึงการใช้งาน แต่มีความละเอียดมากกว่า
user2314737

5
ฉันจะลงคะแนนให้ส่วนที่สองของคำตอบ แต่การเอาชนะerror()ดูเหมือนจะเป็นความคิดที่แย่สำหรับฉัน มันให้บริการเพื่อวัตถุประสงค์ที่แตกต่างกันมันไม่ได้ออกแบบมาสำหรับการพิมพ์การใช้งานที่เป็นมิตรหรือความช่วยเหลือ
Peterino

@Peterino - การแทนที่เกิดขึ้นในคลาสลูกดังนั้นสิ่งนี้ไม่ควรเป็นปัญหา มันชัดเจน
Marcel Wilson

1
@unutbu ยอดเยี่ยม! สิ่งที่ฉันต้องการ คำถามเดียวสามารถใช้กับคำสั่งย่อยได้หรือไม่ ฉันมักจะได้รับ `` Namespace (output = None) ' ฉันจะเรียกใช้ข้อผิดพลาดได้อย่างง่ายดายบนคำสั่งย่อยทั้งหมดได้อย่างไร ฉันต้องการเรียกใช้ข้อผิดพลาดที่นั่น
Jonathan Komar

56

แทนการเขียนชั้นเรียนลอง / ยกเว้นสามารถนำมาใช้แทน

try:
    options = parser.parse_args()
except:
    parser.print_help()
    sys.exit(0)

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

สิ่งนี้จะต้องมีอาร์กิวเมนต์ที่จำเป็นอย่างน้อยหนึ่งข้อ โดยไม่มีข้อโต้แย้งบังคับให้เป็นศูนย์ args ใน commandline ถูกต้อง


ฉันก็ชอบสิ่งนี้กับคำตอบที่ยอมรับ การเพิ่มคลาสเกินความจำเป็นสำหรับการพิมพ์เมื่ออาร์กิวเมนต์ไม่คาดคิด ให้ argparse โมดูลที่ยอดเยี่ยมจัดการกรณีข้อผิดพลาดสำหรับคุณ
Nicole Finnie

7
รหัสนี้พิมพ์ออกมาช่วยได้ 2 ครั้งหาก-hใช้งานธงและงานพิมพ์ที่ไม่จำเป็นจะช่วยในกรณีที่--versionใช้งานธง เพื่อลดปัญหาเหล่านี้คุณสามารถตรวจสอบประเภทข้อผิดพลาดดังนี้:except SystemExit as err: if err.code == 2: parser.print_help()
pkowalczyk

25

ด้วย argparse คุณสามารถทำได้:

parser.argparse.ArgumentParser()
#parser.add_args here

#sys.argv includes a list of elements starting with the program
if len(sys.argv) < 2:
    parser.print_usage()
    sys.exit(1)

5
สิ่งนี้จะต้องมาก่อนการเรียกร้องให้parser.parse_args()
Bob Stein

18

หากคุณมีข้อโต้แย้งที่ต้องระบุเพื่อให้สคริปต์ทำงาน - ใช้พารามิเตอร์ที่จำเป็นสำหรับ ArgumentParser ดังแสดงด้านล่าง: -

parser.add_argument('--foo', required=True)

parse_args () จะรายงานข้อผิดพลาดหากสคริปต์ทำงานโดยไม่มีข้อโต้แย้งใด ๆ


2
นี่เป็นวิธีที่ง่ายที่สุดและจะทำงานกับตัวเลือกที่ไม่ถูกต้องเช่นกัน
Steve Scherer

1
ตกลง ฉันคิดว่ามันจะดีกว่าเสมอในการใช้ความสามารถในตัวของตัวแยกวิเคราะห์อาร์กิวเมนต์เพื่อเขียนตัวจัดการเพิ่มเติมบางชนิด
Christopher Hunter

18

หากคุณเชื่อมโยงฟังก์ชั่นเริ่มต้นสำหรับตัวแยกวิเคราะห์ (ย่อย) ตามที่กล่าวไว้ข้างล่างadd_subparsersคุณสามารถเพิ่มมันเป็นการกระทำเริ่มต้น:

parser = argparse.ArgumentParser()
parser.set_defaults(func=lambda x: parser.print_usage())
args = parser.parse_args()
args.func(args)

เพิ่มข้อลองถ้าคุณเพิ่มข้อยกเว้นเนื่องจากไม่มีข้อโต้แย้งตำแหน่ง


1
คำตอบนี้มีการประเมินต่ำเกินไป เรียบง่ายและทำงานได้ดีมากกับตัวแยกวิเคราะห์ย่อย
orodbhen

คำตอบที่ดี! การเปลี่ยนแปลงเดียวที่ฉันทำคือใช้แลมบ์ดาโดยไม่มีพารามิเตอร์
boh717

12

โซลูชันที่สะอาดที่สุดจะส่งผ่านอาร์กิวเมนต์เริ่มต้นด้วยตนเองหากไม่มีการระบุในบรรทัดคำสั่ง:

parser.parse_args(args=None if sys.argv[1:] else ['--help'])

ตัวอย่างที่สมบูรณ์:

import argparse, sys

parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost', help='Host to connect to')
# parse arguments
args = parser.parse_args(args=None if sys.argv[1:] else ['--help'])

# use your args
print("connecting to {}".format(args.host))

นี่จะพิมพ์วิธีใช้ที่สมบูรณ์ (ไม่ใช่การใช้งานสั้น) หากเรียกว่าอาร์กิวเมนต์ที่ไม่มี


2
sys.argv[1:]เป็นสำนวนที่พบบ่อยมาก ฉันเห็นparser.parse_args(None if sys.argv[1:] else ['-h'])สำนวนและทำความสะอาดมากกว่า
Nuno André

1
@ NunoAndréขอบคุณ - อัปเดตคำตอบ รู้สึกไพเราะมากขึ้นแน่นอน
Ievgen Popovych

10

โยนรุ่นของฉันลงในกองที่นี่:

import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
if not vars(args):
    parser.print_help()
    parser.exit(1)

คุณอาจสังเกตเห็นว่าparser.exit- ฉันทำเช่นนั้นเป็นส่วนใหญ่เพราะมันจะบันทึกบรรทัดการนำเข้าหากนั่นเป็นเหตุผลเดียวsysในไฟล์ ...


parser.exit (1) ดีมาก! นอกจากนี้ที่ดี
cgseller

4
น่าเสียดายที่ parser.parse_args () จะออกหากไม่มีข้อโต้แย้งตำแหน่ง ดังนั้นจะใช้งานได้เฉพาะเมื่อใช้อาร์กิวเมนต์ที่เป็นตัวเลือก
Marcel Wilson

1
@MarcelWilson มันไม่แน่นอน - จับดี! ฉันจะคิดเกี่ยวกับวิธีการเปลี่ยนแปลง
pauricthelodger

not vars(args)อาจไม่ทำงานเมื่ออาร์กิวเมนต์มีdefaultเมธอด
funkid

5

มีคู่ของหนึ่งสมุทรด้วยsys.argv[1:](สำนวนธรรมดามากของงูใหญ่ที่จะอ้างถึงอาร์กิวเมนต์บรรทัดคำสั่งเป็นsys.argv[0]เป็นชื่อของสคริปต์) ที่สามารถทำงานได้

คนแรกคืออธิบายตนเองสะอาดและ pythonic:

args = parser.parse_args(None if sys.argv[1:] else ['-h'])

อันที่สองคือแฮ็กเกอร์ตัวน้อย รวมข้อเท็จจริงที่ประเมินก่อนหน้านี้ว่ารายการว่างอยู่FalseกับTrue == 1และFalse == 0คุณได้สิ่งนี้:

args = parser.parse_args([None, ['-h']][not sys.argv[1:]])

อาจมีวงเล็บมากเกินไป แต่ก็ค่อนข้างชัดเจนหากมีการเลือกอาร์กิวเมนต์ก่อนหน้า

_, *av = sys.argv
args = parser.parse_args([None, ['-h']][not av])

1
parser.print_help()
parser.exit()

parser.exitวิธียังยอมรับstatus(รหัสผลลัพธ์) และmessageค่า (รวมถึงการขึ้นบรรทัดใหม่ต่อท้ายตัวเอง!)

ตัวอย่างที่มีความเห็น :)

#!/usr/bin/env python3

""" Example argparser based python file
"""

import argparse

ARGP = argparse.ArgumentParser(
    description=__doc__,
    formatter_class=argparse.RawTextHelpFormatter,
)
ARGP.add_argument('--example', action='store_true', help='Example Argument')


def main(argp=None):
    if argp is None:
        argp = ARGP.parse_args()  # pragma: no cover

    if 'soemthing_went_wrong' and not argp.example:
        ARGP.print_help()
        ARGP.exit(status=128, message="\nI just don't know what went wrong, maybe missing --example condition?\n")


if __name__ == '__main__':
    main()  # pragma: no cover

ตัวอย่างการโทร:

$ python3 ~ / helloworld.py; echo $
การใช้งาน: helloworld.py [-h] [- ตัวอย่าง]

 ตัวอย่างไฟล์ไพ ธ อนอ้างอิงจาก argparser

อาร์กิวเมนต์ตัวเลือก:
  -h, - ช่วยแสดงข้อความช่วยเหลือนี้และออก
  - ตัวอย่างอาร์กิวเมนต์ตัวอย่าง

ฉันไม่ทราบว่ามีอะไรผิดพลาดอาจหายไป - เงื่อนไขตัวอย่าง?
128
$ python3 ~ / helloworld.py - ตัวอย่าง; echo $
0

0

ตั้งค่าอาร์กิวเมนต์ตำแหน่งของคุณด้วย nargs และตรวจสอบว่า args ตำแหน่งว่างเปล่าหรือไม่

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file', nargs='?')
args = parser.parse_args()
if not args.file:
    parser.print_help()

งูหลามอ้างอิง


0

นี่เป็นอีกวิธีในการทำถ้าคุณต้องการบางสิ่งบางอย่างที่ยืดหยุ่นที่คุณต้องการแสดงความช่วยเหลือถ้า params เฉพาะผ่านไม่มีใครขัดแย้งหรือมากกว่า 1 arg ขัดแย้งกัน:

import argparse
import sys

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--days', required=False,  help="Check mapped inventory that is x days old", default=None)
    parser.add_argument('-e', '--event', required=False, action="store", dest="event_id",
                        help="Check mapped inventory for a specific event", default=None)
    parser.add_argument('-b', '--broker', required=False, action="store", dest="broker_id",
                        help="Check mapped inventory for a broker", default=None)
    parser.add_argument('-k', '--keyword', required=False, action="store", dest="event_keyword",
                        help="Check mapped inventory for a specific event keyword", default=None)
    parser.add_argument('-p', '--product', required=False, action="store", dest="product_id",
                        help="Check mapped inventory for a specific product", default=None)
    parser.add_argument('-m', '--metadata', required=False, action="store", dest="metadata",
                        help="Check mapped inventory for specific metadata, good for debugging past tix", default=None)
    parser.add_argument('-u', '--update', required=False, action="store_true", dest="make_updates",
                        help="Update the event for a product if there is a difference, default No", default=False)
    args = parser.parse_args()

    days = args.days
    event_id = args.event_id
    broker_id = args.broker_id
    event_keyword = args.event_keyword
    product_id = args.product_id
    metadata = args.metadata
    make_updates = args.make_updates

    no_change_counter = 0
    change_counter = 0

    req_arg = bool(days) + bool(event_id) + bool(broker_id) + bool(product_id) + bool(event_keyword) + bool(metadata)
    if not req_arg:
        print("Need to specify days, broker id, event id, event keyword or past tickets full metadata")
        parser.print_help()
        sys.exit()
    elif req_arg != 1:
        print("More than one option specified. Need to specify only one required option")
        parser.print_help()
        sys.exit()

    # Processing logic here ...

ไชโย!


ฉันคิดว่าคุณจะใช้ subparsers ได้ง่ายขึ้นหรือ mutually_exclusive_group
Tim Bray

0

ถ้าคำสั่งของคุณเป็นสิ่งที่ผู้ใช้ต้องการที่จะเลือกการกระทำบางอย่างแล้วใช้กลุ่มพิเศษร่วมกันกับต้อง = True

นี่คือส่วนขยายของคำตอบที่ได้รับจาก pd321

import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--batch", action='store', type=int,  metavar='pay_id')
group.add_argument("--list", action='store_true')
group.add_argument("--all", action='store_true', help='check all payments')

args=parser.parse_args()

if args.batch:
    print('batch {}'.format(args.batch))

if args.list:
    print('list')

if args.all:
    print('all')

เอาท์พุท:

$ python3 a_test.py
การใช้งาน: a_test.py [-h] (- แบทช์ pay_id | - รายการ | - ทั้งหมด)
a_test.py: ข้อผิดพลาด: อาร์กิวเมนต์ตัวใดตัวหนึ่ง - แบทช์ - รายการ - จำเป็นต้องใช้ทั้งหมด

สิ่งนี้จะให้ความช่วยเหลือพื้นฐานเท่านั้น และคำตอบอื่น ๆ จะให้ความช่วยเหลืออย่างเต็มที่กับคุณ แต่อย่างน้อยผู้ใช้ของคุณรู้ว่าพวกเขาสามารถทำได้-h


0

สิ่งนี้ไม่ดี (เช่นกันเพราะดักจับข้อผิดพลาดทั้งหมด) แต่:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)

    parser.add_argument(...)
    ...

นี่คือนิยามของerrorฟังก์ชันของArgumentParserคลาส:

https://github.com/python/cpython/blob/276eb67c29d05a93fbc22eea5470282e73700d20/Lib/argparse.py#L2374

. ตามที่คุณเห็นลายเซ็นต่อไปนี้จะใช้เวลาสองข้อโต้แย้ง อย่างไรก็ตามฟังก์ชั่นนอกคลาสไม่มีสิ่งใดที่รู้เกี่ยวกับอาร์กิวเมนต์แรก: selfเพราะโดยคร่าวๆนี่เป็นพารามิเตอร์สำหรับคลาส (ฉันรู้ว่าคุณรู้ว่า ... ) ดังนั้นเพียงแค่ผ่านของตัวเองselfและmessageใน_error(...)ไม่สามารถ (

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error
    ...
...

จะส่งออก:

...
"AttributeError: 'str' object has no attribute 'print_help'"

) คุณสามารถผ่านparser( self) ใน_errorฟังก์ชันโดยเรียกมันว่า:

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)
    ...
...

แต่คุณไม่ต้องการออกจากโปรแกรมทันที จากนั้นส่งคืน:

def _error(parser):
    def wrapper():
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. อย่างไรก็ตามparserไม่ทราบว่ามันถูกแก้ไขดังนั้นเมื่อเกิดข้อผิดพลาดมันจะส่งสาเหตุของปัญหา (โดยวิธีการแปลที่แปลเป็นภาษาท้องถิ่น) เอาล่ะสกัดมัน:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. ตอนนี้เมื่อเกิดข้อผิดพลาดและparserจะส่งสาเหตุของมันคุณจะดักมันดูที่นี่และ ... โยนออกไป

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