web-dev-qa-db-fra.com

Python argparse: Beaucoup de choix se traduisent par une sortie d'aide moche

J'ai ce code dont je suis généralement satisfait:

import argparse

servers = [ "ApaServer", "BananServer", "GulServer", "SolServer", "RymdServer",
            "SkeppServer", "HavsServer", "PiratServer", "SvartServer", "NattServer", "SovServer" ]

parser = argparse.ArgumentParser(description="A program to update components on servers.")
group = parser.add_mutually_exclusive_group()
group.add_argument('-l', '--list', dest="update", action='store_false', default=False, help='list server components')
group.add_argument('-u', '--updatepom', dest="update", action='store_true', help='update server components')
parser.add_argument('-o', '--only', nargs='*', choices=servers, help='Space separated list of case sensitive server names to process')
parser.add_argument('-s', '--skip', nargs='*', choices=servers, help='Space separated list of case sensitive server names to exclude from processing')
args = parser.parse_args()

J'aime que le choix = serveurs valide les noms de serveur dans l'entrée pour moi, donc je n'ai pas à le faire. Cependant, avoir autant de choix valides rend la sortie de l'aide terrible:

usage: args.py [-h] [-l | -u]
               [-o [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]]]
               [-s [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]]]

A program to update components on servers.

optional arguments:
  -h, --help            show this help message and exit
  -l, --list            list server components
  -u, --updatepom       update server components
  -o [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]], --only [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]]
                        Space separated list of case sensitive server names to
                        process
  -s [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]], --skip [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} [{ApaServer,BananServer,GulServer,SolServer,RymdServer,SkeppServer,HavsServer,PiratServer,SvartServer,NattServer,SovServer} ...]]
                        Space separated list of case sensitive server names to
                        exclude from processing

Quelle voie recommanderiez-vous si je veux:

  • Belle (principalement) sortie d'aide générée automatiquement
  • Validation que les entrées données aux options -o ou -s sont dans servers.

Prime:

  • Serait-il possible d'avoir une correspondance de chaîne insensible à la casse pour les noms de serveur?

Ajouter

J'ai essayé d'utiliser la suggestion michaelfilms où le -o-s les options sont supprimées de la sortie ci-dessus et cette partie est ajoutée:

server optional arguments:
  Valid server names are: ApaServer, BananServer, GulServer, SolServer,
  RymdServer, SkeppServer, HavsServer, PiratServer, SvartServer,
  NattServer, SovServer

Je pense que ça a l'air plutôt bien, mais j'ai vraiment besoin d'aide pour -o et -s options car l'utilisateur ne les connaîtrait pas autrement. Je ne suis donc pas encore tout à fait là-bas en utilisant cette approche.

45
Deleted

Je répète essentiellement ce qu'Ernest a dit - pour éviter la longue liste de choix, définissez metavar = '' pour les arguments basés sur les choix (bien qu'il ne supprime pas l'espace entre l'argument et la virgule (par exemple -o , au lieu de -o,). Vous pouvez ensuite décrire les choix disponibles en détail dans une description générale (RawDescriptionHelpFormatter est utile ici si vous souhaitez les répertorier avec une indentation évidente).

Je ne comprends pas pourquoi la réponse d'Ernest a été rejetée. Ce code

import argparse

servers = [ "ApaServer", "BananServer", "GulServer", "SolServer", "RymdServer",
            "SkeppServer", "HavsServer", "PiratServer", "SvartServer", "NattServer", "SovServer" ]

parser = argparse.ArgumentParser(description="A program to update components on servers.")
group = parser.add_mutually_exclusive_group()
group.add_argument('-l', '--list', dest="update", action='store_false', default=False, help='list server components')
group.add_argument('-u', '--updatepom', dest="update", action='store_true', help='update server components')
parser.add_argument('-o', '--only', choices=servers, help='Space separated list of case sensitive server names to process.  Allowed values are '+', '.join(servers), metavar='')
parser.add_argument('-s', '--skip', choices=servers, help='Space separated list of case sensitive server names to exclude from processing.  Allowed values are '+', '.join(servers), metavar='')
args = parser.parse_args()

produit la sortie d'aide suivante

usage: run.py [-h] [-l | -u] [-o] [-s]

A program to update components on servers.

optional arguments:
  -h, --help       show this help message and exit
  -l, --list       list server components
  -u, --updatepom  update server components
  -o , --only      Space separated list of case sensitive server names to
                   process. Allowed values are ApaServer, BananServer,
                   GulServer, SolServer, RymdServer, SkeppServer, HavsServer,
                   PiratServer, SvartServer, NattServer, SovServer
  -s , --skip      Space separated list of case sensitive server names to
                   exclude from processing. Allowed values are ApaServer,
                   BananServer, GulServer, SolServer, RymdServer, SkeppServer,
                   HavsServer, PiratServer, SvartServer, NattServer, SovServer

J'espère que c'est ce que le message original recherchait.

46
user2463717

Il n'est pas nécessaire de sous-classer quoi que ce soit. Passez simplement un argument metavar avec la chaîne que vous souhaitez voir apparaître dans le message d'aide.

Voir documentation argparse pour plus de détails.

23
Ernest A

J'ai ce même problème, et comme solution de contournement, j'ai utilisé l'épilogue pour décrire chacun des choix d'options. J'ai dû utiliser argparse.RawTextHelpFormatter, qui vous permet de spécifier que l'épilogue est pré-formaté.

def choicesDescriptions():
   return """
Choices supports the following: 
   choice1         - the FIRST option
   choice2         - the SECOND option
   ...
   choiceN         - the Nth option
"""

def getChoices():
   return ["choice1", "choice2", ..., "choiceN"]

parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, epilog=choicesDescriptions())
parser.add_argument(
   'choices', 
   choices=getChoices(),
   help='Arg choice.  See the choices options below'
   )

args = parser.parse_args()
print(args)
9
Mike

Cela n'aidera pas dans les situations où la liste des options est extrêmement longue, comme dans la question d'origine, mais pour les personnes qui, comme moi, sont tombées sur cette question à la recherche d'un moyen de briser les chaînes d'options modérément longues en deux lignes, voici mon Solution:

import argparse

class CustomFormatter(argparse.HelpFormatter):
    """Custom formatter for setting argparse formatter_class. Identical to the
    default formatter, except that very long option strings are split into two
    lines.
    """

    def _format_action_invocation(self, action):
        if not action.option_strings:
            metavar, = self._metavar_formatter(action, action.dest)(1)
            return metavar
        else:
            parts = []
            # if the Optional doesn't take a value, format is:
            #    -s, --long
            if action.nargs == 0:
                parts.extend(action.option_strings)
            # if the Optional takes a value, format is:
            #    -s ARGS, --long ARGS
            else:
                default = action.dest.upper()
                args_string = self._format_args(action, default)
                for option_string in action.option_strings:
                    parts.append('%s %s' % (option_string, args_string))
            if sum(len(s) for s in parts) < self._width - (len(parts) - 1) * 2:
                return ', '.join(parts)
            else:
                return ',\n  '.join(parts)

Ce code remplace la méthode argparse.HelpFormatter par défaut _format_action_invocation et est identique à l'implémentation par défaut, sauf dans les quatre dernières lignes.

Comportement du formateur par défaut:

parser = argparse.ArgumentParser(description="Argparse default formatter.")
parser.add_argument('-a', '--argument', help='not too long')
parser.add_argument('-u', '--ugly', choices=range(20), help='looks messy')
parser.print_help()

sorties:

usage: test.py [-h] [-a ARGUMENT]
               [-u {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}]

Argparse default formatter.

optional arguments:
  -h, --help            show this help message and exit
  -a ARGUMENT, --argument ARGUMENT
                        not too long
  -u {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}, --ugly {0,1,2,3,4,5,6,
7,8,9,10,11,12,13,14,15,16,17,18,19}
                        looks messy

Comportement du formateur personnalisé:

parser = argparse.ArgumentParser(description="Argparse custom formatter.",
                                 formatter_class=CustomFormatter)
parser.add_argument('-a', '--argument', help='not too long')
parser.add_argument('-l', '--less-ugly', choices=range(20), help='less messy')

sorties:

usage: test.py [-h] [-a ARGUMENT]
               [-l {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}]

Argparse custom formatter.

optional arguments:
  -h, --help            show this help message and exit
  -a ARGUMENT, --argument ARGUMENT
                        not too long
  -l {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19},
  --less-ugly {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}
                        less messy
5
vamin

Pour obtenir la sortie attendue, vous devrez sous-classer argparse.HelpFormatter et implémentez la mise en forme dont vous avez besoin. En particulier, vous devrez implémenter votre propre _metavar_formatter méthode, qui est celle chargée de joindre tous les choix en une seule chaîne séparée par des virgules.

4
jcollado

Pourquoi ne pas utiliser parser.add_argument_group pour créer un groupe pour vos options basées sur le serveur et indiquer qu'un argument de description affiche la liste des choix possibles? Ensuite, passez argparse.SUPPRESS dans l'aide pour chacune des options individuelles. Je crois que cela vous donnera ce que vous voulez.

3
michaelfilms

http://bugs.python.org/issue16468argparse only supports iterable choices est le problème des bogues concernant la mise en forme des choix. La liste des choix peut apparaître à 3 endroits: la ligne d'utilisation, les lignes d'aide et les messages d'erreur.

Tout ce qui importe à l'analyseur, c'est de faire un in (__contains__) test. Mais pour le formatage, les listes longues, les `` listes '' non bornées (par exemple des entiers> 100) et d'autres objets qui ne sont pas itérables posent des problèmes. metavar est la façon dont les utilisateurs actuels peuvent contourner la plupart des problèmes de formatage (cela peut ne pas aider avec les messages d'erreur). Examinez le problème pour avoir des idées sur la façon de modifier votre propre version de argparse.

1
hpaulj