Argparse Python: Faire au moins un argument requis


92

J'utilise argparsepour un programme Python qui peut -process, -uploadou les deux:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Le programme n'a pas de sens sans au moins un paramètre. Comment puis-je configurer argparsepour forcer au moins un paramètre à être choisi?

METTRE À JOUR:

Suite aux commentaires: Quelle est la manière pythonique de paramétrer un programme avec au moins une option?


9
-xest universellement un drapeau et facultatif. Coupez le -si nécessaire.

1
Ne pourriez-vous pas faire processle comportement par défaut (sans avoir besoin de spécifier aucune option) et permettre à l'utilisateur de le changer uploadsi cette option est définie? Habituellement, les options doivent être facultatives, d'où le nom. Les options obligatoires doivent être évitées (cela se trouve également dans la argparse documentation).
Tim Pietzcker

@AdamMatan Cela fait presque trois ans que vous avez posé votre question mais j'ai aimé le défi qui s'y cache et j'ai profité de la disponibilité de nouvelles solutions pour ce genre de tâches.
Jan Vlcinsky

Réponses:


107
if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')

1
C'est probablement le seul moyen, s'il argparsen'y a pas d'option intégrée pour cela.
Adam Matan

28
args = vars(parser.parse_args())
if not any(args.values()):
    parser.error('No arguments provided.')

3
+1 pour une solution généralisée. Tout comme l'utilisation de vars(), qui est également utile pour passer des options soigneusement nommées à un constructeur avec **.
Lenna

C'est exactement ce que je fais avec. Merci!
brentlance

1
Dang, j'aime ça vars. Je viens de le faire .__dict__et je me suis senti stupide avant.
Theo Belaire

1
bonnes réponses. Les deux "vars" et "any" étaient nouveaux pour moi :-)
Vivek Jha

21

Si ce n'est pas la partie `` ou les deux '' (j'ai initialement manqué ceci), vous pouvez utiliser quelque chose comme ceci:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
    parser.error("One of --process or --upload must be given")

Cependant, ce serait probablement une meilleure idée d'utiliser des sous-commandes à la place.


4
Je pense qu'il veut autoriser --processOU --upload, pas XOR. Cela empêche les deux options d'être définies en même temps.
phihag

+1 parce que vous avez mentionné des sous-commandes. Pourtant - comme quelqu'un l'a souligné dans les commentaires -xet --xxxsont généralement des paramètres optionnels.
mac

20

Je sais que c'est vieux comme de la saleté, mais la façon d'exiger une option mais d'en interdire plus d'une (XOR) est comme ceci:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()
print args

Production:

>opt.py  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: one of the arguments -process -upload is required  

>opt.py -upload  
Namespace(process=False, upload=True)  

>opt.py -process  
Namespace(process=True, upload=False)  

>opt.py -upload -process  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: argument -process: not allowed with argument -upload  

3
Malheureusement, l'OP ne veut pas de XOR. C'est l'un ou les deux, mais pas aucun, donc votre dernier scénario de test ne répond pas à leurs exigences.
kdopen

2
@kdopen: le répondant a précisé qu'il s'agit d'une variante de la question initiale, que j'ai trouvée utile: "la façon d'exiger une option mais d'en interdire plus d'une" Peut-être que l'étiquette de Stack Exchange appellerait plutôt une nouvelle question . Mais avoir cette réponse ici m'a aidé ...
erik.weathers

2
Ce message ne répond pas à la question initiale
Marc

2
Comment cela répond-il à la question «au moins un»?
xaxxon

2
Malheureusement, l'OP ne veut pas de XOR.
duckman_1991

8

Examen des exigences

  • utiliser argparse(je vais ignorer celui-ci)
  • permettent d'appeler une ou deux actions (au moins une requise).
  • essayez par Pythonic (je l'appellerais plutôt "POSIX")

Il existe également des exigences implicites lorsque l'on vit en ligne de commande:

  • expliquer l'utilisation à l'utilisateur d'une manière qui est facile à comprendre
  • les options doivent être facultatives
  • permettre de spécifier des indicateurs et des options
  • permettent la combinaison avec d'autres paramètres (comme le nom ou les noms de fichier).

Exemple de solution utilisant docopt(fichier managelog.py):

"""Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Essayez de l'exécuter:

$ python managelog.py
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Montrez l'aide:

$ python managelog.py -h
Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>

Et utilisez-le:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
 '--pswd': 'secret',
 '--user': 'user',
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': False,
 'upload': True}

Alternative courte short.py

Il peut y avoir une variante encore plus courte:

"""Manage logfiles
Usage:
    short.py [options] (process|upload)... -- <logfile>...
    short.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

L'utilisation ressemble à ceci:

$ python short.py -V process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 1,
 'upload': 1}

Notez qu'au lieu de valeurs booléennes pour les clés "process" et "upload", il y a des compteurs.

Il s'avère que nous ne pouvons pas empêcher la duplication de ces mots:

$ python short.py -V process process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 2,
 'upload': 1}

Conclusions

Concevoir une bonne interface de ligne de commande peut parfois être difficile.

Il existe plusieurs aspects du programme basé sur la ligne de commande:

  • bonne conception de la ligne de commande
  • sélectionner / utiliser un analyseur approprié

argparse offre beaucoup, mais restreint les scénarios possibles et peut devenir très complexe.

Avec les docoptchoses vont beaucoup plus court tout en préservant la lisibilité et en offrant un haut degré de flexibilité. Si vous parvenez à obtenir des arguments analysés à partir du dictionnaire et à effectuer certaines conversions (en entiers, en ouvrant des fichiers ..) manuellement (ou par une autre bibliothèque appelée schema), vous pouvez trouver la docoptbonne solution pour l'analyse en ligne de commande.


Jamais entendu parler de docopt, bonne suggestion!
Ton van den Heuvel

@TonvandenHeuvel Bon. Je veux juste confirmer, je l'utilise toujours comme solution préférée pour les interfaces de ligne de commande.
Jan Vlcinsky

Meilleure réponse evar, merci pour les exemples détaillés.
jnovack

5

Si vous avez besoin d'un programme python pour s'exécuter avec au moins un paramètre, ajoutez un argument qui n'a pas le préfixe d'option (- ou - par défaut) et défini nargs=+(Minimum d'un argument requis). Le problème avec cette méthode que j'ai trouvé est que si vous ne spécifiez pas l'argument, argparse générera une erreur "trop ​​peu d'arguments" et n'imprimera pas le menu d'aide. Si vous n'avez pas besoin de cette fonctionnalité, voici comment le faire dans le code:

import argparse

parser = argparse.ArgumentParser(description='Your program description')
parser.add_argument('command', nargs="+", help='describe what a command is')
args = parser.parse_args()

Je pense que lorsque vous ajoutez un argument avec les préfixes d'option, nargs régit l'ensemble de l'analyseur d'arguments et pas seulement l'option. (Ce que je veux dire, c'est que si vous avez un --optionindicateur avec nargs="+", alors --optionflag attend au moins un argument. Si vous avez optionavec nargs="+", il attend au moins un argument global.)


Vous pourriez ajouter choices=['process','upload']à cet argument.
hpaulj

5

Pour http://bugs.python.org/issue11588 , j'explore des moyens de généralisermutually_exclusive_group concept pour gérer des cas comme celui-ci.

Avec ce développement argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py je suis capable d'écrire:

parser = argparse.ArgumentParser(prog='PROG', 
    description='Log archiver arguments.')
group = parser.add_usage_group(kind='any', required=True,
    title='possible actions (at least one is required)')
group.add_argument('-p', '--process', action='store_true')
group.add_argument('-u', '--upload',  action='store_true')
args = parser.parse_args()
print(args)

qui produit ce qui suit help:

usage: PROG [-h] (-p | -u)

Log archiver arguments.

optional arguments:
  -h, --help     show this help message and exit

possible actions (at least one is required):
  -p, --process
  -u, --upload

Cela accepte les entrées comme '-u', '-up', '--proc --up' etc.

Il finit par exécuter un test similaire à https://stackoverflow.com/a/6723066/901925 , bien que le message d'erreur doive être plus clair:

usage: PROG [-h] (-p | -u)
PROG: error: some of the arguments process upload is required

Je me demande:

  • les paramètres sont-ils kind='any', required=Truesuffisamment clairs (acceptez l'un des groupes; au moins un est requis)?

  • l'utilisation est-elle (-p | -u)claire? Un groupe mutuellement_exclusif requis produit la même chose. Existe-t-il une notation alternative?

  • L'utilisation d'un groupe comme celui-ci est-elle plus intuitive qu'un phihag'ssimple test?


Je ne trouve aucune mention add_usage_groupsur cette page: docs.python.org/2/library/argparse.html ; pouvez-vous fournir un lien vers la documentation correspondante?
P. Myer Nore

@ P.MyerNore, j'ai fourni un lien - au début de cette réponse. Cela n'a pas été mis en production.
hpaulj

5

La meilleure façon de le faire est d'utiliser le module intégré python add_mutually_exclusive_group .

parser = argparse.ArgumentParser(description='Log archiver arguments.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Si vous voulez qu'un seul argument soit sélectionné par la ligne de commande, utilisez simplement required = True comme argument pour le groupe

group = parser.add_mutually_exclusive_group(required=True)

2
Comment cela vous donne-t-il "au moins un" - n'est-ce pas "exactement un"?
xaxxon

3
Malheureusement, l'OP ne veut pas de XOR. OP cherche OR
duckman_1991

Cela n'a pas répondu à la question d'OP, mais cela a répondu à la mienne alors merci quand même ¯_ (ツ) _ / ¯
rosstex

2

Peut-être utiliser des sous-analyseurs?

import argparse

parser = argparse.ArgumentParser(description='Log archiver arguments.')
subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
parser_process = subparsers.add_parser('process', help='Process logs')
parser_upload = subparsers.add_parser('upload', help='Upload logs')
args = parser.parse_args()

print("Subparser: ", args.subparser_name)

--helpMontre maintenant :

$ python /tmp/aaa.py --help
usage: aaa.py [-h] {process,upload} ...

Log archiver arguments.

positional arguments:
  {process,upload}  sub-command help
    process         Process logs
    upload          Upload logs

optional arguments:
  -h, --help        show this help message and exit
$ python /tmp/aaa.py
usage: aaa.py [-h] {process,upload} ...
aaa.py: error: too few arguments
$ python3 /tmp/aaa.py upload
Subparser:  upload

Vous pouvez également ajouter des options supplémentaires à ces sous-analyseurs. Au lieu d'utiliser cela, dest='subparser_name'vous pouvez également lier des fonctions à appeler directement sur une sous-commande donnée (voir la documentation).


2

Cela atteint le but et cela sera également corrigé dans la --helpsortie générée automatiquement par argparse , qui est à mon avis ce que la plupart des programmeurs sensés veulent (fonctionne également avec des arguments optionnels):

parser.add_argument(
    'commands',
    nargs='+',                      # require at least 1
    choices=['process', 'upload'],  # restrict the choice
    help='commands to execute'
)

Documents officiels à ce sujet: https://docs.python.org/3/library/argparse.html#choices


1

Utilisez append_const pour une liste d'actions, puis vérifiez que la liste est remplie:

parser.add_argument('-process', dest=actions, const="process", action='append_const')
parser.add_argument('-upload',  dest=actions, const="upload", action='append_const')

args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

Vous pouvez même spécifier les méthodes directement dans les constantes.

def upload:
    ...

parser.add_argument('-upload',  dest=actions, const=upload, action='append_const')
args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

else:
    for action in args.actions:
        action()
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.