Dalam Python, menggunakan argparse, hanya mengizinkan bilangan bulat positif

164

Judul ini cukup meringkas apa yang saya inginkan terjadi.

Inilah yang saya miliki, dan sementara program tidak meledak pada integer nonpositif, saya ingin pengguna diberi tahu bahwa integer nonpositif pada dasarnya adalah omong kosong.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-g", "--games", type=int, default=162,
                    help="The number of games to simulate")
args = parser.parse_args()

Dan hasilnya:

python simulate_many.py -g 20
Setting up...
Playing games...
....................

Output dengan negatif:

python simulate_many.py -g -2
Setting up...
Playing games...

Sekarang, jelas saya bisa saja menambahkan jika untuk menentukan if args.gamesadalah negatif, tetapi saya ingin tahu apakah ada cara untuk menjebaknya di argparsetingkat, sehingga dapat mengambil keuntungan dari pencetakan penggunaan otomatis.

Idealnya, itu akan mencetak sesuatu yang mirip dengan ini:

python simulate_many.py -g a
usage: simulate_many.py [-h] [-g GAMES] [-d] [-l LEAGUE]
simulate_many.py: error: argument -g/--games: invalid int value: 'a'

Seperti itu:

python simulate_many.py -g -2
usage: simulate_many.py [-h] [-g GAMES] [-d] [-l LEAGUE]
simulate_many.py: error: argument -g/--games: invalid positive int value: '-2'

Untuk saat ini saya melakukan ini, dan saya rasa saya senang:

if args.games <= 0:
    parser.print_help()
    print "-g/--games: must be positive."
    sys.exit(1)
jgritty
sumber

Jawaban:

244

Ini harus dimungkinkan memanfaatkan type. Anda masih perlu mendefinisikan metode aktual yang memutuskan ini untuk Anda:

def check_positive(value):
    ivalue = int(value)
    if ivalue <= 0:
        raise argparse.ArgumentTypeError("%s is an invalid positive int value" % value)
    return ivalue

parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=check_positive)

Ini pada dasarnya hanyalah sebuah contoh diadaptasi dari perfect_squarefungsi dalam docs pada argparse.

Yuushi
sumber
1
Bisakah fungsi Anda memiliki beberapa nilai? Bagaimana cara kerjanya?
Tom
2
Jika konversi menjadi intgagal, apakah masih akan ada keluaran yang dapat dibaca? Atau haruskah Anda try raisekonversi secara manual untuk itu?
Nih
4
@ MR Ini akan memberikan sesuatu seperti error: argument foo: invalid check_positive value: 'foo=<whatever>'. Anda bisa menambahkan try:... except ValueError:di sekitarnya yang memunculkan kembali pengecualian dengan pesan kesalahan yang lebih baik.
Yuushi
59

type akan menjadi opsi yang direkomendasikan untuk menangani kondisi / pemeriksaan, seperti dalam jawaban Yuushi.

Dalam kasus spesifik Anda, Anda juga dapat menggunakan choicesparameter jika batas atas Anda juga diketahui:

parser.add_argument('foo', type=int, choices=xrange(5, 10))

Catatan: Gunakan rangesebagai ganti xrangeuntuk python 3.x

aneroid
sumber
3
Saya membayangkan ini akan sangat tidak efisien, karena Anda akan menghasilkan rentang dan kemudian bersepeda melaluinya memvalidasi input Anda. Cepat ifjauh lebih cepat.
TravisThomas
2
@ trav1th Memang mungkin, tapi ini contoh penggunaan dari dokumen. Juga, saya telah mengatakan dalam jawaban saya bahwa jawaban Yuushi adalah yang harus dicari. Bagus untuk memberi opsi. Dan dalam kasus argparse, ini terjadi sekali per eksekusi, menggunakan generator ( xrange) dan tidak memerlukan kode tambahan. Pertukaran itu tersedia. Terserah masing-masing untuk memutuskan ke mana harus pergi.
aneroid
16
Untuk lebih jelas tentang poin jgritty pada jawaban penulis, pilihan = xrange (0,1000) akan menghasilkan seluruh daftar bilangan bulat dari 1 hingga 999 inklusif yang ditulis ke konsol Anda setiap kali Anda menggunakan --membantu atau jika argumen yang tidak valid adalah disediakan. Bukan pilihan yang baik di sebagian besar keadaan.
biomiker
9

Cara cepat dan kotor, jika Anda memiliki maks yang dapat diprediksi dan juga min untuk arg Anda, digunakan choicesdengan kisaran

parser.add_argument('foo', type=int, choices=xrange(0, 1000))
ben penulis
sumber
24
Kelemahannya adalah output yang mengerikan.
jgritty
6
penekanan pada kotor , kurasa.
penulis ben
4
Untuk lebih jelas tentang poin jgritty, pilihan = xrange (0,1000) akan menghasilkan seluruh daftar bilangan bulat dari 1 hingga 999 yang ditulis ke konsol Anda setiap kali Anda menggunakan --help atau jika argumen yang tidak valid disediakan. Bukan pilihan yang baik di sebagian besar keadaan.
biomiker
8

Alternatif yang lebih sederhana, terutama jika subclassing argparse.ArgumentParser, adalah memulai validasi dari dalam parse_argsmetode.

Di dalam subclass seperti itu:

def parse_args(self, args=None, namespace=None):
    """Parse and validate args."""
    namespace = super().parse_args(args, namespace)
    if namespace.games <= 0:
         raise self.error('The number of games must be a positive integer.')
    return namespace

Teknik ini mungkin tidak sekeren pemanggil kustom, tetapi melakukan pekerjaan.


Tentang ArgumentParser.error(message):

Metode ini mencetak pesan penggunaan termasuk pesan ke kesalahan standar dan mengakhiri program dengan kode status 2.


Kredit: jawaban oleh jonatan

Acumenus
sumber
Atau setidaknya, ganti print "-g/--games: must be positive."; sys.exit(1)dengan adil parser.error("-g/--games: must be positive."). (Penggunaan seperti dalam jawaban jonatan .)
aneroid
3

Jika seseorang (seperti saya) menemukan pertanyaan ini dalam pencarian Google, berikut adalah contoh bagaimana menggunakan pendekatan modular untuk dengan rapi menyelesaikan masalah yang lebih umum yaitu mengizinkan integer argparse hanya dalam rentang yang ditentukan :

# Custom argparse type representing a bounded int
class IntRange:

    def __init__(self, imin=None, imax=None):
        self.imin = imin
        self.imax = imax

    def __call__(self, arg):
        try:
            value = int(arg)
        except ValueError:
            raise self.exception()
        if (self.imin is not None and value < self.imin) or (self.imax is not None and value > self.imax):
            raise self.exception()
        return value

    def exception(self):
        if self.imin is not None and self.imax is not None:
            return argparse.ArgumentTypeError(f"Must be an integer in the range [{self.imin}, {self.imax}]")
        elif self.imin is not None:
            return argparse.ArgumentTypeError(f"Must be an integer >= {self.imin}")
        elif self.imax is not None:
            return argparse.ArgumentTypeError(f"Must be an integer <= {self.imax}")
        else:
            return argparse.ArgumentTypeError("Must be an integer")

Ini memungkinkan Anda melakukan sesuatu seperti:

parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=IntRange(1))     # Must have foo >= 1
parser.add_argument('bar', type=IntRange(1, 7))  # Must have 1 <= bar <= 7

Variabel foosekarang hanya memungkinkan bilangan bulat positif , seperti yang diminta OP.

Perhatikan bahwa selain bentuk-bentuk di atas, maksimum juga dimungkinkan dengan IntRange:

parser.add_argument('other', type=IntRange(imax=10))  # Must have other <= 10
pallgeuer
sumber