Python argparse: Buat setidaknya satu argumen yang diperlukan

96

Saya telah menggunakan argparseuntuk program Python yang bisa -process, -uploadatau keduanya:

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()

Program tidak ada artinya tanpa setidaknya satu parameter. Bagaimana cara saya mengkonfigurasi argparseuntuk memaksa setidaknya satu parameter dipilih?

MEMPERBARUI:

Mengikuti komentar: Apa cara Pythonic untuk membuat parameter program dengan setidaknya satu opsi?

Adam Matan
sumber
9
-xsecara universal adalah sebuah bendera dan opsional. Potong -jika diperlukan.
1
Tidak bisa Anda membuat processperilaku default (tanpa perlu menentukan pilihan ada) dan memungkinkan pengguna untuk perubahan itu ke dalam uploadjika yang opsi disetel? Biasanya, opsi harus opsional, karena itulah namanya. Opsi yang diperlukan harus dihindari (ini juga ada di argparse dokumen).
Tim Pietzcker
@AdamMatan Sudah hampir tiga tahun sejak Anda mengajukan pertanyaan Anda tetapi saya menyukai tantangan yang tersembunyi di dalamnya dan menggunakan keuntungan dari solusi baru yang tersedia untuk tugas-tugas semacam ini.
Jan Vlcinsky

Jawaban:

112
if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')
phihag
sumber
1
Itu mungkin satu-satunya cara, jika argparsetidak memiliki opsi bawaan untuk ini.
Adam Matan
32
args = vars(parser.parse_args())
if not any(args.values()):
    parser.error('No arguments provided.')
brentlance
sumber
3
1 untuk solusi umum. Juga seperti penggunaan vars(), yang juga berguna untuk meneruskan opsi yang diberi nama dengan cermat ke konstruktor dengan **.
Lenna
Itulah tepatnya yang saya lakukan dengannya. Terima kasih!
brentlance
1
Sial, aku suka itu vars. Saya baru saja melakukannya .__dict__dan merasa bodoh sebelumnya.
Theo Belaire
1
jawaban yang bagus. Baik "vars" dan "any" adalah hal baru bagi saya :-)
Vivek Jha
21

Jika bukan bagian 'atau keduanya' (awalnya saya melewatkan ini), Anda dapat menggunakan sesuatu seperti ini:

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")

Meskipun demikian, mungkin lebih baik menggunakan sub-perintah sebagai gantinya.

Jacek Konieczny
sumber
4
Saya pikir dia ingin mengizinkan --processOR --upload, bukan XOR. Ini mencegah kedua opsi disetel pada waktu yang sama.
phihag
+1 karena Anda menyebutkan subperintah. Namun - seperti yang ditunjukkan seseorang di komentar -xdan --xxxbiasanya merupakan parameter opsional.
mac
20

Saya tahu ini sudah tua seperti kotoran, tetapi cara untuk meminta satu opsi tetapi melarang lebih dari satu (XOR) adalah seperti ini:

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

Keluaran:

>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  
Knut
sumber
4
Sayangnya, OP tidak menginginkan XOR. Ini salah satu atau keduanya, tetapi tidak tidak sama sekali sehingga kasus pengujian terakhir Anda tidak memenuhi persyaratan mereka.
kdopen
3
@kdopen: responden memang mengklarifikasi bahwa ini adalah variasi dari pertanyaan asli, yang menurut saya berguna: "cara untuk meminta satu opsi tetapi melarang lebih dari satu" Mungkin etiket Stack Exchange akan meminta pertanyaan baru sebagai gantinya . Tetapi memiliki jawaban ini hadir di sini membantu saya ...
erik.weathers
2
Posting ini tidak menjawab pertanyaan awal
Marc
2
Bagaimana ini menjawab pertanyaan "setidaknya satu"?
xaxxon
2
Sayangnya, OP tidak menginginkan XOR.
duckman_1991
8

Tinjauan Persyaratan

  • gunakan argparse(saya akan mengabaikan yang ini)
  • memungkinkan satu atau dua tindakan dipanggil (setidaknya satu diperlukan).
  • coba dengan Pythonic (saya lebih suka menyebutnya "POSIX" -like)

Ada juga beberapa persyaratan implisit saat menggunakan command line:

  • menjelaskan penggunaan kepada pengguna dengan cara yang mudah dimengerti
  • opsi harus opsional
  • Izinkan menentukan bendera dan opsi
  • memungkinkan penggabungan dengan parameter lain (seperti nama file atau nama).

Solusi sampel menggunakan docopt(file 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

Coba jalankan:

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

Tunjukkan bantuan:

$ 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>

Dan gunakan itu:

$ 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}

Alternatif pendek short.py

Mungkin ada varian yang lebih pendek:

"""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

Penggunaannya terlihat seperti ini:

$ 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}

Perhatikan, sebagai ganti nilai boolean untuk kunci "proses" dan "unggah" yang ada adalah penghitung.

Ternyata, kami tidak dapat mencegah duplikasi kata-kata ini:

$ 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}

Kesimpulan

Mendesain antarmuka baris perintah yang baik terkadang menjadi tantangan.

Ada beberapa aspek program berbasis baris perintah:

  • desain baris perintah yang bagus
  • memilih / menggunakan parser yang tepat

argparse menawarkan banyak hal, tetapi membatasi kemungkinan skenario dan bisa menjadi sangat kompleks.

Dengan docoptsegala sesuatunya menjadi lebih pendek sambil mempertahankan keterbacaan dan menawarkan tingkat fleksibilitas yang tinggi. Jika Anda mengelola mendapatkan argumen parsing dari kamus dan melakukan beberapa konversi (ke integer, membuka file ..) secara manual (atau dengan pustaka lain yang disebut schema), Anda mungkin merasa docoptcocok untuk penguraian baris perintah.

Jan Vlcinsky
sumber
Belum pernah mendengar tentang docopt, saran bagus!
Ton van den Heuvel
@TonenEuvel Bagus. Saya hanya ingin mengonfirmasi, saya masih menggunakannya sebagai solusi pilihan saya untuk antarmuka baris perintah.
Jan Vlcinsky
Jawaban terbaik evar, terima kasih atas contoh terperinci.
jnovack
5

Jika Anda memerlukan program python untuk dijalankan dengan setidaknya satu parameter, tambahkan argumen yang tidak memiliki awalan opsi (- atau - secara default) dan set nargs=+(Minimal satu argumen diperlukan). Masalah dengan metode ini yang saya temukan adalah jika Anda tidak menentukan argumennya, argparse akan menghasilkan kesalahan "argumen terlalu sedikit" dan tidak mencetak menu bantuan. Jika Anda tidak membutuhkan fungsionalitas itu, berikut ini cara melakukannya dalam kode:

import argparse

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

Saya pikir ketika Anda menambahkan argumen dengan prefiks opsi, narg mengatur seluruh parser argumen dan bukan hanya opsi. (Yang saya maksud adalah, jika Anda memiliki sebuah --optionflag dengan nargs="+", maka --optionflag mengharapkan setidaknya satu argumen. Jika Anda memiliki optiondengan nargs="+", itu mengharapkan setidaknya satu argumen secara keseluruhan.)

NuclearPeon
sumber
Anda bisa menambahkan choices=['process','upload']argumen itu.
hpaulj
5

Untuk http://bugs.python.org/issue11588 saya mencari cara untuk menggeneralisasi mutually_exclusive_groupkonsep untuk menangani kasus seperti ini.

Dengan perkembangan ini argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py Saya bisa menulis:

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)

yang menghasilkan berikut ini 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

Ini menerima input seperti '-u', '-up', '--proc --up' dll.

Itu akhirnya menjalankan tes yang mirip dengan https://stackoverflow.com/a/6723066/901925 , meskipun pesan kesalahan harus lebih jelas:

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

Aku penasaran:

  • apakah parameternya kind='any', required=Truecukup jelas (terima salah satu grup; setidaknya satu diperlukan)?

  • apakah penggunaannya (-p | -u)jelas? Mutually_exclusive_group yang diperlukan menghasilkan hal yang sama. Apakah ada notasi alternatif?

  • Apakah menggunakan grup seperti ini lebih intuitif daripada phihag'stes sederhana?

hpaulj
sumber
Saya tidak dapat menemukan apa pun yang disebutkan add_usage_groupdi halaman ini: docs.python.org/2/library/argparse.html ; maukah Anda memberikan tautan ke dokumentasi untuk itu?
P. Myer Nore
@ P.MyerNore, saya menyediakan tautan - di awal jawaban ini. Ini belum diproduksi.
hpaulj
5

Cara terbaik untuk melakukannya adalah dengan menggunakan modul inbuilt python 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()

Jika Anda ingin hanya satu argumen yang dipilih oleh baris perintah, gunakan saja required = True sebagai argumen untuk grup

group = parser.add_mutually_exclusive_group(required=True)
faizan baig
sumber
2
Bagaimana ini membuat Anda "setidaknya satu" - bukankah itu membuat Anda "tepat satu"?
xaxxon
3
Sayangnya, OP tidak menginginkan XOR. OP mencari OR
duckman_1991
Ini tidak menjawab pertanyaan OP, tapi menjawab pertanyaan saya jadi terima kasih ¯_ (ツ) _ / ¯
rosstex
2

Mungkin menggunakan sub-parser?

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)

Sekarang --helpmenunjukkan:

$ 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

Anda juga dapat menambahkan opsi tambahan ke sub-parser ini. Selain itu, dest='subparser_name'Anda juga dapat mengikat fungsi untuk dipanggil secara langsung pada sub-perintah yang diberikan (lihat dokumen).

jhutar
sumber
2

Ini mencapai tujuan dan ini juga akan direfleksikan dalam --helpoutput autogenerated argparse , yang merupakan apa yang diinginkan oleh sebagian besar pemrogram waras (juga bekerja dengan argumen opsional):

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

Dokumen resmi tentang ini: https://docs.python.org/3/library/argparse.html#choices

Bob
sumber
1

Gunakan append_const ke daftar tindakan dan kemudian periksa apakah daftar tersebut diisi:

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

Anda bahkan dapat menentukan metode langsung di dalam konstanta.

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()
storm_m2138
sumber