Saltar al contenido

Python: Typehints para objetos argparse.Namespace

Solución:

Considere definir una clase de extensión para argparse.Namespace que proporciona las sugerencias de tipo que desea:

class MyProgramArgs(argparse.Namespace):
    def __init__():
        self.somearg = 'defaultval' # type: str

Entonces usa namespace= pasar eso a parse_args:

def process_argv():
    parser = argparse.ArgumentParser()
    parser.add_argument('--somearg')
    nsp = MyProgramArgs()
    parsed = parser.parse_args(['--somearg','someval'], namespace=nsp)  # type: MyProgramArgs
    the_arg = parsed.somearg  # <- Pycharm should not complain

El analizador de argumentos mecanografiados se hizo exactamente para este propósito. Envuelve argparse. Su ejemplo se implementa como:

from tap import Tap


class ArgumentParser(Tap):
    somearg: str


parsed = ArgumentParser().parse_args(['--somearg', 'someval'])
the_arg = parsed.somearg

Aquí hay una foto de él en acción.
ingrese la descripción de la imagen aquí

Está en PyPI y se puede instalar con: pip install typed-argument-parser

Divulgación completa: soy uno de los creadores de esta biblioteca.

No sé nada sobre cómo PyCharm maneja estas sugerencias tipográficas, pero entiendo el Namespace código.

argparse.Namespace es una clase simple; esencialmente un objeto con algunos métodos que facilitan la visualización de los atributos. Y para facilitar las pruebas unitarias, tiene un __eq__ método. Puede leer la definición en el argparse.py expediente.

los parser interactúa con el espacio de nombres de la forma más general posible, con getattr, setattr, hasattr. Entonces puedes usar casi cualquier dest cadena, incluso aquellos a los que no puede acceder con el .dest sintaxis.

Asegúrese de no confundir el add_argument type= parámetro; eso es una función.

Usando el tuyo namespace class (desde cero o en subclases) como se sugiere en la otra respuesta puede ser la mejor opción. Esto se describe brevemente en la documentación. Objeto de espacio de nombres. No he visto que esto se haga mucho, aunque lo he sugerido varias veces para manejar necesidades especiales de almacenamiento. Entonces tendrás que experimentar.

Si usa subanálisis, el uso de una clase de espacio de nombres personalizada puede fallar, http://bugs.python.org/issue27859

Preste atención al manejo de incumplimientos. El valor predeterminado predeterminado para la mayoría argparse acciones es None. Es útil usar esto después de analizar para hacer algo especial si el usuario no proporcionó esta opción.

 if args.foo is None:
     # user did not use this optional
     args.foo = 'some post parsing default'
 else:
     # user provided value
     pass

Eso podría interferir con las sugerencias de tipo. Cualquiera que sea la solución que intente, preste atención a los valores predeterminados.


A namedtuple no funcionará como Namespace.

Primero, el uso adecuado de una clase de espacio de nombres personalizada es:

nm = MyClass(<default values>)
args = parser.parse_args(namespace=nm)

Es decir, inicializa una instancia de esa clase y la pasa como parámetro. El retorno args será la misma instancia, con nuevos atributos establecidos por análisis.

En segundo lugar, solo se puede crear una tupla con nombre, no se puede cambiar.

In [72]: MagicSpace=namedtuple('MagicSpace',['foo','bar'])
In [73]: nm = MagicSpace(1,2)
In [74]: nm
Out[74]: MagicSpace(foo=1, bar=2)
In [75]: nm.foo='one'
...
AttributeError: can't set attribute
In [76]: getattr(nm, 'foo')
Out[76]: 1
In [77]: setattr(nm, 'foo', 'one')    # not even with setattr
...
AttributeError: can't set attribute

Un espacio de nombres tiene que funcionar getattr y setattr.

Otro problema con namedtuple es que no establece ningún tipo de type información. Simplemente define los nombres de los campos / atributos. Por tanto, no hay nada que comprobar la escritura estática.

Si bien es fácil obtener los nombres de atributos esperados del parser, no puede obtener los tipos esperados.

Para un analizador simple:

In [82]: parser.print_usage()
usage: ipython3 [-h] [-foo FOO] bar
In [83]: [a.dest for a in parser._actions[1:]]
Out[83]: ['foo', 'bar']
In [84]: [a.type for a in parser._actions[1:]]
Out[84]: [None, None]

Las acciones dest es el nombre de atributo normal. Pero type no es el tipo estático esperado de ese atributo. Es una función que puede convertir o no la cadena de entrada. Aquí None significa que la cadena de entrada se guarda tal cual.

Porque la escritura estática y argparse requieren información diferente, no hay una manera fácil de generar uno a partir del otro.

Creo que lo mejor que puede hacer es crear su propia base de datos de parámetros, probablemente en un diccionario, y crear tanto la clase de espacio de nombres como el analizador a partir de eso, con sus propias funciones de utilidad.

Digamos dd es diccionario con las claves necesarias. Entonces podemos crear un argumento con:

parser.add_argument(dd['short'],dd['long'], dest=dd['dest'], type=dd['typefun'], default=dd['default'], help=dd['help'])

Usted u otra persona tendrá que crear una definición de clase de espacio de nombres que establezca el default (fácil) y de tipo estático (¿difícil?) de dicho diccionario.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *