J'ai écrit des programmes python en ligne de commande et utilisé argparse
pour le faire. J'ai structuré mon code un peu comme suit.
def main(arg1, arg2):
# magic
pass
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('arg1')
parser.add_argument('arg2')
args = parser.parse_args()
main(args.arg1, args.arg2)
C'est vraiment super irritant de devoir appeler arg1
et arg2
3 fois. Je comprends devoir le faire deux fois.
Existe-t-il un moyen de traiter l'espace de noms renvoyé par la fonction parse_args
comme un tuple? Ou mieux encore comme tuple et dict pour les arguments optionnels et faire le déballage?
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('arg1')
parser.add_argument('arg2')
parser.add_argument('--opt-arg', default='default_value')
args, kwargs = parser.magic_method_call_that_would_make_my_life_amazing()
# I get goosebumps just thinking about this
main(*args, **kwargs)
3 réponses
https://docs.python.org/3/library/argparse.html#the-namespace-object
Cette classe est délibérément simple, juste une sous-classe d'objets avec une représentation de chaîne lisible. Si vous préférez avoir une vue dictée des attributs, vous pouvez utiliser l'idiome Python standard, vars () :
>>>
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo')
>>> args = parser.parse_args(['--foo', 'BAR'])
>>> vars(args)
{'foo': 'BAR'}
Notez que l'une des grandes avancées, ou changements au moins, de optparse
à argparse
est que les arguments positionnels, tels que les vôtres, sont traités de la même manière que les options. Ils apparaissent tous les deux dans l'objet args
Namespace
. Dans optparse
, les positions ne sont que les restes de l'analyse des options définies. Vous pouvez obtenir le même effet dans argparse
en omettant vos arguments et en utilisant parse_known_args
:
parser = argparse.ArgumentParser()
args, extras = parser.parse_known_args()
args
est maintenant un espace de noms et extras
une liste. Vous pouvez alors appeler votre fonction comme:
myfoo(*extras, **vars(args))
Par exemple:
In [994]: import argparse
In [995]: def foo(*args, **kwargs):
.....: print(args)
.....: print(kwargs)
.....:
In [996]: parser=argparse.ArgumentParser()
In [997]: parser.add_argument('-f','--foo')
Out[997]: _StoreAction(option_strings=['-f', '--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)
In [998]: args,extras = parser.parse_known_args(['-f','foobar','arg1','arg2'])
In [999]: args
Out[999]: Namespace(foo='foobar')
In [1000]: extras
Out[1000]: ['arg1', 'arg2']
In [1001]: foo(*extras, **vars(args))
('arg1', 'arg2')
{'foo': 'foobar'}
Ce même paragraphe argparse
montre que vous pouvez définir votre propre classe Namespace
. Il ne serait pas difficile d'en définir un qui se comporte comme un dictionnaire (à utiliser comme **args
) et comme un espace de noms. Tout ce que argparse
exige, c'est qu'il fonctionne avec getattr
et setattr
.
In [1002]: getattr(args,'foo')
Out[1002]: 'foobar'
In [1004]: setattr(args,'bar','ugg')
In [1005]: args
Out[1005]: Namespace(bar='ugg', foo='foobar')
Une autre fonctionnalité Python standard me permet de passer vars(args)
en tant que tuple:
In [1013]: foo(*vars(args).items())
(('foo', 'foobar'), ('bar', 'ugg'))
{}
Pour une réponse similaire de janvier dernier: https://stackoverflow.com/a/34932478/901925
Là, je donne des idées sur la façon de séparer les «positionnels» des «optionnels» après l'analyse.
Voici une classe d'espace de noms personnalisée qui inclut, dans son API, un moyen de se retourner en tant que dictionnaire:
In [1014]: class MyNameSpace(argparse.Namespace):
......: def asdict(self):
......: return vars(self)
......:
In [1015]: args = parser.parse_args(['-f','foobar'], namespace=MyNameSpace())
In [1016]: args
Out[1016]: MyNameSpace(foo='foobar')
In [1017]: foo(**args.asdict())
()
{'foo': 'foobar'}
Une autre idée - utilisez l'un des multiples nargs
(2, '*', '+') pour l'argument positionnel. Vous n'avez alors qu'un seul nom à taper lorsque vous le transmettez à votre fonction.
parser.add_argument('pos',nargs='+')
args = ...
args.pos # a list, possibly empty
foo(*args.pos, **vars(args))
Vous pouvez voir une question similaire posée ici.
Modifier : à la recherche d'un moyen qui n'utiliserait pas de méthode interne, j'ai trouvé cette discussion qui suggéré d'utiliser vars()
. Cela fonctionne assez bien:
import argparse
def main(arg1, arg2):
print arg1, arg2
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('arg1')
parser.add_argument('arg2')
args = parser.parse_args()
main(**vars(args))
Quel est le problème de faire
if __name__ == '__main__':
# do argparse stuff as above
main(args)
Autrement dit, pourquoi êtes-vous si accro à donner des main()
arguments positionnels?
Pour être honnête, je fais généralement l'argument en analysant soit a) [pour les petits scripts, etc.] au début du module, ce qui me fournit une variable qui est dans la portée de toutes les fonctions ou b) [généralement] à l'intérieur {{X0 }} si j'utilise votre idiome:
def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument('arg1')
parser.add_argument('arg2')
args = parser.parse_args()
return args
def main():
args = parse_arguments()
# do stuff with args.arg1 and args.arg2
Questions connexes
Questions liées
De nouvelles questions
python
Python est un langage de programmation multi-paradigme, typé dynamiquement et polyvalent. Il est conçu pour être rapide à apprendre, comprendre, utiliser et appliquer une syntaxe propre et uniforme. Veuillez noter que Python 2 est officiellement hors support à partir du 01-01-2020. Néanmoins, pour les questions Python spécifiques à la version, ajoutez la balise [python-2.7] ou [python-3.x]. Lorsque vous utilisez une variante Python (par exemple, Jython, PyPy) ou une bibliothèque (par exemple, Pandas et NumPy), veuillez l'inclure dans les balises.