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

Відповіді:


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.


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 Дійсно, це може бути, але це приклад використання в документах. Крім того, я сказав у своїй відповіді, що відповідь Юуші - це саме те, що потрібно робити. Добре давати варіанти. А у випадку аргпарсу, це відбувається один раз за виконання, використовує генератор ( xrange) і не потребує додаткового коду. Такий компроміс доступний. До кожного вирішувати, яким шляхом рухатися.
анероїд

16
Щоб зрозуміти точку jgritty у відповіді автора автора, вибір = xrange (0,1000) призведе до того, що весь список цілих чисел від 1 до 999 включно буде записуватися на вашу консоль кожного разу, коли ви використовуєте --help або якщо невірний аргумент є за умови. Не вдалий вибір у більшості обставин.
біомікер

9

Швидкий і брудний спосіб, якщо у вас є передбачуваний макс, а також хв для вашої аргументації, використовується choicesз діапазоном

parser.add_argument('foo', type=int, choices=xrange(0, 1000))

24
Недоліком є ​​огидний вихід.
jgritty

6
акцент на брудному , я гадаю.
Бен автор

4
Щоб зрозуміти точку jgritty, вибір = xrange (0,1000) призведе до того, що весь список цілих чисел від 1 до 999 включно буде записаний на вашу консоль кожного разу, коли ви використовуєте --help або якщо буде надано недійсний аргумент. Не вдалий вибір у більшості обставин.
біомікер

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.


Кредит: відповідь jonatan


Або принаймні, замінюючи 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допускає лише додатні цілі числа , як запитує ОП.

Зауважте, що крім перерахованих вище форм, максимум можливо також IntRange:

parser.add_argument('other', type=IntRange(imax=10))  # Must have other <= 10
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.