Python Argparse arguments conditionnellement requis

j'ai fait autant de recherches que possible mais je n'ai pas trouvé la meilleure façon de faire certains arguments cmdline nécessaires que dans certaines conditions, dans ce cas seulement 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

De ce que j'ai vu, d'autres personnes semblent juste ajouter leurs propres vérifier à 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
demandé sur DJMcCarthy12 2014-09-02 18:41:21

5 réponses

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

import argparse
import sys

if __name__ == '__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 True ou False selon que l'utilisateur utilisé --argument. Déjà testé, semble fonctionner et garantit que -a et -b avoir un comportement indépendant l'un de l'autre.

16
répondu Mira 2017-05-26 22:34:17

vous pouvez implémenter une vérification en fournissant une action personnalisée pour --argument, ce qui aura un mot-clé argument pour spécifier une autre action(s) requis 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épendra de quoi, exactement,--argument devrait faire. Mais, par exemple, si --argument régulièrement, prenez-en un argument-et-save-il d'un type d'action, alors il suffit d'hériter de argparse._StoreAction devrait être suffisant.

Dans l'exemple analyseur de nous enregistrer une référence à l' --a option à l'intérieur du --argument option, et lorsque --argument est vu sur la ligne de commande, il définit l' required pavillon sur --aTrue. Une fois toutes les options traitées, argparse vérifie que toute option requis a été défini.

9
répondu chepner 2014-09-02 19:40:01

votre test de parsing post est correct, surtout si le test par défaut est is None convient à vos besoins.

http://bugs.python.org/issue11588'Add "necessarily inclusive" groups to argparse' cherche à implémenter des tests comme celui-ci en utilisant le groups mécanisme (une généralisation de mutuall_exclusive_groups).

j'ai écrit un ensemble de UsageGroups qui implémentent des tests comme xor (s'excluent mutuellement),and,or et not. Je pensais que ceux-là où complet, mais je n'ont pas été en mesure d'exprimer votre cas en termes de ces opérations. (on dirait que je besoin d' nand - pas et, voir ci-dessous)

ce script utilise un custom Test classe, que l'essentiel de l'implémente post-analyse de test. seen_actions est une liste d'Actions que l'analyse a vu.

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())

la sortie de L'échantillon est:

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 encore besoin de travail. Et ça ne fait rien que le test post-parsing ne peut pas.


test génère une erreur si (argument & (!a or !b)). À l'inverse, ce qui est permis est !(argument & (!a or !b)) = !(argument & !(a and b)). Par l'ajout d'un nand test pour mon UsageGroup classes, je peux mettre en œuvre votre cas:

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'usage est (en utilisant !() pour marquer un "nand' test):

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

je pense que c'est la façon la plus claire et la plus courte d'exprimer ce problème en utilisant des groupes d'usage général.


dans mes tests, des entrées qui analysent avec succès:

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

ceux qui sont censés soulever des erreurs sont:

'--arg=3'
'--arg=3 -a1'
'--arg=3 -b2'
3
répondu hpaulj 2014-09-04 16:26:12

Jusqu'à http://bugs.python.org/issue11588 est résolu, j'avais juste utiliser nargs:

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

de Cette façon, si quelqu'un a des fournitures --arguments, il aura 2 valeurs.

peut-être que son résultat CLI est moins lisible, mais le code est beaucoup plus petit. Vous pouvez corriger cela avec de bonnes docs/aide.

0
répondu Yajo 2016-03-29 11:37:05

pour les arguments, j'ai trouvé une solution rapide-n-sale comme celle-ci. Suppositions: (1)' -- help ' devrait afficher help et ne pas se plaindre de l'argument requis et (2) nous Parsons sys.argv

p = argparse.ArgumentParser(...)
p.add_argument('-required', ..., required = '--help' not in sys.argv )

cela peut facilement être modifié pour correspondre à un paramètre spécifique. Pour les positionnels requis (qui ne seront pas demandés si par exemple '--help' est donné en ligne de commande) j'ai trouvé ce qui suit: [les positionnels ne permettent pas un required=... mot clé arg!]

p.add_argument('pattern', ..., narg = '+' if '--help' not in sys.argv else '*' )

fondamentalement, cela fait passer le nombre d'occurrences requises de 'pattern' sur la ligne de commande d'un-ou-plus à zéro-ou-plus dans le cas où '--help' est spécifié.

-1
répondu haavee 2016-06-19 22:27:37