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.