172 votes

Argparse: L'argument requis 'y' si 'x' est présent

J'ai une exigence comme suit:

./xyifier --prox --lport lport --rport rport

pour l'argument prox , j'utilise action = 'store_true' pour vérifier s'il est présent ou non. Je n'ai pas besoin de l'un des arguments. Mais, si --prox est défini, j'ai également besoin de rport et lport. Y a-t-il un moyen facile de le faire avec argparse sans écrire de code conditionnel personnalisé.

Plus de code:

non_int.add_argument('--prox', action='store_true', help='Indicateur pour activer le proxy')
non_int.add_argument('--lport', type=int, help='Port d'écoute.')
non_int.add_argument('--rport', type=int, help='Port du proxy.')

193voto

borntyping Points 2731

Non, il n'y a pas d'option dans argparse pour créer des ensembles d'options mutuellement inclusifs.

La façon la plus simple de gérer cela serait :

if args.prox et (args.lport est None or args.rport est None):
    parser.error("--prox nécessite --lport et --rport.")

En fait, il y a déjà une PR ouverte avec une proposition d'amélioration : https://github.com/python/cpython/issues/55797

88voto

Mira Points 1235

Vous parlez de la nécessité conditionnelle d'arguments. Comme l'a dit @borntyping, vous pourriez vérifier l'erreur et faire parser.error(), ou vous pourriez simplement appliquer une exigence liée à --prox lorsque vous ajoutez un nouvel argument.

Une solution simple pour votre exemple pourrait être :

non_int.add_argument('--prox', action='store_true', help='Drapeau pour activer le proxy')
non_int.add_argument('--lport', required='--prox' in sys.argv, type=int)
non_int.add_argument('--rport', required='--prox' in sys.argv, type=int)

Ainsi, required reçoit soit True ou False selon que l'utilisateur a utilisé --prox. Cela garantit également que -lport et -rport aient un comportement indépendant l'un de l'autre.

21voto

Aditya Sriram Points 363

Que diriez-vous d'utiliser la méthode parser.parse_known_args() et d'ajouter ensuite les arguments --lport et --rport comme arguments requis si --prox est présent.

# ajoutez simplement --prox maintenant
non_int = argparse.ArgumentParser(description="Question stackoverflow", 
                                  usage="%(prog)s [-h] [--prox --lport port --rport port]")
non_int.add_argument('--prox', action='store_true', 
                     help='Drapeau pour activer le proxy, nécessite des arguments supplémentaires lport et rport')
opts, rem_args = non_int.parse_known_args()
if opts.prox:
    non_int.add_argument('--lport', required=True, type=int, help='Port d'écoute.')
    non_int.add_argument('--rport', required=True, type=int, help='Port du proxy.')
    # utilisez les options et l'espace de noms de la première analyse
    non_int.parse_args(rem_args, namespace = opts)

Gardez également à l'esprit que vous pouvez fournir l'espace de noms opts généré après la première analyse tout en analysant les arguments restants la deuxième fois. De cette façon, à la fin, après que toute l'analyse soit terminée, vous aurez un seul espace de noms avec toutes les options.

Inconvénients:

  • Si --prox n'est pas présent, les deux autres options dépendantes ne sont même pas présentes dans l'espace de noms. Bien que, selon votre cas d'utilisation, si --prox n'est pas présent, ce qui arrive aux autres options est sans importance.
  • Besoin de modifier le message d'utilisation car l'analyseur ne connaît pas la structure complète
  • --lport et --rport n'apparaissent pas dans le message d'aide

10voto

hpaulj Points 6132

Utilisez-vous lport lorsque prox n'est pas défini. Si ce n'est pas le cas, pourquoi ne pas faire de lport et rport des arguments de prox? par exemple

parser.add_argument('--prox', nargs=2, type=int, help='Prox: listen and proxy ports')

Cela évite à vos utilisateurs de taper. Il est aussi facile de tester if args.prox is not None: que if args.prox:.

2voto

Daniel Butler Points 105

La réponse acceptée a très bien fonctionné pour moi! Puisque tout le code est inutilisable sans tests, voici comment j'ai testé la réponse acceptée. parser.error() ne lève pas d'erreur argparse.ArgumentError mais arrête plutôt le processus. Vous devez tester pour SystemExit.

avec pytest

import pytest
from . import parse_arguments  # code qui provoque une erreur de parse()

def test_args_parsed_raises_error():
    with pytest.raises(SystemExit):
        parse_arguments(["argument that raises error"])

avec unittests

from unittest import TestCase
from . import parse_arguments  # code qui provoque une erreur de parse()

class TestArgs(TestCase):

    def test_args_parsed_raises_error():
        with self.assertRaises(SystemExit) as cm:
            parse_arguments(["argument that raises error"])

inspiré de: Utiliser unittest pour tester les erreurs de sortie d'argparse

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X