Lassen Sie in Python mit argparse nur positive Ganzzahlen zu

164

Der Titel fasst ziemlich genau zusammen, was ich gerne gemacht hätte.

Folgendes habe ich, und obwohl das Programm keine nicht positive Ganzzahl in die Luft jagt, möchte ich, dass der Benutzer darüber informiert wird, dass eine nicht positive Ganzzahl im Grunde genommen Unsinn ist.

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

Und die Ausgabe:

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

Ausgabe mit einem Negativ:

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

Jetzt könnte ich natürlich nur ein Wenn hinzufügen, um festzustellen if args.games, ob es negativ ist, aber ich war neugierig, ob es eine Möglichkeit gibt, es auf der argparseEbene abzufangen, um den automatischen Verwendungsdruck zu nutzen.

Im Idealfall würde es etwas Ähnliches drucken:

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'

Wie so:

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'

Im Moment mache ich das und ich denke, ich bin glücklich:

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

Antworten:

244

Dies sollte möglich sein type. Sie müssen noch eine tatsächliche Methode definieren, die dies für Sie entscheidet:

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)

Dies ist im Grunde nur ein angepasstes Beispiel aus der perfect_squareFunktion in den Dokumenten auf argparse.

Yuushi
quelle
1
Kann Ihre Funktion mehrere Werte haben? Wie funktioniert das?
Tom
2
Wenn die Konvertierung in intfehlschlägt, gibt es dann immer noch eine lesbare Ausgabe? Oder sollten Sie try raisedie Konvertierung dafür manuell durchführen?
NOhs
4
@ MrZ Es wird so etwas geben error: argument foo: invalid check_positive value: 'foo=<whatever>'. Sie können einfach ein try:... hinzufügen except ValueError:, das eine Ausnahme mit einer besseren Fehlermeldung erneut auslöst.
Yuushi
59

type wäre die empfohlene Option, um Bedingungen / Überprüfungen zu behandeln, wie in Yuushis Antwort.

In Ihrem speziellen Fall können Sie den choicesParameter auch verwenden , wenn Ihre Obergrenze ebenfalls bekannt ist:

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

Hinweis: Verwenden Sie rangeanstelle von xrangefür Python 3.x.

Aneroid
quelle
3
Ich stelle mir vor, dass dies ziemlich ineffizient wäre, da Sie einen Bereich generieren und ihn dann durchlaufen würden, um Ihre Eingabe zu validieren. Ein schneller ifist viel schneller.
TravisThomas
2
@ trav1th In der Tat mag es sein, aber es ist eine Beispielverwendung aus den Dokumenten. Außerdem habe ich in meiner Antwort gesagt, dass Yuushis Antwort die richtige ist. Gut, Optionen zu geben. Und im Fall von Argparse geschieht dies einmal pro Ausführung, verwendet einen Generator ( xrange) und benötigt keinen zusätzlichen Code. Dieser Kompromiss ist verfügbar. Es liegt an jedem, zu entscheiden, welchen Weg er gehen soll.
Aneroid
16
Um klarer zu werden, wie jgritty auf die Antwort des Ben-Autors eingeht, führt die Auswahl = xrange (0,1000) dazu, dass die gesamte Liste von Ganzzahlen von 1 bis einschließlich 999 jedes Mal, wenn Sie --help verwenden oder wenn ein ungültiges Argument vorliegt, auf Ihre Konsole geschrieben wird unter der Voraussetzung. In den meisten Fällen keine gute Wahl.
Biomiker
9

Der schnelle und schmutzige Weg, wenn Sie ein vorhersehbares Maximum und ein vorhersehbares Minimum für Ihr Argument haben, ist die Verwendung choicesmit einem Bereich

parser.add_argument('foo', type=int, choices=xrange(0, 1000))
ben Autor
quelle
24
Der Nachteil dort ist die schreckliche Ausgabe.
jgritty
6
Betonung auf schmutzig , denke ich.
Ben Autor
4
Um den Punkt von jgritty klarer zu machen, führt die Auswahl = xrange (0,1000) dazu, dass die gesamte Liste von Ganzzahlen von 1 bis einschließlich 999 bei jeder Verwendung von --help oder wenn ein ungültiges Argument angegeben wird, in Ihre Konsole geschrieben wird. In den meisten Fällen keine gute Wahl.
Biomiker
8

Eine einfachere Alternative, insbesondere bei Unterklassen argparse.ArgumentParser, besteht darin, die Validierung innerhalb der parse_argsMethode zu initiieren .

Innerhalb einer solchen Unterklasse:

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

Diese Technik ist möglicherweise nicht so cool wie eine benutzerdefinierte aufrufbare, aber sie erledigt den Job.


Über ArgumentParser.error(message):

Diese Methode druckt eine Verwendungsnachricht einschließlich der Nachricht zum Standardfehler und beendet das Programm mit dem Statuscode 2.


Kredit: Antwort von Jonatan

Scharfsinn
quelle
Oder zumindest durch print "-g/--games: must be positive."; sys.exit(1)nur ersetzen parser.error("-g/--games: must be positive."). (Verwendung wie in Jonatans Antwort .)
Aneroid
3

Falls jemand (wie ich) bei einer Google-Suche auf diese Frage stößt, finden Sie hier ein Beispiel für die Verwendung eines modularen Ansatzes, um das allgemeinere Problem, Argparse-Ganzzahlen nur in einem bestimmten Bereich zuzulassen, sauber zu lösen :

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

Dies ermöglicht Ihnen Folgendes:

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

Die Variable fooerlaubt jetzt nur positive ganze Zahlen , wie das OP gefragt.

Beachten Sie, dass zusätzlich zu den oben genannten Formularen nur ein Maximum möglich ist mit IntRange:

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