type statically parameter library hinting python python-3.x pycharm argparse type-hinting

statically - Python: Typehints para objetos argparse.Namespace



typing io python (2)

¿Hay alguna forma de que los analizadores estáticos de Python (p. Ej., En PyCharm, otros IDE) seleccionen Typehints en objetos argparse.Namespace ? Ejemplo:

parser = argparse.ArgumentParser() parser.add_argument(''--somearg'') parsed = parser.parse_args([''--somearg'',''someval'']) # type: argparse.Namespace the_arg = parsed.somearg # <- Pycharm complains that parsed object has no attribute ''somearg''

Si elimino la declaración de tipo en el comentario en línea, PyCharm no se queja, pero tampoco recoge los atributos no válidos. Por ejemplo:

parser = argparse.ArgumentParser() parser.add_argument(''--somearg'') parsed = parser.parse_args([''--somearg'',''someval'']) # no typehint the_arg = parsed.somaerg # <- typo in attribute, but no complaint in PyCharm. Raises AttributeError when executed.

¿Algunas ideas?

Actualizar

Inspirado por la respuesta de Austin a continuación, la solución más sencilla que pude encontrar es una que use namedtuples :

from collections import namedtuple ArgNamespace = namedtuple(''ArgNamespace'', [''some_arg'', ''another_arg'']) parser = argparse.ArgumentParser() parser.add_argument(''--some-arg'') parser.add_argument(''--another-arg'') parsed = parser.parse_args([''--some-arg'', ''val1'', ''--another-arg'', ''val2'']) # type: ArgNamespace x = parsed.some_arg # good... y = parsed.another_arg # still good... z = parsed.aint_no_arg # Flagged by PyCharm!

Si bien esto es satisfactorio, todavía no me gusta tener que repetir los nombres de los argumentos. Si la lista de argumentos crece considerablemente, será tedioso actualizar ambas ubicaciones. Lo que sería ideal es extraer de alguna manera los argumentos del objeto de parser como el siguiente:

parser = argparse.ArgumentParser() parser.add_argument(''--some-arg'') parser.add_argument(''--another-arg'') MagicNamespace = parser.magically_extract_namespace() parsed = parser.parse_args([''--some-arg'', ''val1'', ''--another-arg'', ''val2'']) # type: MagicNamespace

No he podido encontrar nada en el módulo argparse que pudiera hacer esto posible, y todavía no estoy seguro si alguna herramienta de análisis estático podría ser lo suficientemente inteligente como para obtener esos valores y no detener el IDE.

Sigue buscando...

Actualización 2

Según el comentario de hpaulj, lo más cercano que pude encontrar al método descrito anteriormente que extraería "mágicamente" los atributos del objeto analizado es algo que extraería el atributo dest de cada una de las _action del analizador:

parser = argparse.ArgumentParser() parser.add_argument(''--some-arg'') parser.add_argument(''--another-arg'') MagicNamespace = namedtuple(''MagicNamespace'', [act.dest for act in parser._actions]) parsed = parser.parse_args([''--some-arg'', ''val1'', ''--another-arg'', ''val2'']) # type: MagicNamespace

Pero esto todavía no causa que los errores de atributo se marquen en el análisis estático. Esto también es cierto si paso namespace=MagicNamespace en la llamada parser.parse_args .


Considere la posibilidad de definir una clase de extensión para argparse.Namespace que proporcione las sugerencias de tipo que desee:

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

Luego use namespace= para 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


No sé nada sobre cómo PyCharm maneja estas sugerencias de tipo, pero entiendo el código del Namespace .

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

El parser interactúa con el espacio de nombres de la manera más general posible: con getattr , setattr , hasattr . Por lo tanto, puede usar casi cualquier cadena de dest , incluso a las que no puede acceder con la sintaxis .dest .

Asegúrese de no confundir el parámetro add_argument type= ; esa es una funcion

El uso de su propia clase de namespace (desde cero o subclasificado) 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 lo he visto hacer mucho, aunque lo he sugerido varias veces para manejar las necesidades especiales de almacenamiento. Así que tendrás que experimentar.

Si usa subparsers, el uso de una clase de espacio de nombres personalizada puede interrumpirse, http://bugs.python.org/issue27859

Preste atención al manejo de los incumplimientos. El valor predeterminado predeterminado para la mayoría de argparse acciones argparse 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 en el tipo de pistas. Cualquiera que sea la solución que intente, preste atención a los valores predeterminados.

Un namedtuple con nombre no funcionará como un 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, inicia una instancia de esa clase y la pasa como parámetro. Los args devueltos serán la misma instancia, con nuevos atributos establecidos por análisis.

En segundo lugar, un grupo con nombre solo se puede crear, 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 trabajar con getattr y setattr .

Otro problema con namedtuple es que no establece ningún tipo de información de type . Solo define los nombres de campos / atributos. Así que no hay nada para la tipificación estática para comprobar.

Si bien es fácil obtener los nombres de atributos esperados del parser , no puede obtener ningún tipo esperado.

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 el type no es el tipo estático esperado de ese atributo. Es una función que puede o no convertir la cadena de entrada. Aquí None significa que la cadena de entrada se guarda como está.

Debido a que la escritura estática y argparse requieren información diferente, no hay una manera fácil de generar una de la otra.

Creo que lo mejor que puedes hacer es crear tu propia base de datos de parámetros, probablemente en un diccionario, y crear tanto la clase de espacio de nombres como el parsesr, con tus propias funciones de utilidad.

Digamos que dd es el 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 tipo default (fácil) y estático (¿duro?) De tal diccionario.