En Python, usando argparse, permita solo números enteros positivos

6 minutos de lectura

avatar de usuario
jgritty

El título resume bastante bien lo que me gustaría que sucediera.

Esto es lo que tengo, y aunque el programa no explota en un número entero no positivo, quiero que el usuario sepa que un número entero no positivo es básicamente una tontería.

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

Y la salida:

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

Salida con un negativo:

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

Ahora, obviamente podría agregar un si para determinar if args.games es negativo, pero tenía curiosidad por saber si había una manera de atraparlo en el argparse nivel, para aprovechar la impresión de uso automático.

Idealmente, imprimiría algo similar a esto:

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'

Al igual que:

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'

Por ahora estoy haciendo esto, y supongo que estoy feliz:

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

avatar de usuario
Yuushi

Esto debería ser posible utilizando type. Aún deberá definir un método real que decida esto por usted:

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)

Esto es básicamente solo un ejemplo adaptado del perfect_square función en el documentos en argparse.

  • ¿Tu función puede tener múltiples valores? ¿Cómo funciona?

    – Tomás

    1 de julio de 2016 a las 22:11

  • Si la conversión a int falla, ¿seguirá habiendo una salida legible? o deberías try raise la conversión manualmente para eso?

    – NO

    12/09/2017 a las 15:58

  • @MrZ Dará algo como error: argument foo: invalid check_positive value: 'foo=<whatever>'. Simplemente podría agregar un try:except ValueError: a su alrededor que vuelve a generar una excepción con un mejor mensaje de error.

    – Yuushi

    13 de septiembre de 2017 a las 5:33


avatar de usuario
aneroide

type sería la opción recomendada para manejar condiciones/controles, como en la respuesta de Yuushi.

En tu caso concreto, también puedes utilizar el choices parámetro si también se conoce su límite superior:

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

Nota: Usar range en vez de xrange para pitón 3.x

  • Me imagino que esto sería bastante ineficiente, ya que generaría un rango y luego lo recorrería para validar su entrada. Un rápido if es mucho más rápido.

    – TravisThomas

    8 oct 2014 a las 18:27

  • @ trav1th De hecho, podría serlo, pero es un ejemplo de uso de los documentos. Además, he dicho en mi respuesta que la respuesta de Yuushi es la que hay que buscar. Bueno para dar opciones. Y en el caso de argparse, sucede una vez por ejecución, usa un generador (xrange) y no requiere código adicional. Esa compensación está disponible. Depende de cada uno decidir qué camino tomar.

    – aneroide

    9 oct 2014 a las 11:45

  • Para ser más claro sobre el punto de jgritty sobre la respuesta del autor de ben, las opciones = x rango (0,1000) darán como resultado que la lista completa de enteros del 1 al 999 inclusive se escriba en su consola cada vez que use –help o si un argumento no válido es previsto. No es una buena opción en la mayoría de las circunstancias.

    – biomiker

    29 de abril de 2016 a las 16:21

  • Si usa una amplia gama de números, no creo que esta sea la solución más limpia, ya que –help generará los números enteros que se procesan y arruinará por completo la vista de –help

    – JimShapedCoding

    9 de junio de 2021 a las 8:17

La forma rápida y sucia, si tiene un máximo predecible y un mínimo para su argumento, es usar choices con un rango

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

  • La desventaja es la horrible salida.

    – jgritty

    2 de enero de 2013 a las 6:05

  • enfásis en suciocreo.

    – ben autor

    2 de enero de 2013 a las 6:07

  • Para ser más claro sobre el punto de jgritty, las opciones = x rango (0,1000) darán como resultado que la lista completa de números enteros del 1 al 999 inclusive se escriba en su consola cada vez que use –help o si se proporciona un argumento no válido. No es una buena opción en la mayoría de las circunstancias.

    – biomiker

    29 de abril de 2016 a las 16:21

avatar de usuario
Asclepio

Una alternativa más simple, especialmente si se subclasifica argparse.ArgumentParseres iniciar la validación desde dentro del parse_args método.

Dentro de tal subclase:

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

Esta técnica puede no ser tan genial como un invocable personalizado, pero hace el trabajo.


Sobre ArgumentParser.error(message):

Este método imprime un mensaje de uso que incluye el mensaje de error estándar y finaliza el programa con un código de estado de 2.


Crédito: respuesta de jonatan

avatar de usuario
Palgeuer

En caso de que alguien (como yo) encuentre esta pregunta en una búsqueda de Google, aquí hay un ejemplo de cómo usar un enfoque modular para resolver claramente el problema más general de permitir números enteros argparse solo en un rango especificado:

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

Esto le permite hacer algo como:

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

La variable foo ahora solo permite enteros positivoscomo preguntó el OP.

Tenga en cuenta que, además de las formas anteriores, también es posible un máximo con IntRange:

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

avatar de usuario
Jan Heinrich Reimer

Según la respuesta de Yuushi, también puede definir una función de ayuda simple que puede verificar si un número es positivo para varios tipos numéricos:

def positive(numeric_type):
    def require_positive(value):
        number = numeric_type(value)
        if number <= 0:
            raise ArgumentTypeError(f"Number {value} must be positive.")
        return number

    return require_positive

La función auxiliar se puede utilizar para anotar cualquier tipo de argumento numérico como este:

parser = argparse.ArgumentParser(...)
parser.add_argument("positive-integer", type=positive(int))
parser.add_argument("positive-float", type=positive(float))

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad