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?
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.
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 --a
True
. Une fois toutes les options traitées, argparse
vérifie que toute option requis a été défini.
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'
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.
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é.