Apa cara terbaik untuk mem-parsing argumen baris perintah? [Tutup]


Jawaban:


183

Jawaban ini menyarankan optparseyang sesuai untuk versi Python yang lebih lama. Untuk Python 2.7 dan di atasnya, argparsegantikan optparse. Lihat jawaban ini untuk informasi lebih lanjut.

Seperti yang ditunjukkan orang lain, Anda lebih baik menggunakan optparse daripada getopt. getopt cukup banyak pemetaan satu-ke-satu dari fungsi perpustakaan getopt (3) C standar, dan tidak sangat mudah digunakan.

optparse, sementara sedikit lebih bertele-tele, jauh lebih terstruktur dan lebih mudah untuk diperluas nanti.

Inilah garis khas untuk menambahkan opsi ke parser Anda:

parser.add_option('-q', '--query',
            action="store", dest="query",
            help="query string", default="spam")

Cukup banyak berbicara sendiri; pada waktu pemrosesan, ia akan menerima -q atau --query sebagai opsi, menyimpan argumen dalam atribut yang disebut kueri dan memiliki nilai default jika Anda tidak menentukannya. Ini juga mendokumentasikan diri sendiri ketika Anda mendeklarasikan argumen bantuan (yang akan digunakan saat dijalankan dengan -h / - help) di sana dengan opsi.

Biasanya Anda mengurai argumen Anda dengan:

options, args = parser.parse_args()

Ini akan, secara default, mem-parsing argumen standar yang diteruskan ke skrip (sys.argv [1:])

options.query kemudian akan diatur ke nilai yang Anda berikan ke skrip.

Anda membuat parser hanya dengan melakukan

parser = optparse.OptionParser()

Ini semua dasar-dasar yang Anda butuhkan. Berikut skrip Python lengkap yang menunjukkan ini:

import optparse

parser = optparse.OptionParser()

parser.add_option('-q', '--query',
    action="store", dest="query",
    help="query string", default="spam")

options, args = parser.parse_args()

print 'Query string:', options.query

5 baris python yang menunjukkan dasar-dasar.

Simpan di sample.py, dan jalankan sekali dengan

python sample.py

dan sekali dengan

python sample.py --query myquery

Di luar itu, Anda akan menemukan bahwa optparse sangat mudah diperluas. Di salah satu proyek saya, saya membuat kelas Command yang memungkinkan Anda untuk membuat sarang perintah di pohon perintah dengan mudah. Ini menggunakan optparse berat untuk rantai perintah bersama. Ini bukan sesuatu yang bisa saya jelaskan dengan mudah dalam beberapa baris, tetapi merasa bebas untuk menelusuri di repositori saya untuk kelas utama, serta kelas yang menggunakannya dan parser opsi


9
Jawaban ini sangat jelas dan mudah diikuti - untuk python 2.3 hingga 2.6. Untuk python 2.7+ itu bukan jawaban terbaik karena argparse sekarang menjadi bagian dari library standar dan optparse sudah tidak digunakan lagi.
matt wilkie

Dalam kasus saya, saya ingin membuat profil aplikasi saya untuk mendeteksi kelambatan. Ada alat lain yang disebut [tuna] ( github.com/nschloe/tuna ) yang memungkinkan saya untuk membuat profil seluruh aplikasi dengan hanya menambahkan agrs -mcProfile -o program.proftetapi agrparcer menangkap args ini, bagaimana cara meneruskan args ini ke python exe ???
Yogeshwar

231

argparseadalah cara untuk pergi. Berikut ini ringkasan singkat cara menggunakannya:

1) Inisialisasi

import argparse

# Instantiate the parser
parser = argparse.ArgumentParser(description='Optional app description')

2) Tambahkan Argumen

# Required positional argument
parser.add_argument('pos_arg', type=int,
                    help='A required integer positional argument')

# Optional positional argument
parser.add_argument('opt_pos_arg', type=int, nargs='?',
                    help='An optional integer positional argument')

# Optional argument
parser.add_argument('--opt_arg', type=int,
                    help='An optional integer argument')

# Switch
parser.add_argument('--switch', action='store_true',
                    help='A boolean switch')

3) Parse

args = parser.parse_args()

4) Akses

print("Argument values:")
print(args.pos_arg)
print(args.opt_pos_arg)
print(args.opt_arg)
print(args.switch)

5) Periksa Nilai

if args.pos_arg > 10:
    parser.error("pos_arg cannot be larger than 10")

Pemakaian

Penggunaan yang benar:

$ ./app 1 2 --opt_arg 3 --switch

Argument values:
1
2
3
True

Argumen yang salah:

$ ./app foo 2 --opt_arg 3 --switch
usage: convert [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
app: error: argument pos_arg: invalid int value: 'foo'

$ ./app 11 2 --opt_arg 3
Argument values:
11
2
3
False
usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]
convert: error: pos_arg cannot be larger than 10

Bantuan penuh:

$ ./app -h

usage: app [-h] [--opt_arg OPT_ARG] [--switch] pos_arg [opt_pos_arg]

Optional app description

positional arguments:
  pos_arg            A required integer positional argument
  opt_pos_arg        An optional integer positional argument

optional arguments:
  -h, --help         show this help message and exit
  --opt_arg OPT_ARG  An optional integer argument
  --switch           A boolean switch

10
Ini sangat ringkas dan bermanfaat dan di sini adalah dokumen resmi (untuk kenyamanan): docs.python.org/3/library/argparse.html
Christophe

1
Jika Anda menemukan argparse, gunakan verbose plac sebagai gantinya.
Nimitz14

76

Menggunakan docopt

Sejak 2012 ada modul yang sangat mudah, kuat dan sangat keren untuk parsing argumen yang disebut docopt . Berikut ini contoh yang diambil dari dokumentasinya:

"""Naval Fate.

Usage:
  naval_fate.py ship new <name>...
  naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
  naval_fate.py ship shoot <x> <y>
  naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
  naval_fate.py (-h | --help)
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --speed=<kn>  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.

"""
from docopt import docopt


if __name__ == '__main__':
    arguments = docopt(__doc__, version='Naval Fate 2.0')
    print(arguments)

Jadi ini dia: 2 baris kode ditambah tali doc Anda yang adalah penting dan Anda mendapatkan argumen Anda diurai dan tersedia di objek argumen Anda.

Menggunakan python-fire

Sejak 2017 ada modul keren lain bernama python-fire . Itu dapat menghasilkan antarmuka CLI untuk kode Anda dengan Anda melakukan parsing argumen nol . Berikut adalah contoh sederhana dari dokumentasi (program kecil ini memperlihatkan fungsi doubleke baris perintah):

import fire

class Calculator(object):

  def double(self, number):
    return 2 * number

if __name__ == '__main__':
  fire.Fire(Calculator)

Dari baris perintah, Anda dapat menjalankan:

> calculator.py double 10
20
> calculator.py double --number=15
30

4
bagaimana cara docopt "tidak perlu instalasi"? ini adalah modul python sehingga harus diinstal. 'ImportError: Tidak ada modul bernama docopt'
tajam

1
@keen itu tidak termasuk dengan python pasti tetapi Anda tidak perlu menginstalnya: "Anda bisa saja memasukkan file docopt.py ke proyek Anda - itu mandiri" - github.com/docopt/docopt
ndemou

9
kami hanya memiliki definisi instalasi yang berbeda - dan saya ingin menunjukkannya untuk pembaca di masa mendatang.
tajam

1
@keen Saya telah menambahkan catatan "tidak ada instalasi" untuk orang-orang yang membagikan definisi Anda :-)
ndemou

39

Cara pinggul baru adalah argparseuntuk alasan ini . argparse> optparse> getopt

pembaruan: Pada py2.7 argparse adalah bagian dari perpustakaan standar dan optparse sudah tidak digunakan lagi.


Tautan utama Anda adalah 404 jadi saya menggantinya dengan tautan ke pertanyaan SO yang membahas topik yang sama.
Joe Holloway

28

Saya lebih suka Klik . Ini mengabstraksi opsi pengelolaan dan memungkinkan "(...) membuat antarmuka baris perintah yang indah dengan cara yang mudah dikompilasi dengan kode sesedikit yang diperlukan".

Berikut contoh penggunaannya:

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
              help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)

if __name__ == '__main__':
    hello()

Itu juga secara otomatis menghasilkan halaman bantuan yang diformat dengan baik:

$ python hello.py --help
Usage: hello.py [OPTIONS]

  Simple program that greets NAME for a total of COUNT times.

Options:
  --count INTEGER  Number of greetings.
  --name TEXT      The person to greet.
  --help           Show this message and exit.

14

Cukup banyak orang menggunakan getopt

Berikut ini contoh kode untuk dokumen:

import getopt, sys

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output="])
    except getopt.GetoptError:
        # print help information and exit:
        usage()
        sys.exit(2)
    output = None
    verbose = False
    for o, a in opts:
        if o == "-v":
            verbose = True
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-o", "--output"):
            output = a

Jadi singkatnya, ini dia cara kerjanya.

Anda punya dua jenis opsi. Mereka yang menerima argumen, dan mereka yang seperti switch.

sys.argvAnda cukup banyak char** argvdi C. Seperti di C Anda melewatkan elemen pertama yang merupakan nama program Anda dan hanya menguraikan argumen:sys.argv[1:]

Getopt.getopt akan menguraikannya sesuai dengan aturan yang Anda berikan dalam argumen.

"ho:v"di sini menggambarkan argumen singkat: -ONELETTER. Cara :yang -omenerima satu argumen.

Akhirnya ["help", "output="]menjelaskan argumen panjang ( --MORETHANONELETTER). The =setelah keluaran sekali lagi berarti output yang menerima satu argumen.

Hasilnya adalah daftar pasangan (opsi, argumen)

Jika suatu opsi tidak menerima argumen apa pun (seperti di --helpsini) argbagian tersebut adalah string kosong. Anda kemudian biasanya ingin mengulang daftar ini dan menguji nama opsi seperti pada contoh.

Saya harap ini membantu Anda.


6
Dengan penghentian getoptversi Python yang lebih baru, jawaban ini sudah kedaluwarsa.
antar

1
@ shuttle87 Pada python3.7.2, getoptmasih belum usang ... Tetapi dokumentasinya menyatakan bahwa ini terutama disediakan untuk pengguna yang akrab dengan getopt()fungsi C , dan mengakui bahwa bagi pengguna lain argparsemungkin merupakan solusi yang lebih baik, memungkinkan untuk "menulis lebih sedikit kode dan mendapatkan bantuan yang lebih baik dan pesan kesalahan ".
Skippy le Grand Gourou


6

Jika Anda perlu, ini dapat membantu jika Anda perlu mengambil argumen unicode di Win32 (2K, XP dll):


from ctypes import *

def wmain(argc, argv):
    print argc
    for i in argv:
        print i
    return 0

def startup():
    size = c_int()
    ptr = windll.shell32.CommandLineToArgvW(windll.kernel32.GetCommandLineW(), byref(size))
    ref = c_wchar_p * size.value
    raw = ref.from_address(ptr)
    args = [arg for arg in raw]
    windll.kernel32.LocalFree(ptr)
    exit(wmain(len(args), args))
startup()

Terima kasih. Script ini membantu saya menemukan beberapa kutipan yang sangat rumit yang perlu saya lakukan ketika mengirimkan perintah startup ke GVim.
telotortium

6

Argumen baris perintah ringan

Meskipun argparsehebat dan merupakan jawaban yang tepat untuk sakelar baris perintah yang sepenuhnya didokumentasikan dan fitur-fitur canggih, Anda dapat menggunakan default argumen fungsi untuk menangani argumen posisi langsung dengan sangat sederhana.

import sys

def get_args(name='default', first='a', second=2):
    return first, int(second)

first, second = get_args(*sys.argv)
print first, second

Argumen 'nama' menangkap nama skrip dan tidak digunakan. Hasil tes terlihat seperti ini:

> ./test.py
a 2
> ./test.py A
A 2
> ./test.py A 20
A 20

Untuk skrip sederhana di mana saya hanya ingin beberapa nilai default, saya menemukan ini cukup memadai. Anda mungkin juga ingin memasukkan beberapa jenis paksaan dalam nilai-nilai kembali atau nilai-nilai baris perintah semua akan menjadi string.


2
kutipan tidak cocok dalam pernyataan def.
historystamp


3

Saya pikir cara terbaik untuk proyek yang lebih besar adalah optparse, tetapi jika Anda mencari cara yang mudah, mungkin http://werkzeug.pocoo.org/documentation/script adalah sesuatu untuk Anda.

from werkzeug import script

# actions go here
def action_foo(name=""):
    """action foo does foo"""
    pass

def action_bar(id=0, title="default title"):
    """action bar does bar"""
    pass

if __name__ == '__main__':
    script.run()

Jadi pada dasarnya setiap fungsi action_ * terpapar ke baris perintah dan pesan bantuan yang bagus dihasilkan secara gratis.

python foo.py 
usage: foo.py <action> [<options>]
       foo.py --help

actions:
  bar:
    action bar does bar

    --id                          integer   0
    --title                       string    default title

  foo:
    action foo does foo

    --name                        string

Saya telah mengembangkan paket kecil memanfaatkan penciptaan argumen otomatis: declarative_parser. Tentu saja, jika seseorang bekerja dengan werkzeug, mungkin lebih baik menyimpannya werkzung.script. Bagaimanapun, saya penggemar berat pendekatan semacam itu.
krassowski

3

Kode argparse bisa lebih lama dari kode implementasi aktual!

Itulah masalah yang saya temukan dengan opsi parsing argumen paling populer adalah bahwa jika parameter Anda hanya sederhana, kode untuk mendokumentasikannya menjadi tidak proporsional besar untuk manfaat yang mereka berikan.

Pendatang baru relatif ke adegan parsing argumen (saya pikir) adalah plac .

Itu membuat beberapa trade-off yang diakui dengan argparse, tetapi menggunakan dokumentasi sebaris dan hanya membungkus main()fungsi fungsi tipe:

def main(excel_file_path: "Path to input training file.",
     excel_sheet_name:"Name of the excel sheet containing training data including columns 'Label' and 'Description'.",
     existing_model_path: "Path to an existing model to refine."=None,
     batch_size_start: "The smallest size of any minibatch."=10.,
     batch_size_stop:  "The largest size of any minibatch."=250.,
     batch_size_step:  "The step for increase in minibatch size."=1.002,
     batch_test_steps: "Flag.  If True, show minibatch steps."=False):
"Train a Spacy (http://spacy.io/) text classification model with gold document and label data until the model nears convergence (LOSS < 0.5)."

    pass # Implementation code goes here!

if __name__ == '__main__':
    import plac; plac.call(main)

Titik informasi: penggunaan plas yang paling rapi (seperti ditunjukkan dalam contoh) hanya untuk Python 3.x saja, karena menggunakan anotasi fungsi 3.x.
Nyonya

1

Consoleargs layak disebutkan di sini. Sangat mudah digunakan. Saksikan berikut ini:

from consoleargs import command

@command
def main(url, name=None):
  """
  :param url: Remote URL 
  :param name: File name
  """
  print """Downloading url '%r' into file '%r'""" % (url, name)

if __name__ == '__main__':
  main()

Sekarang di konsol:

% python demo.py --help
Usage: demo.py URL [OPTIONS]

URL:    Remote URL 

Options:
    --name -n   File name

% python demo.py http://www.google.com/
Downloading url ''http://www.google.com/'' into file 'None'

% python demo.py http://www.google.com/ --name=index.html
Downloading url ''http://www.google.com/'' into file ''index.html''

Saya telah menggunakan pendekatan yang serupa di declarative-parser , lihat pengurangan argumen (mengetik, docstring, kwargs) dalam dokumen. Perbedaan utama: python3, type hints, pip-installable.
krassowski

1
Komit terakhir pada 2012
Boris

0

Inilah metode, bukan perpustakaan, yang sepertinya bekerja untuk saya.

Tujuan di sini harus singkat, setiap argumen diurai oleh satu baris, argumen berbaris agar mudah dibaca, kode sederhana dan tidak bergantung pada modul khusus apa pun (hanya os + sys), memperingatkan tentang argumen yang hilang atau tidak dikenal dengan anggun. , gunakan loop sederhana untuk / range (), dan bekerja melintasi python 2.x dan 3.x

Ditampilkan dua flag toggle (-d, -v), dan dua nilai dikontrol oleh argumen (-i xxx dan -o xxx).

import os,sys

def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  = "infile"
    outfile = "outfile"

    # Parse command line
    skip = 0
    for i in range(1, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] == "-d": debug ^= 1
            elif sys.argv[i][:2] == "-v": verbose ^= 1
            elif sys.argv[i][:2] == "-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] == "-h": HelpAndExit()
            elif sys.argv[i][:1] == "-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("%d,%d,%s,%s" % (debug,verbose,infile,outfile))

Tujuan dari NextArg () adalah untuk mengembalikan argumen berikutnya sambil memeriksa data yang hilang, dan 'lompati' melompati loop ketika NextArg () digunakan, menjaga flag yang diuraikan menjadi satu liner.


0

Saya memperluas pendekatan Erco untuk memungkinkan argumen posisi yang diperlukan dan untuk argumen opsional. Ini harus mendahului argumen -d, -v dll.

Argumen posisional dan opsional dapat diambil masing-masing dengan PosArg (i) dan OptArg (i, default). Ketika argumen opsional ditemukan, posisi awal pencarian opsi (misalnya -i) digerakkan 1 di depan untuk menghindari menyebabkan fatal 'tidak terduga'.

import os,sys


def HelpAndExit():
    print("<<your help output goes here>>")
    sys.exit(1)

def Fatal(msg):
    sys.stderr.write("%s: %s\n" % (os.path.basename(sys.argv[0]), msg))
    sys.exit(1)

def NextArg(i):
    '''Return the next command line argument (if there is one)'''
    if ((i+1) >= len(sys.argv)):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return(1, sys.argv[i+1])

def PosArg(i):
    '''Return positional argument'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    return sys.argv[i]

def OptArg(i, default):
    '''Return optional argument (if there is one)'''
    if i >= len(sys.argv):
        Fatal("'%s' expected an argument" % sys.argv[i])
    if sys.argv[i][:1] != '-':
        return True, sys.argv[i]
    else:
        return False, default


### MAIN
if __name__=='__main__':

    verbose = 0
    debug   = 0
    infile  = "infile"
    outfile = "outfile"
    options_start = 3

    # --- Parse two positional parameters ---
    n1 = int(PosArg(1))
    n2 = int(PosArg(2))

    # --- Parse an optional parameters ---
    present, a3 = OptArg(3,50)
    n3 = int(a3)
    options_start += int(present)

    # --- Parse rest of command line ---
    skip = 0
    for i in range(options_start, len(sys.argv)):
        if not skip:
            if   sys.argv[i][:2] == "-d": debug ^= 1
            elif sys.argv[i][:2] == "-v": verbose ^= 1
            elif sys.argv[i][:2] == "-i": (skip,infile)  = NextArg(i)
            elif sys.argv[i][:2] == "-o": (skip,outfile) = NextArg(i)
            elif sys.argv[i][:2] == "-h": HelpAndExit()
            elif sys.argv[i][:1] == "-":  Fatal("'%s' unknown argument" % sys.argv[i])
            else:                         Fatal("'%s' unexpected" % sys.argv[i])
        else: skip = 0

    print("Number 1 = %d" % n1)
    print("Number 2 = %d" % n2)
    print("Number 3 = %d" % n3)
    print("Debug    = %d" % debug)
    print("verbose  = %d" % verbose)
    print("infile   = %s" % infile)
    print("outfile  = %s" % outfile) 
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.