Argparse

การสร้าง CLI สำหรับ script ที่รับตัวแปรตอนเรียกโปรแกรมนั้น แม้จะใช้ sys.argv เพื่อดึงส่วน argument ได้ แต่เมื่อต้องทำงานที่ซับซ้อนแล้ว module ที่จะช่วยให้งานนี้เป็นระเบียบเรียบร้อยคือ argparse

ตัวอย่างเช่น โปรแกรม sq.py ที่จะทำการยกกำลังสองเลขที่รับเข้ามา

    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument('num')
    args = parser.parse_args()

    print(int(args.num)**2)

เมื่อเรียกโปรแกรมนี้ผ่านทาง CLI จะได้ผลลัพท์ดังนี้

$ python sq.py
usage: sq.py [-h] num
sq.py: error: too few arguments
$
$ python sq.py 19
361

สังเกตกว่า num มีประเภทตัวแปรเป็น string ก่อนที่จะนำไปใช้คำนวณต้องแปลงค่าให้เป็น int ก่อน ซึ่งเราสามารถบอกประเภทให้ตัว parser แต่แรกเลยก็ได้ โดยแก้ไขบรรทัดที่ 4 เป็น

    parser.add_argument('num', type=int)

จากตัวอย่างที่แล้ว ตัวแปร num คือตัวแปรแบบ positional argument ซึ่งหมายความว่า ต้องป้อนตัวแปรเหล่านี้ให้ครบ

ลองดูโปรแกรม acal.py ที่รับตัวเลข 3 ตัว แล้วหาผลรวม

    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument('num', type=int, nargs=3)
    args = parser.parse_args()

    print(sum(args.num))

นอกจากกำหนดจำนวน argument ด้วยตัวเลขแล้ว ยังสามารถใช้ wildcard ?, +, * เพื่อบอกจำนวนก็ย่อมได้ เช่นปรับปรุงโปรแกรมข้างต้นให้รับ argument ได้ไม่จำกัดโดยเปลี่ยนบรรทัดที่ 4 เป็น

    parser.add_argument('num', type=int, nargs='+')

สำหรับตัวแปรอีกประเภทคือ optional argument เช่น -h โดยมีวิธีเขียนที่แตกต่างออกไปเล็กน้อย

คราวนี้มาปรับปรุง acal.py ให้รับ optional argument เพื่อให้สามารถเลือก max หรือ min แทนการ sum ได้

    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument('num', type=int, nargs='+')
    parser.add_argument('-M', '--max', dest='main', action='store_const',
                        const=max, default=sum)
    parser.add_argument('-m', '--min', dest='main', action='store_const',
                        const=min, default=sum)
    args = parser.parse_args()

    print(args.main(args.num))

และยังมี optional argument แบบที่สามารถรับ argument ต่อท้ายอีกได้ด้วย ลองดูตัวอย่างการเปิดไฟล์สำหรับเขียนโดยเพิ่มคำสั่งนี้เข้าไปหลังบรรทัดที่ 8

    parser.add_argument('-f', '--file', metavar='FILE', dest='file',
                        type=argparse.FileType('w'))

เนื่องจากการเปิดไฟล์ด้วย open เพื่อเขียนต้องบอก option 'w' ด้วย แต่เพราะ type รับตัวแปรเดียว ตรงนี้ให้ใช้ argparse.FileType('w') แทน (หรือจะใชประยุกต์ใช้ lambda f: open(f, 'w') ก็ย่อมได้)

จุดสังเกตอีกอย่างที่เพิ่มขึ้นมาคือ metavar ซึ่งเป็นการบ่งว่า optional argument นี้รับตัวแปรเพิ่มได้ แต่ถ้าเอาไปใส่ให้กับ positional argument จะกลายเป็นแค่การบอกชื่อย่อเท่านั้น

หลังจากเปิดไฟล์กันได้แล้ว ถึงตอนนี้ก็ต้องจัดการกับการแสดงผลที่บรรทัด 11 ใหม่หมด

    output = args.main(args.num)
    if args.file:
        args.file.write(str(output))
        args.file.close()
    else:
        print(output)

คราวนี้ก็ลองเขียน help เต็มรูปแบบให้กับ acal.py กันดูครับ

    import argparse

    parser = argparse.ArgumentParser(description='Advance Calculator.',
                                     epilog='WTFPL (C) 2012 neizod')
    parser.add_argument('numbers', metavar='N', type=int, nargs='+',
                        help='list of numbers for summation')
    parser.add_argument('-M', '--max', dest='main', action='store_const',
                        const=max, default=sum, help='find max insteed')
    parser.add_argument('-m', '--min', dest='main', action='store_const',
                        const=min, default=sum, help='find min instead')
    parser.add_argument('-f', '--file', metavar='FILE', dest='file',
                        type=argparse.FileType('w'),
                        help='store output to a file')
    args = parser.parse_args()

    output = args.main(args.numbers)
    if args.file:
        args.file.write(str(output))
        args.file.close()
    else:
        print(output)

Nattawut Phetmak

Jack of all Trades

blog comments powered by Disqus