J'ai écrit l'exemple de code suivant pour illustrer mon problème.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-v', '--version', action='version',
                    version='%(prog)s 1.0')
parser.parse_args()

Cela produit le message d'aide suivant.

$ python foo.py --help
usage: foo.py [-h] [-v]

optional arguments:
  -h, --help     show this help message and exit
  -v, --version  show program's version number and exit

Je souhaite personnaliser cette sortie d'aide de manière à ce qu'elle capitalise toutes les phrases et phrases, et mette point après les phrases. En d'autres termes, je souhaite que le message d'aide soit généré comme ceci.

$ python foo.py --help
Usage: foo.py [-h] [-v]

Optional arguments:
  -h, --help     Show this help message and exit.
  -v, --version  Show program's version number and exit.

Est-ce quelque chose que je peux contrôler en utilisant l'API argparse. Si c'est le cas, comment? Pourriez-vous s'il vous plaît fournir un petit exemple qui montre comment cela peut être fait?

29
Lone Learner 7 mars 2016 à 18:06

3 réponses

Meilleure réponse

Tout d'abord: mettre ces phrases en majuscule va à l'encontre des conventions, et argparse n'est pas vraiment outillé pour vous aider à changer ces chaînes facilement. Vous avez trois classes différentes de chaînes ici: le texte standard du formateur d'aide, les titres de section et le texte d'aide par option spécifique. Toutes ces chaînes sont localisables; vous pourriez simplement fournir une traduction 'en majuscule' pour toutes ces chaînes via le gettext() prise en charge du module. Cela dit, vous pouvez accéder à toutes ces chaînes et les remplacer si vous êtes suffisamment déterminé et lisez un peu le code source.

L'action version inclut un texte help par défaut, mais vous pouvez fournir le vôtre en définissant l'argument help. La même chose s'applique à l'action help; si vous définissez l'argument add_help sur {{ X5}} vous pouvez ajouter cette action manuellement:

parser = argparse.ArgumentParser(add_help=False)

parser.add_argument('-v', '--version', action='version',
                    version='%(prog)s 1.0', help="Show program's version number and exit.")
parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
                    help='Show this help message and exit.')

Ensuite, le message optional arguments est un titre de groupe ; chaque analyseur a deux groupes par défaut, l'un pour les arguments positionnels, l'autre pour les arguments optionnels. Vous pouvez les atteindre par les attributs _positionals et _optionals, qui ont tous deux un attribut title:

parser._positionals.title = 'Positional arguments'
parser._optionals.title = 'Optional arguments'

Soyez averti , en accédant aux noms commençant par un trait de soulignement, vous vous aventurez dans l'API privée non documentée du module, et votre code risque de casser dans les futures mises à jour.

Enfin, pour changer la chaîne usage, vous devrez sous-classer le formateur d'aide; transmettez la sous-classe en tant qu'argument formatter_class:

class CapitalisedHelpFormatter(argparse.HelpFormatter):
    def add_usage(self, usage, actions, groups, prefix=None):
        if prefix is None:
            prefix = 'Usage: '
        return super(CapitalisedHelpFormatter, self).add_usage(
            usage, actions, groups, prefix)

parser = argparse.ArgumentParser(formatter_class=CapitalisedHelpFormatter)

Démo, rassemblant tous ces éléments:

>>> import argparse
>>> class CapitalisedHelpFormatter(argparse.HelpFormatter):
...     def add_usage(self, usage, actions, groups, prefix=None):
...         if prefix is None:
...             prefix = 'Usage: '
...         return super(CapitalisedHelpFormatter, self).add_usage(
...             usage, actions, groups, prefix)
...
>>> parser = argparse.ArgumentParser(add_help=False, formatter_class=CapitalisedHelpFormatter)
>>> parser._positionals.title = 'Positional arguments'
>>> parser._optionals.title = 'Optional arguments'
>>> parser.add_argument('-v', '--version', action='version',
...                     version='%(prog)s 1.0', help="Show program's version number and exit.")
_VersionAction(option_strings=['-v', '--version'], dest='version', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help="Show program's version number and exit.", metavar=None)
>>> parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
...                     help='Show this help message and exit.')
_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='Show this help message and exit.', metavar=None)
>>> print(parser.format_help())
Usage: [-v] [-h]

Optional arguments:
  -v, --version  Show program's version number and exit.
  -h, --help     Show this help message and exit.
39
Martijn Pieters 7 mars 2016 à 16:28

Martijn a donné quelques corrections qui lui sont venues à l'esprit - la fourniture du paramètre help et une classe Formatter personnalisée.

Une autre solution partielle consiste à modifier la chaîne d'aide après la création de l'argument. add_argument crée et renvoie un objet Action contenant les paramètres et les valeurs par défaut. Vous pouvez enregistrer un lien vers celui-ci et modifier le Action. Vous pouvez également obtenir une liste de ces actions et agir en conséquence.

Permettez-moi d'illustrer, pour un analyseur simple avec l'aide par défaut et un autre argument, la liste d'actions est:

In [1064]: parser._actions
Out[1064]: 
[_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None),
 _StoreAction(option_strings=['-f', '--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)]

Je peux afficher et modifier l'attribut help de l'un de ces éléments:

In [1065]: parser._actions[0].help
Out[1065]: 'show this help message and exit'
In [1066]: parser._actions[0].help='Show this help message and exit.'

Produire cette aide:

In [1067]: parser.parse_args(['-h'])
usage: ipython3 [-h] [-f FOO]    
optional arguments:
  -h, --help         Show this help message and exit.
  -f FOO, --foo FOO

L'utilisation de la liste parser._actions utilise un attribut "privé", que certaines personnes jugent imprudent. Mais en Python, cette distinction public / privé n'est pas étroite et peut être rompue avec précaution. Martijn le fait en accédant au parser._positionals.title.

Une autre façon de modifier ce titre de groupe consiste à utiliser des groupes d'arguments personnalisés

ogroup=parser.add_argument_group('Correct Optionals Title')
ogroup.add_argument('-v',...)
ogroup.add_argument('-h',...)
4
hpaulj 7 mars 2016 à 17:04

Au lieu de compter sur l'API interne, qui est sujette à changement sans préavis, voici une alternative utilisant uniquement l'API publique. C'est sans doute plus complexe mais vous donne à son tour un contrôle maximal sur ce qui est imprimé:

class ArgumentParser(argparse.ArgumentParser):

    def __init__(self, *args, **kwargs):
        super(ArgumentParser, self).__init__(*args, **kwargs)
        self.program = { key: kwargs[key] for key in kwargs }
        self.options = []

    def add_argument(self, *args, **kwargs):
        super(ArgumentParser, self).add_argument(*args, **kwargs)
        option = {}
        option["flags"] = [ item for item in args ]
        for key in kwargs:
            option[key] = kwargs[key]
        self.options.append(option)

    def print_help(self):
        # use self.program/self.options to produce custom help text

Comment ça fonctionne:

  • appuyez sur le constructeur de argparse.ArgumentParser pour capturer et stocker des informations sur le programme (par exemple, description, utilisation)
  • appuyez sur argparse.ArgumentParser.add_argument() pour capturer et stocker les arguments ajoutés (par exemple, indicateurs, aide, valeurs par défaut)
  • redéfinir argparse.ArgumentParser.print_help() et utiliser les informations / arguments de programme précédemment stockés pour produire du texte d'aide

Voici un exemple complet couvrant certains cas d'utilisation courants. Notez qu'il n'est en aucun cas complet (par exemple, il n'y a pas de support pour les arguments de position ou les options avec plus d'un argument), mais devrait donner une impression de ce qui est possible:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import argparse
import textwrap

class ArgumentParser(argparse.ArgumentParser):

    def __init__(self, *args, **kwargs):
        super(ArgumentParser, self).__init__(*args, **kwargs)
        self.program = { key: kwargs[key] for key in kwargs }
        self.options = []

    def add_argument(self, *args, **kwargs):
        super(ArgumentParser, self).add_argument(*args, **kwargs)
        option = {}
        option["flags"] = [ item for item in args ]
        for key in kwargs:
            option[key] = kwargs[key]
        self.options.append(option)

    def print_help(self):
        wrapper = textwrap.TextWrapper(width=80)

        # Print usage
        if "usage" in self.program:
            print("Usage: %s" % self.program["usage"])
        else:
            usage = []
            for option in self.options:
                usage += [ "[%s %s]" % (item, option["metavar"]) if "metavar" in option else "[%s %s]" % (item, option["dest"].upper()) if "dest" in option else "[%s]" % item for item in option["flags"] ]
            wrapper.initial_indent = "Usage: %s " % os.path.basename(sys.argv[0])
            wrapper.subsequent_indent = len(wrapper.initial_indent) * " "
            output = str.join(" ", usage)
            output = wrapper.fill(output)
            print(output)
        print()

        # Print description
        if "description" in self.program:
            print(self.program["description"])
            print()

        # Print options
        print("Options:")
        maxlen = 0
        for option in self.options:
            option["flags2"] = str.join(", ", [ "%s %s" % (item, option["metavar"]) if "metavar" in option else "%s %s" % (item, option["dest"].upper()) if "dest" in option else item for item in option["flags"] ])
            if len(option["flags2"]) > maxlen:
                maxlen = len(option["flags2"])
        for option in self.options:
            template = "  %-" + str(maxlen) + "s  "
            wrapper.initial_indent = template % option["flags2"]
            wrapper.subsequent_indent = len(wrapper.initial_indent) * " "
            if "help" in option and "default" in option:
                output = option["help"]
                output += " (default: '%s')" % option["default"] if isinstance(option["default"], str) else " (default: %s)" % str(option["default"])
                output = wrapper.fill(output)
            elif "help" in option:
                output = option["help"]
                output = wrapper.fill(output)
            elif "default" in option:
                output = "Default: '%s'" % option["default"] if isinstance(option["default"], str) else "Default: %s" % str(option["default"])
                output = wrapper.fill(output)
            else:
                output = wrapper.initial_indent
            print(output)

# Main
if (__name__ == "__main__"):
    #parser = argparse.ArgumentParser(description="Download program based on some library.", argument_default=argparse.SUPPRESS, allow_abbrev=False, add_help=False)
    #parser = argparse.ArgumentParser(usage="%s [OPTION]..." % os.path.basename(sys.argv[0]), description="Download program based on some library.", argument_default=argparse.SUPPRESS, allow_abbrev=False, add_help=False)
    #parser = ArgumentParser(usage="%s [OPTION]..." % os.path.basename(sys.argv[0]), description="Download program based on some library.", argument_default=argparse.SUPPRESS, allow_abbrev=False, add_help=False)
    parser = ArgumentParser(description="Download program based on some library.", argument_default=argparse.SUPPRESS, allow_abbrev=False, add_help=False)

    parser.add_argument("-c", "--config-file", action="store", dest="config_file", metavar="file", type=str, default="config.ini")
    parser.add_argument("-d", "--database-file", action="store", dest="database_file", metavar="file", type=str, help="SQLite3 database file to read/write", default="database.db")
    parser.add_argument("-l", "--log-file", action="store", dest="log_file", metavar="file", type=str, help="File to write log to", default="debug.log")
    parser.add_argument("-f", "--data-file", action="store", dest="data_file", metavar="file", type=str, help="Data file to read", default="data.bin")
    parser.add_argument("-t", "--threads", action="store", dest="threads", type=int, help="Number of threads to spawn", default=3)
    parser.add_argument("-p", "--port", action="store", dest="port", type=int, help="TCP port to listen on for access to the web interface", default="12345")
    parser.add_argument("--max-downloads", action="store", dest="max_downloads", metavar="value", type=int, help="Maximum number of concurrent downloads", default=5)
    parser.add_argument("--download-timeout", action="store", dest="download_timeout", metavar="value", type=int, help="Download timeout in seconds", default=120)
    parser.add_argument("--max-requests", action="store", dest="max_requests", metavar="value", type=int, help="Maximum number of concurrent requests", default=10)
    parser.add_argument("--request-timeout", action="store", dest="request_timeout", metavar="value", type=int, help="Request timeout in seconds", default=60)
    parser.add_argument("--main-interval", action="store", dest="main_interval", metavar="value", type=int, help="Main loop interval in seconds", default=60)
    parser.add_argument("--thread-interval", action="store", dest="thread_interval", metavar="value", type=int, help="Thread loop interval in milliseconds", default=500)
    parser.add_argument("--console-output", action="store", dest="console_output", metavar="value", type=str.lower, choices=["stdout", "stderr"], help="Output to use for console", default="stdout")
    parser.add_argument("--console-level", action="store", dest="console_level", metavar="value", type=str.lower, choices=["debug", "info", "warning", "error", "critical"], help="Log level to use for console", default="info")
    parser.add_argument("--logfile-level", action="store", dest="logfile_level", metavar="value", type=str.lower, choices=["debug", "info", "warning", "error", "critical"], help="Log level to use for log file", default="info")
    parser.add_argument("--console-color", action="store", dest="console_color", metavar="value", type=bool, help="Colorized console output", default=True)
    parser.add_argument("--logfile-color", action="store", dest="logfile_color", metavar="value", type=bool, help="Colorized log file output", default=False)
    parser.add_argument("--log-template", action="store", dest="log_template", metavar="value", type=str, help="Template to use for log lines", default="[%(created)d] [%(threadName)s] [%(levelname)s] %(message)s")
    parser.add_argument("-h", "--help", action="help", help="Display this message")

    args = parser.parse_args(["-h"])

Sortie produite:

Usage: argparse_custom_usage.py [-c file] [--config-file file] [-d file]
                                [--database-file file] [-l file] [--log-file
                                file] [-f file] [--data-file file] [-t THREADS]
                                [--threads THREADS] [-p PORT] [--port PORT]
                                [--max-downloads value] [--download-timeout
                                value] [--max-requests value] [--request-timeout
                                value] [--main-interval value] [--thread-
                                interval value] [--console-output value]
                                [--console-level value] [--logfile-level value]
                                [--console-color value] [--logfile-color value]
                                [--log-template value] [-h] [--help]

Download program based on some library.

Options:
  -c file, --config-file file    Default: 'config.ini'
  -d file, --database-file file  SQLite3 database file to read/write (default:
                                 'database.db')
  -l file, --log-file file       File to write log to (default: 'debug.log')
  -f file, --data-file file      Data file to read (default: 'data.bin')
  -t THREADS, --threads THREADS  Number of threads to spawn (default: 3)
  -p PORT, --port PORT           TCP port to listen on for access to the web
                                 interface (default: '12345')
  --max-downloads value          Maximum number of concurrent downloads
                                 (default: 5)
  --download-timeout value       Download timeout in seconds (default: 120)
  --max-requests value           Maximum number of concurrent requests (default:
                                 10)
  --request-timeout value        Request timeout in seconds (default: 60)
  --main-interval value          Main loop interval in seconds (default: 60)
  --thread-interval value        Thread loop interval in milliseconds (default:
                                 500)
  --console-output value         Output to use for console (default: 'stdout')
  --console-level value          Log level to use for console (default: 'info')
  --logfile-level value          Log level to use for log file (default: 'info')
  --console-color value          Colorized console output (default: True)
  --logfile-color value          Colorized log file output (default: False)
  --log-template value           Template to use for log lines (default:
                                 '[%(created)d] [%(threadName)s] [%(levelname)s]
                                 %(message)s')
  -h, --help                     Display this message
1
Maxxim 5 avril 2020 à 08:37