Argparse: Arguments requis répertoriés sous «arguments facultatifs»?

229

J'utilise le code simple suivant pour analyser certains arguments; notez que l'un d'eux est obligatoire. Malheureusement, lorsque l'utilisateur exécute le script sans fournir l'argument, le texte d'utilisation / d'aide affiché n'indique pas qu'il existe un argument non facultatif, ce que je trouve très déroutant. Comment puis-je obtenir python pour indiquer qu'un argument n'est pas facultatif?

Voici le code:

import argparse
if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Foo')
    parser.add_argument('-i','--input', help='Input file name', required=True)
    parser.add_argument('-o','--output', help='Output file name', default="stdout")
    args = parser.parse_args()
    print ("Input file: %s" % args.input )
    print ("Output file: %s" % args.output )

Lors de l'exécution du code ci-dessus sans fournir l'argument requis, j'obtiens la sortie suivante:

usage: foo.py [-h] -i INPUT [-o OUTPUT]

Foo

optional arguments:
    -h, --help            show this help message and exit
    -i INPUT, --input INPUT
                          Input file name
    -o OUTPUT, --output OUTPUT
                          Output file name
mort
la source
5
Dans la ligne d'utilisation, la -i INPUTpièce n'est pas entourée de crochets, ce qui indique que la subtilité est effectivement requise. En outre, vous pouvez expliquer manuellement cela via le helpparam
Jaime RGP
7
@JaimeRGP Oui, mais ce n'est pas suffisant, bien sûr, et c'est aussi moins qu'éminent. Le nom de groupe attribué optional argumentsaux arguments requis est toujours trompeur.
Acumenus

Réponses:

316

Les paramètres commençant par -ou --sont généralement considérés comme facultatifs. Tous les autres paramètres sont des paramètres positionnels et en tant que tels requis par la conception (comme les arguments des fonctions positionnelles). Il est possible d'exiger des arguments facultatifs, mais c'est un peu contraire à leur conception. Puisqu'ils font toujours partie des arguments non positionnels, ils seront toujours répertoriés sous l'en-tête déroutant «arguments facultatifs» même s'ils sont requis. Les crochets manquants dans la partie utilisation montrent cependant qu'ils sont effectivement nécessaires.

Voir aussi la documentation :

En général, le module argparse suppose que des indicateurs comme -f et --bar indiquent des arguments facultatifs, qui peuvent toujours être omis sur la ligne de commande.

Remarque: Les options requises sont généralement considérées comme de mauvaise forme car les utilisateurs s'attendent à ce que les options soient facultatives, et elles doivent donc être évitées dans la mesure du possible.

Cela étant dit, les en-têtes «arguments positionnels» et «arguments facultatifs» dans l'aide sont générés par deux groupes d'arguments dans lesquels les arguments sont automatiquement séparés. Maintenant, vous pouvez "pirater" et changer le nom des options, mais une solution beaucoup plus élégante serait de créer un autre groupe pour "les arguments nommés requis" (ou ce que vous voulez les appeler):

parser = argparse.ArgumentParser(description='Foo')
parser.add_argument('-o', '--output', help='Output file name', default='stdout')
requiredNamed = parser.add_argument_group('required named arguments')
requiredNamed.add_argument('-i', '--input', help='Input file name', required=True)
parser.parse_args(['-h'])
usage: [-h] [-o OUTPUT] -i INPUT

Foo

optional arguments:
  -h, --help            show this help message and exit
  -o OUTPUT, --output OUTPUT
                        Output file name

required named arguments:
  -i INPUT, --input INPUT
                        Input file name
poussée
la source
J'ai eu le même problème. J'ai essayé ta solution. Il ajoute les arguments au nouveau groupe mais mon code ne semble pas fonctionner après cela. Toutes les solutions seraient appréciées. Lien vers mon code - pastebin.com/PvC2aujz
Zarar Mahmud
1
@ZararMahmud: Vous passez des arguments vides à la ligne 24 de votre code: parser.parse_args([])utilisez plutôt parser.parse_args()sans arguments pour capturer le contenu de sys.argv. Per argparse
Devin
@poke: Belle solution! Mais cela n'aide pas si vous avez besoin de groupes exclusifs mutuels, ou si je manque quelque chose?
Juge
@Judge je recommanderais de lire ce pymotw.com/3/argparse/#mutually-exclusive-options
Peter Moore
79

Comme je préfère lister les arguments requis avant facultatif, je le pirate via:

    parser = argparse.ArgumentParser()
    parser._action_groups.pop()
    required = parser.add_argument_group('required arguments')
    optional = parser.add_argument_group('optional arguments')
    required.add_argument('--required_arg', required=True)
    optional.add_argument('--optional_arg')
    return parser.parse_args()

et cela donne:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
               [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  --optional_arg OPTIONAL_ARG

Je peux vivre sans que «l'aide» apparaisse dans le groupe d'arguments optionnels.

Karl Rosaen
la source
3
Cela oblige-t-il réellement argparse à traiter l'un des arguments comme requis?
Anthony
6
Je pense que l'argument «requis» doit encore être défini lors de l'ajout d'un argument.
Karl Rosaen
C'est vraiment sympathique.
Paul Cezanne
7
@Anthony - non, vous avez besoin du 'required = True' dans add_argument pour cela. La réponse ci-dessus illustre simplement le regroupement d'arguments.
user2275693
47

Construire hors de @Karl Rosaen

parser = argparse.ArgumentParser()
optional = parser._action_groups.pop() # Edited this line
required = parser.add_argument_group('required arguments')
# remove this line: optional = parser...
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')
parser._action_groups.append(optional) # added this line
return parser.parse_args()

et cela donne:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
RalphyZ
la source
1
BTW, existe-t-il des moyens (méthodes) pour accéder à _action_groupsans accéder au membre protégé? Dans mon cas, je dois ajouter un argument à un groupe (personnalisé) déjà existant.
machin
C'est bien. Résout l'élément --help apparaissant dans une deuxième liste facultative.
Jeremy
Remarque : cette réponse casse l'API exposée, vérifiez la réponse de Bryan_D ci-dessous.
lol
18

Encore une fois, en partant de @RalphyZ

Celui-ci ne casse pas l'API exposée.

from argparse import ArgumentParser, SUPPRESS
# Disable default help
parser = ArgumentParser(add_help=False)
required = parser.add_argument_group('required arguments')
optional = parser.add_argument_group('optional arguments')

# Add back help 
optional.add_argument(
    '-h',
    '--help',
    action='help',
    default=SUPPRESS,
    help='show this help message and exit'
)
required.add_argument('--required_arg', required=True)
optional.add_argument('--optional_arg')

Qui affichera la même chose que ci-dessus et devrait survivre aux futures versions:

usage: main.py [-h] [--required_arg REQUIRED_ARG]
           [--optional_arg OPTIONAL_ARG]

required arguments:
  --required_arg REQUIRED_ARG

optional arguments:
  -h, --help                    show this help message and exit
  --optional_arg OPTIONAL_ARG
Bryan_D
la source
Pouvez-vous expliquer comment la réponse de RalphyZ brise l'API exposée?
jeremysprofile
5
_action_groupsest destiné à un usage interne uniquement. Par conséquent, il n'y a aucune garantie de compatibilité entre les versions.
Bryan_D