В Python, используя argparse, разрешите только положительные целые числа

164

Название в значительной степени суммирует то, что я хотел бы, чтобы произошло.

Вот что у меня есть, и хотя программа не взорвалась от неположительного целого числа, я хочу, чтобы пользователь был проинформирован о том, что неположительное целое число в основном бессмысленно.

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

И вывод:

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

Выход с отрицательным:

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

Теперь, очевидно, я мог бы просто добавить if, чтобы определить, if args.gamesявляется отрицательным, но мне было любопытно, если бы был способ отловить его на argparseуровне, чтобы воспользоваться преимуществами автоматической печати использования.

В идеале было бы напечатано что-то похожее на это:

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'

Вот так:

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'

Пока я делаю это, и я думаю, что я счастлив:

if args.games <= 0:
    parser.print_help()
    print "-g/--games: must be positive."
    sys.exit(1)
jgritty
источник

Ответы:

244

Это должно быть возможно с использованием type. Вам все еще нужно определить фактический метод, который решит это для вас:

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)

Это в основном только адаптированный пример из perfect_squareфункции в документации по argparse.

Yuushi
источник
1
Может ли ваша функция иметь несколько значений? Как это работает?
Том
2
Если преобразование в intне удастся, все еще будет читаемый вывод? Или вы должны try raiseконвертировать вручную для этого?
NOhs
4
@MrZ Это даст что-то вроде error: argument foo: invalid check_positive value: 'foo=<whatever>'. Вы могли бы просто добавить try:... except ValueError:вокруг него, который повторно вызывает исключение с лучшим сообщением об ошибке.
Юиши
59

type будет рекомендуемым вариантом для обработки условий / проверок, как в ответе Юши.

В вашем конкретном случае вы также можете использовать choicesпараметр, если ваш верхний предел также известен:

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

Примечание: используйте rangeвместо xrangePython 3.x

анероид
источник
3
Я полагаю, что это будет довольно неэффективно, поскольку вы будете генерировать диапазон, а затем циклически проверять его, чтобы подтвердить свой вклад. Быстрый ifнамного быстрее.
TravisThomas
2
@ trav1th На самом деле это может быть, но это пример использования из документов. Кроме того, я сказал в своем ответе, что ответ Юуши - тот, на который нужно пойти. Хорошо, чтобы дать варианты. А в случае argparse, это происходит один раз за исполнение, использует generator ( xrange) и не требует дополнительного кода. Этот компромисс доступен. Каждый решает, в какую сторону идти.
анероид
16
Чтобы быть более понятным относительно точки зрения jgritty на ответ автора, choices = xrange (0,1000) приведет к тому, что весь список целых чисел от 1 до 999 включительно будет записываться в вашу консоль каждый раз, когда вы используете --help, или если неверный аргумент при условии. Не очень хороший выбор в большинстве случаев.
biomiker
9

Быстрый и грязный способ, если у вас есть предсказуемый максимум и минимум для вашего аргумента, это использовать choicesс диапазоном

parser.add_argument('foo', type=int, choices=xrange(0, 1000))
Бен автор
источник
24
Недостатком является отвратительный вывод.
jgritty
6
акцент на грязный , я думаю.
Бен автор
4
Чтобы прояснить точку зрения jgritty, choices = xrange (0,1000) приведет к тому, что весь список целых чисел от 1 до 999 включительно будет записываться в вашу консоль каждый раз, когда вы используете --help или если указан неверный аргумент. Не очень хороший выбор в большинстве случаев.
biomiker
8

Более простой альтернативой, особенно в случае argparse.ArgumentParserсоздания подклассов , является инициирование проверки изнутри parse_argsметода.

Внутри такого подкласса:

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

Эта техника может быть не такой крутой, как пользовательская функция, но она делает свою работу.


О себе ArgumentParser.error(message):

Этот метод печатает сообщение об использовании, включая сообщение о стандартной ошибке, и завершает программу с кодом состояния 2.


Кредит: ответ Джонатаном

Акаменус
источник
Или, по крайней мере, заменить print "-g/--games: must be positive."; sys.exit(1)просто parser.error("-g/--games: must be positive."). (Использование, как в ответе Джонатана .)
анероид
3

В случае, если кто-то (например, я) сталкивается с этим вопросом в поиске в Google, вот пример того, как использовать модульный подход, чтобы аккуратно решить более общую проблему разрешения целых чисел argparse только в указанном диапазоне :

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

Это позволяет вам сделать что-то вроде:

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

Теперь переменная fooдопускает только положительные целые числа , как в запросе OP.

Обратите внимание, что в дополнение к вышеперечисленным формам возможен только максимум с IntRange:

parser.add_argument('other', type=IntRange(imax=10))  # Must have other <= 10
pallgeuer
источник