web-dev-qa-db-fra.com

Plusieurs arguments de position avec Python et argparse

J'essaie d'utiliser argparse pour analyser les arguments de ligne de commande d'un programme sur lequel je travaille. Essentiellement, je dois prendre en charge plusieurs arguments de position répartis dans les arguments facultatifs, mais je ne peux pas faire fonctionner argparse dans cette situation. Dans le programme réel, j'utilise une action personnalisée (j'ai besoin de stocker un instantané de l'espace de noms chaque fois qu'un argument positionnel est trouvé), mais le problème que je rencontre peut être répliqué avec l'action append :

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-a', action='store_true')
>>> parser.add_argument('-b', action='store_true')
>>> parser.add_argument('input', action='append')
>>> parser.parse_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
usage: ipython [-h] [-a] [-b] input
ipython: error: unrecognized arguments: filetwo filethree

J'aimerais que cela se traduise par l'espace de noms (a=True, b=True, input=['fileone', 'filetwo', 'filethree']), mais je ne vois pas comment faire - si c'est le cas. Je ne vois rien dans les documents ou Google qui dit d'une manière ou d'une autre si cela est possible, bien que ce soit tout à fait possible (probablement?) J'ai oublié quelque chose. Est-ce que quelqu'un a des suggestions?

26
Blair

srgerg avait raison sur la définition des arguments positionnels. Pour obtenir le résultat souhaité, vous devez les accepter comme arguments facultatifs et modifier l'espace de noms résultant en fonction de vos besoins.

Vous pouvez utiliser une action personnalisée:

class MyAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):

        # Set optional arguments to True or False
        if option_string:
            attr = True if values else False
            setattr(namespace, self.dest, attr)

        # Modify value of "input" in the namespace
        if hasattr(namespace, 'input'):
            current_values = getattr(namespace, 'input')
            try:
                current_values.extend(values)
            except AttributeError:
                current_values = values
            finally:
                setattr(namespace, 'input', current_values)
        else:
            setattr(namespace, 'input', values)

parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='+', action=MyAction)
parser.add_argument('-b', nargs='+', action=MyAction)
parser.add_argument('input', nargs='+', action=MyAction)

Et voici ce que vous obtenez:

>>> parser.parse_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
Namespace(a=True, b=True, input=['fileone', 'filetwo', 'filethree'])

Ou vous pouvez modifier l'espace de noms résultant comme ceci:

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-a', nargs='+')
>>> parser.add_argument('-b', nargs='+')
>>> parser.add_argument('input', nargs='+')
>>> result = parser.parse_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])

>>> inputs = []
>>> inputs.extend(result.a)
>>> inputs.extend(result.b)
>>> inputs.extend(result.input)

>>> modified = argparse.Namespace(
        a = result.a != [],
        b = result.b != [],
        input = inputs)

Et voici ce que vous obtenez:

>>> modified
Namespace(a=True, b=True, input=['filetwo', 'filethree', 'fileone'])

Cependant, les deux méthodes entraînent un code moins lisible et moins maintenable. Il vaut peut-être mieux changer la logique du programme et le faire d'une manière différente.

10
Wang Dingwei

Vous ne pouvez pas entrelacer les commutateurs (c'est-à-dire -a et -b) avec les arguments positionnels (c'est-à-dire fileone, filetwo et filethree) de cette manière. Les commutateurs doivent apparaître avant ou après les arguments de position, pas entre les deux.

De plus, pour avoir plusieurs arguments positionnels, vous devez spécifier le paramètre nargs à add_argument. Par exemple:

parser.add_argument('input', nargs='+')

Cela indique à argparse de consommer un ou plusieurs arguments positionnels et de les ajouter à une liste. Voir la documentation argparse pour plus d'informations. Avec cette ligne, le code:

parser.parse_args(['-a', '-b', 'fileone', 'filetwo', 'filethree'])

résulte en:

Namespace(a=True, b=True, input=['fileone', 'filetwo', 'filethree'])
18
srgerg

L'action "ajouter" a plus de sens avec une option:

parser.add_argument('-i', '--input',action='append')
parser.parse_args(['-i','fileone', '-a', '-i','filetwo', '-b', '-i','filethree'])

Vous pouvez entrelacer les options avec des positions distinctes ("entrée1 -a entrée2 -b entrée3"), mais vous ne pouvez pas entrelacer les options au sein d'une position multi-éléments. Mais vous pouvez accomplir cela avec une analyse en deux étapes.

import argparse
parser1 = argparse.ArgumentParser()
parser1.add_argument('-a', action='store_true')
parser1.add_argument('-b', action='store_true')
parser2 = argparse.ArgumentParser()
parser2.add_argument('input', nargs='*')

ns, rest = parser1.parse_known_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
# Namespace(a=True, b=True), ['fileone', 'filetwo', 'filethree']

ns = parser2.parse_args(rest, ns)
# Namespace(a=True, b=True, input=['fileone', 'filetwo', 'filethree'])

http://bugs.python.org/issue14191 est un correctif proposé qui le fera avec un seul appel à:

parser.parse_intermixed_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
3
hpaulj

Il me semble que hpaulj est sur la bonne voie mais rend les choses un peu plus compliquées que nécessaire. Je soupçonne que Blair cherche quelque chose qui ressemble au comportement de l'ancien module optparse et n'a pas vraiment besoin de la liste des arguments d'entrée dans le champ d'entrées de l'objet args. Il veut juste

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', action='store_true')
parser.add_argument('-b', action='store_true')
opts, args = parser.parse_known_args(['fileone', '-a', 'filetwo', '-b', 'filethree'])
# Namespace(a=True, b=True), ['fileone', 'filetwo', 'filethree']

Dans la langue vernaculaire de l'optparse, les "options" sont disponibles dans opts, et la liste des autres "arguments" éventuellement entrecoupés est dans args.

2
Malcolm