Existe-t-il un moyen pour que les analyseurs statiques Python (par exemple dans PyCharm, d'autres IDE)) reprennent les typographies sur argparse.Namespace
objets? Exemple:
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 je supprime la déclaration de type dans le commentaire en ligne, PyCharm ne se plaint pas, mais ne détecte pas non plus les attributs invalides. Par exemple:
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.
Des idées?
Inspiré par réponse d'Austin ci-dessous, la solution la plus simple que j'ai pu trouver est celle utilisant 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!
Bien que cela soit satisfaisant, je n'aime toujours pas avoir à répéter les noms des arguments. Si la liste d'arguments s'allonge considérablement, la mise à jour des deux emplacements sera fastidieuse. L'idéal serait d'extraire en quelque sorte les arguments de l'objet parser
comme suit:
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
Je n'ai pas pu trouver quoi que ce soit dans le module argparse
qui pourrait rendre cela possible, et je ne sais toujours pas si L'outil d'analyse statique pourrait être assez intelligent pour obtenir ces valeurs et ne pas arrêter le IDE).
Toujours à la recherche...
Selon le commentaire de hpaulj, la chose la plus proche que j'ai pu trouver de la méthode décrite ci-dessus qui extrairait "par magie" les attributs de l'objet analysé est quelque chose qui extrairait l'attribut dest
de chacun des analyseurs _action
s .:
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
Mais cela ne fait toujours pas signaler les erreurs d'attribut dans l'analyse statique. Cela est également vrai si je réussis namespace=MagicNamespace
dans le parser.parse_args
appel.
analyseur d'arguments typés a été créé exactement dans ce but. Il enveloppe argparse
. Votre exemple est implémenté comme:
from tap import Tap
class ArgumentParser(Tap):
somearg: str
parsed = ArgumentParser().parse_args(['--somearg', 'someval'])
the_arg = parsed.somearg
Divulgation complète: je suis l'un des créateurs de cette bibliothèque.
Pensez à définir une classe d'extension sur argparse.Namespace
qui fournit les indications de type souhaitées:
class MyProgramArgs(argparse.Namespace):
def __init__():
self.somearg = 'defaultval' # type: str
Utilisez ensuite namespace=
pour passer ç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
Je ne sais rien sur la façon dont PyCharm gère ces caractères typographiques, mais je comprends le code Namespace
.
argparse.Namespace
est une classe simple; essentiellement un objet avec quelques méthodes qui facilitent la visualisation des attributs. Et pour plus de facilité, il a un __eq__
méthode. Vous pouvez lire la définition dans le argparse.py
fichier.
parser
interagit avec l'espace de noms de la manière la plus générale possible - avec getattr
, setattr
, hasattr
. Vous pouvez donc utiliser presque toutes les chaînes dest
, même celles auxquelles vous ne pouvez pas accéder avec le .dest
syntaxe.
Assurez-vous de ne pas confondre le add_argument
type=
paramètre; c'est une fonction.
Utiliser votre propre classe namespace
(à partir de zéro ou sous-classe) comme suggéré dans l'autre réponse peut être la meilleure option. Ceci est décrit brièvement dans la documentation. objet d'espace de noms . Je n'ai pas vu cela faire beaucoup, bien que je l'ai suggéré à quelques reprises pour gérer des besoins de stockage spéciaux. Vous devrez donc expérimenter.
Si vous utilisez des sous-analyseurs, l'utilisation d'une classe d'espace de noms personnalisée peut se casser, http://bugs.python.org/issue27859
Faites attention à la gestion des défauts. La valeur par défaut par défaut pour la plupart des actions argparse
est None
. Il est pratique de l'utiliser après l'analyse pour faire quelque chose de spécial si l'utilisateur n'a pas fourni cette option.
if args.foo is None:
# user did not use this optional
args.foo = 'some post parsing default'
else:
# user provided value
pass
Cela pourrait gêner les indices de type. Quelle que soit la solution que vous essayez, faites attention aux valeurs par défaut.
Un namedtuple
ne fonctionnera pas comme un Namespace
.
Tout d'abord, l'utilisation appropriée d'une classe d'espace de noms personnalisée est la suivante:
nm = MyClass(<default values>)
args = parser.parse_args(namespace=nm)
Autrement dit, vous initialisez une instance de cette classe et la transmettez en tant que paramètre. Le args
retourné sera la même instance, avec de nouveaux attributs définis par l'analyse.
Deuxièmement, un tuple nommé peut uniquement être créé, il ne peut pas être modifié.
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 espace de noms doit fonctionner avec getattr
et setattr
.
Un autre problème avec namedtuple
est qu'il ne définit aucun type d'informations type
. Il définit simplement les noms de champ/attribut. Il n'y a donc rien à vérifier pour la saisie statique.
Bien qu'il soit facile d'obtenir les noms d'attribut attendus à partir de parser
, vous ne pouvez pas obtenir les types attendus.
Pour un analyseur 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]
Les actions dest
est le nom d'attribut normal. Mais type
n'est pas le type statique attendu de cet attribut. Il s'agit d'une fonction qui peut convertir ou non la chaîne d'entrée. Ici None
signifie que la chaîne d'entrée est enregistrée telle quelle.
Étant donné que la saisie statique et argparse
nécessitent des informations différentes, il n'y a pas de moyen facile de générer l'une à partir de l'autre.
Je pense que le mieux que vous puissiez faire est de créer votre propre base de données de paramètres, probablement dans un dictionnaire, et de créer à la fois la classe Namespace et le parsesr, avec vos propres fonctions utilitaires.
Disons que dd
est un dictionnaire avec les clés nécessaires. Ensuite, nous pouvons créer un argument avec:
parser.add_argument(dd['short'],dd['long'], dest=dd['dest'], type=dd['typefun'], default=dd['default'], help=dd['help'])
Vous ou quelqu'un d'autre devrez trouver une définition de classe Namespace qui définit le default
(facile) et le type statique (difficile?) D'un tel dictionnaire.