web-dev-qa-db-fra.com

Arguments requis par Python Argparse

J'ai fait autant de recherches que possible, mais je n'ai pas trouvé le meilleur moyen de rendre certains arguments de commande nécessaires uniquement dans certaines conditions, dans ce cas uniquement si d'autres arguments ont été donnés. Voici ce que je veux faire à un niveau très basique:

p = argparse.ArgumentParser(description='...')
p.add_argument('--argument', required=False)
p.add_argument('-a', required=False) # only required if --argument is given
p.add_argument('-b', required=False) # only required if --argument is given

D'après ce que j'ai vu, d'autres personnes semblent simplement ajouter leur propre chèque à la fin:

if args.argument and (args.a is None or args.b is None):
    # raise argparse error here

Y a-t-il un moyen de faire cela nativement dans le paquet argparse?

21
DJMcCarthy12

Je cherche une réponse simple à ce genre de question depuis un certain temps. Tout ce que vous avez à faire est de vérifier si '--argument' est dans sys.argv, donc pour votre exemple de code, vous pouvez simplement faire:

import argparse
import sys

if __== '__main__':
    p = argparse.ArgumentParser(description='...')
    p.add_argument('--argument', required=False)
    p.add_argument('-a', required='--argument' in sys.argv) #only required if --argument is given
    p.add_argument('-b', required='--argument' in sys.argv) #only required if --argument is given
    args = p.parse_args()

De cette façon, required reçoit soit True, soit False, selon que l'utilisateur a utilisé --argument ou non. Déjà testé, semble fonctionner et garantit que -a et -b se comportent de manière indépendante.

19
Mira

Vous pouvez implémenter une vérification en fournissant une action personnalisée pour --argument, qui prendra un argument de mot clé supplémentaire pour spécifier la ou les autres actions devant devenir obligatoires si --argument est utilisé.

import argparse

class CondAction(argparse.Action):
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        x = kwargs.pop('to_be_required', [])
        super(CondAction, self).__init__(option_strings, dest, **kwargs)
        self.make_required = x

    def __call__(self, parser, namespace, values, option_string=None):
        for x in self.make_required:
            x.required = True
        try:
            return super(CondAction, self).__call__(parser, namespace, values, option_string)
        except NotImplementedError:
            pass

p = argparse.ArgumentParser()
x = p.add_argument("--a")
p.add_argument("--argument", action=CondAction, to_be_required=[x])

La définition exacte de CondAction dépend de ce que --argument doit faire exactement. Mais, par exemple, si --argument est un type d'action classique, prendre un argument et sauvegarder, il ne suffit que d'hériter de argparse._StoreAction.

Dans l'exemple d'analyse, nous enregistrons une référence à l'option --a à l'intérieur de l'option --argument. Lorsque --argument apparaît sur la ligne de commande, il active l'indicateur required sur --a sur True. Une fois toutes les options traitées, argparse vérifie que toutes les options marquées comme requises ont été définies.

10
chepner

Votre test de post-analyse est correct, surtout si le test des valeurs par défaut avec is None convient à vos besoins.

http://bugs.python.org/issue11588'Add "necessarily inclusive" groups to argparse' étudie la mise en œuvre de tels tests à l'aide du mécanisme groups (une généralisation de mutuall_exclusive_groups).

J'ai écrit un ensemble de UsageGroups qui implémente des tests tels que xor (mutuellement exclusif), and, or et not. Je pensais que celles-ci étaient complètes, mais je n'ai pas été en mesure d'exprimer votre cas en ce qui concerne ces opérations. (on dirait que j'ai besoin de nand - pas et, voir ci-dessous)

Ce script utilise une classe Test personnalisée, qui implémente essentiellement votre test de post-analyse. seen_actions est une liste d'actions que l'analyse a vues.

class Test(argparse.UsageGroup):
    def _add_test(self):
        self.usage = '(if --argument then -a and -b are required)'
        def testfn(parser, seen_actions, *vargs, **kwargs):
            "custom error"
            actions = self._group_actions
            if actions[0] in seen_actions:
                if actions[1] not in seen_actions or actions[2] not in seen_actions:
                    msg = '%s - 2nd and 3rd required with 1st'
                    self.raise_error(parser, msg)
            return True
        self.testfn = testfn
        self.dest = 'Test'
p = argparse.ArgumentParser(formatter_class=argparse.UsageGroupHelpFormatter)
g1 = p.add_usage_group(kind=Test)
g1.add_argument('--argument')
g1.add_argument('-a')
g1.add_argument('-b')
print(p.parse_args())

Exemple de sortie:

1646:~/mypy/argdev/usage_groups$ python3 issue25626109.py --arg=1 -a1
usage: issue25626109.py [-h] [--argument ARGUMENT] [-a A] [-b B]
                        (if --argument then -a and -b are required)
issue25626109.py: error: group Test: argument, a, b - 2nd and 3rd required with 1st

usage et les messages d'erreur ont encore besoin de travail. Et cela ne fait rien que le test de post-analyse ne puisse pas.


Votre test génère une erreur si (argument & (!a or !b)). Inversement, ce qui est autorisé est !(argument & (!a or !b)) = !(argument & !(a and b)). En ajoutant un test nand à mes classes UsageGroup, je peux implémenter votre cas de la manière suivante:

p = argparse.ArgumentParser(formatter_class=argparse.UsageGroupHelpFormatter)
g1 = p.add_usage_group(kind='nand', dest='nand1')
arg = g1.add_argument('--arg', metavar='C')
g11 = g1.add_usage_group(kind='nand', dest='nand2')
g11.add_argument('-a')
g11.add_argument('-b')

L'utilisation est (en utilisant !() pour marquer un test 'nand'):

usage: issue25626109.py [-h] !(--arg C & !(-a A & -b B))

Je pense que c'est le moyen le plus court et le plus clair d'exprimer ce problème en utilisant des groupes d'usage général.


Dans mes tests, les entrées qui analysent avec succès sont les suivantes:

''
'-a1'
'-a1 -b2'
'--arg=3 -a1 -b2'

Ceux qui sont supposés générer des erreurs sont:

'--arg=3'
'--arg=3 -a1'
'--arg=3 -b2'
3
hpaulj

Jusqu'à ce que http://bugs.python.org/issue11588 soit résolu, je n'utiliserais que nargs :

p = argparse.ArgumentParser(description='...')
p.add_argument('--arguments', required=False, nargs=2, metavar=('A', 'B'))

De cette façon, si quelqu'un fournit --arguments, il aura 2 valeurs.

Peut-être que son résultat CLI est moins lisible, mais le code est beaucoup plus petit. Vous pouvez résoudre ce problème avec une bonne documentation/aide.

0
Yajo