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)
14
Ben Hoff 6 mars 2016 à 05:39

3 réponses

Meilleure réponse

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

Passez soigneusement les arguments positionnels en tant qu'args et les arguments facultatifs en tant que kwargs de l'argpase à une fonction

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))
11
Community 23 mai 2017 à 10:34

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))
7
Community 23 mai 2017 à 11:55

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 
5
chryss 6 mars 2016 à 02:52