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?
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.
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.
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'
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.