Python argparse: come inserire una nuova riga nel testo della guida?


341

Sto usando argparsein Python 2.7 per analizzare le opzioni di input. Una delle mie opzioni è una scelta multipla. Voglio fare un elenco nel suo testo di aiuto, ad es

from argparse import ArgumentParser

parser = ArgumentParser(description='test')

parser.add_argument('-g', choices=['a', 'b', 'g', 'd', 'e'], default='a',
    help="Some option, where\n"
         " a = alpha\n"
         " b = beta\n"
         " g = gamma\n"
         " d = delta\n"
         " e = epsilon")

parser.parse_args()

Tuttavia, argparseelimina tutte le nuove righe e gli spazi consecutivi. Il risultato sembra

~ / Download: 52 $ python2.7 x.py -h
utilizzo: x.py [-h] [-g {a, b, g, d, e}]

test

argomenti opzionali:
  -h, --help mostra questo messaggio di aiuto ed esce
  -g {a, b, g, d, e} Alcune opzioni, dove a = alpha b = beta g = gamma d = delta e
                  = epsilon

Come inserire nuove righe nel testo della guida?


Non ho Python 2.7 con me, quindi posso provare le mie idee. Che ne dici di usare il testo di aiuto tra virgolette ("" "" ""). Le nuove linee sopravvivono usando questo?
pyfunc,

4
@pyfunc: No. Lo stripping viene eseguito in runtime da argparse, non dall'interprete, quindi il passaggio a """..."""non aiuta.
kennytm,

Questo ha funzionato per me
cardamomo il

Risposte:


394

Prova a usare RawTextHelpFormatter:

from argparse import RawTextHelpFormatter
parser = ArgumentParser(description='test', formatter_class=RawTextHelpFormatter)

6
Penso di no. Potresti sottoclassarlo, ma sfortunatamente Only the name of this class is considered a public API. All the methods provided by the class are considered an implementation detail. Quindi probabilmente non è una grande idea, anche se potrebbe non importare, dal momento che 2.7 è pensato per essere l'ultimo pitone 2.x e ti aspetteresti comunque di riformattare molte cose per 3.x. In realtà sto eseguendo 2.6 con argparseinstallato via in easy_installmodo che la documentazione stessa non sia aggiornata.
intuito il

3
Alcuni collegamenti: per Python 2.7 e Python 3. * . Il pacchetto 2.6 dovrebbe, secondo il suo wiki , essere conforme a quello ufficiale 2.7. Dal documento: "Passare RawDescriptionHelpFormatter come formatter_class = indica che la descrizione e l'epilog sono già formattate correttamente e non devono essere racchiuse tra le righe"
Stefano,

83
Prova invece formatter_class = RawDescriptionHelpFormatterche funziona solo con la descrizione ed epilog piuttosto che con il testo di aiuto.
MarkHu,

3
Ho notato che anche con RawTextHelpFormatter, le nuove linee iniziali e finali vengono rimosse. Per ovviare a questo, puoi semplicemente aggiungere due o più newline consecutive; tutti tranne uno newline sopravviveranno.
MrMas

11
Puoi anche combinare i formattatori, ad es. class Formatter( argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): passE poi formatter_class=Formatter.
Terry Brown,

79

Se si desidera solo ignorare un'opzione, non è necessario utilizzare RawTextHelpFormatter. Invece subclassare HelpFormattere fornire un'introduzione speciale per le opzioni che dovrebbero essere gestite "raw" (io uso "R|rest of help"):

import argparse

class SmartFormatter(argparse.HelpFormatter):

    def _split_lines(self, text, width):
        if text.startswith('R|'):
            return text[2:].splitlines()  
        # this is the RawTextHelpFormatter._split_lines
        return argparse.HelpFormatter._split_lines(self, text, width)

E usalo:

from argparse import ArgumentParser

parser = ArgumentParser(description='test', formatter_class=SmartFormatter)

parser.add_argument('-g', choices=['a', 'b', 'g', 'd', 'e'], default='a',
    help="R|Some option, where\n"
         " a = alpha\n"
         " b = beta\n"
         " g = gamma\n"
         " d = delta\n"
         " e = epsilon")

parser.parse_args()

Tutte le altre chiamate a .add_argument()cui non è possibile iniziare la guida R|verranno spostate normalmente.

Questo fa parte dei miei miglioramenti su argparse . Lo SmartFormatter completo supporta anche l'aggiunta delle impostazioni predefinite a tutte le opzioni e l'input non elaborato della descrizione delle utility. La versione completa ha il suo _split_linesmetodo, in modo che qualsiasi formattazione fatta ad es. Stringhe di versione venga preservata:

parser.add_argument('--version', '-v', action="version",
                    version="version...\n   42!")

Voglio farlo per un messaggio di versione, ma questo SmartFormatter sembra funzionare solo con il testo di aiuto, non con il testo della versione speciale. parser.add_argument('-v', '--version', action='version',version=get_version_str()) È possibile estenderlo a quel caso?
mc_electron

@mc_electron anche la versione completa di SmartFormatter ha la sua _split_linese preserva le interruzioni di linea (non è necessario specificare "R |" all'inizio, se si desidera quell'opzione, patchare il _VersionAction.__call__metodo
Anthon,

Non sto interamente facendo trekking sulla prima parte del tuo commento, anche se vedo _VersionAction.__call__che probabilmente lo vorrei solo parser.exit(message=version)invece di usare la versione formattata. Esiste un modo per farlo senza rilasciare una copia patchata di argparse?
mc_electron,

@mc_electron Mi riferisco ai miglioramenti che ho pubblicato su bitbucket (come per il link ai miei miglioramenti su argparse nella risposta). Ma si può anche patchare il __call__nel _VersionActionfacendo argparse._VersionAction.__call__ = smart_versiondopo aver definitodef smart_version(self, parser, namespace, values, option_string=None): ...
Anthon

Grande idea. Non mi ha aiutato poiché l'epilogo e la descrizione non sembrano scorrere attraverso _split_lines :(
Pod

31

Un altro modo semplice per farlo è quello di includere l' involucro di testo .

Per esempio,

import argparse, textwrap
parser = argparse.ArgumentParser(description='some information',
        usage='use "python %(prog)s --help" for more information',
        formatter_class=argparse.RawTextHelpFormatter)

parser.add_argument('--argument', default=somedefault, type=sometype,
        help= textwrap.dedent('''\
        First line
        Second line
        More lines ... '''))

In questo modo, possiamo evitare il lungo spazio vuoto davanti a ciascuna linea di uscita.

usage: use "python your_python_program.py --help" for more information

Prepare input file

optional arguments:
-h, --help            show this help message and exit
--argument ARGUMENT
                      First line
                      Second line
                      More lines ...

11

Ho riscontrato un problema simile (Python 2.7.6). Ho provato a scomporre la sezione della descrizione in più righe usando RawTextHelpFormatter:

parser = ArgumentParser(description="""First paragraph 

                                       Second paragraph

                                       Third paragraph""",  
                                       usage='%(prog)s [OPTIONS]', 
                                       formatter_class=RawTextHelpFormatter)

options = parser.parse_args()

E ottenuto:

utilizzo: play-with-argparse.py [OPZIONI]

Primo paragrafo 

                        Secondo paragrafo

                        Terzo paragrafo

argomenti opzionali:
  -h, --help mostra questo messaggio di aiuto ed esce

Quindi RawTextHelpFormatternon è una soluzione. Perché stampa la descrizione come appare nel codice sorgente, preservando tutti i caratteri degli spazi bianchi (voglio mantenere le schede extra nel mio codice sorgente per la leggibilità, ma non voglio stamparli tutti. Anche il formattatore grezzo non termina la riga quando è troppo lungo, più di 80 caratteri per esempio).

Grazie a @Anton che ha ispirato la giusta direzione sopra . Ma questa soluzione richiede una leggera modifica per formattare la sezione descrittiva .

In ogni caso, è necessario un formattatore personalizzato. Ho esteso la HelpFormatterclasse esistente e ho sostituito il _fill_textmetodo in questo modo:

import textwrap as _textwrap
class MultilineFormatter(argparse.HelpFormatter):
    def _fill_text(self, text, width, indent):
        text = self._whitespace_matcher.sub(' ', text).strip()
        paragraphs = text.split('|n ')
        multiline_text = ''
        for paragraph in paragraphs:
            formatted_paragraph = _textwrap.fill(paragraph, width, initial_indent=indent, subsequent_indent=indent) + '\n\n'
            multiline_text = multiline_text + formatted_paragraph
        return multiline_text

Confronta con il codice sorgente originale proveniente dal modulo argparse :

def _fill_text(self, text, width, indent):
    text = self._whitespace_matcher.sub(' ', text).strip()
    return _textwrap.fill(text, width, initial_indent=indent,
                                       subsequent_indent=indent)

Nel codice originale viene racchiusa l'intera descrizione. Nel formatter personalizzato sopra l'intero testo è suddiviso in più blocchi e ciascuno di essi è formattato in modo indipendente.

Quindi, con l'aiuto del formatter personalizzato:

parser = ArgumentParser(description= """First paragraph 
                                        |n                              
                                        Second paragraph
                                        |n
                                        Third paragraph""",  
                usage='%(prog)s [OPTIONS]',
                formatter_class=MultilineFormatter)

options = parser.parse_args()

l'output è:

utilizzo: play-with-argparse.py [OPZIONI]

Primo paragrafo

Secondo paragrafo

Terzo paragrafo

argomenti opzionali:
  -h, --help mostra questo messaggio di aiuto ed esce

1
Questo è meraviglioso --- è successo su questo dopo aver quasi rinunciato e aver pensato di reimplementare del tutto l'argomento di aiuto ... mi ha risparmiato una buona dose di seccatura.
Paul Gowder,

2
la sottoclasse HelpFormatterè problematica poiché gli sviluppatori argparse garantiscono solo che il nome della classe sopravviverà nelle versioni future di argparse. Fondamentalmente si sono scritti un segno di spunta in modo che possano cambiare i nomi dei metodi se sarebbe conveniente per loro farlo. Lo trovo frustrante; il minimo che avrebbero potuto fare è stato esposto alcuni metodi nell'API.
MrMas

Non proprio quello che l'OP stava chiedendo, ma esattamente quello che volevo, grazie!
Huw Walters,

2

Volevo avere sia le interruzioni di riga manuali nel testo descrittivo, sia il suo avvolgimento automatico; ma nessuno dei suggerimenti qui ha funzionato per me - così ho finito per modificare la classe SmartFormatter fornita nelle risposte qui; nonostante i problemi con i nomi dei metodi argparse non essendo un'API pubblica, ecco cosa ho (come un file chiamato test.py):

import argparse
from argparse import RawDescriptionHelpFormatter

# call with: python test.py -h

class SmartDescriptionFormatter(argparse.RawDescriptionHelpFormatter):
  #def _split_lines(self, text, width): # RawTextHelpFormatter, although function name might change depending on Python
  def _fill_text(self, text, width, indent): # RawDescriptionHelpFormatter, although function name might change depending on Python
    #print("splot",text)
    if text.startswith('R|'):
      paragraphs = text[2:].splitlines()
      rebroken = [argparse._textwrap.wrap(tpar, width) for tpar in paragraphs]
      #print(rebroken)
      rebrokenstr = []
      for tlinearr in rebroken:
        if (len(tlinearr) == 0):
          rebrokenstr.append("")
        else:
          for tlinepiece in tlinearr:
            rebrokenstr.append(tlinepiece)
      #print(rebrokenstr)
      return '\n'.join(rebrokenstr) #(argparse._textwrap.wrap(text[2:], width))
    # this is the RawTextHelpFormatter._split_lines
    #return argparse.HelpFormatter._split_lines(self, text, width)
    return argparse.RawDescriptionHelpFormatter._fill_text(self, text, width, indent)

parser = argparse.ArgumentParser(formatter_class=SmartDescriptionFormatter, description="""R|Blahbla bla blah blahh/blahbla (bla blah-blabla) a blahblah bl a blaha-blah .blah blah

Blah blah bla blahblah, bla blahblah blah blah bl blblah bl blahb; blah bl blah bl bl a blah, bla blahb bl:

  blah blahblah blah bl blah blahblah""")

options = parser.parse_args()

Funziona così in 2.7 e 3.4:

$ python test.py -h
usage: test.py [-h]

Blahbla bla blah blahh/blahbla (bla blah-blabla) a blahblah bl a blaha-blah
.blah blah

Blah blah bla blahblah, bla blahblah blah blah bl blblah bl blahb; blah bl
blah bl bl a blah, bla blahb bl:

  blah blahblah blah bl blah blahblah

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

1

A partire da SmartFomatter sopra descritto, sono arrivato a quella soluzione:

class SmartFormatter(argparse.HelpFormatter):
    '''
         Custom Help Formatter used to split help text when '\n' was 
         inserted in it.
    '''

    def _split_lines(self, text, width):
        r = []
        for t in text.splitlines(): r.extend(argparse.HelpFormatter._split_lines(self, t, width))
        return r

Si noti che stranamente l'argomento formatter_class passato al parser di primo livello non è ereditato dai sub_parser, bisogna passarlo di nuovo per ogni sub_parser creato.


0

Prefazione

Per questa domanda, argparse.RawTextHelpFormatterè utile per me.

Ora, voglio condividere come uso il argparse.

So che potrebbe non essere correlato alla domanda,

ma queste domande mi hanno infastidito per un po '.

Quindi voglio condividere la mia esperienza, spero che possa essere utile per qualcuno.

Eccoci qui.

Moduli di terze parti

colorama : per cambiare il colore del testo:pip install colorama

Fa funzionare le sequenze di caratteri di escape ANSI (per produrre testo terminale colorato e posizionamento del cursore) in MS Windows

Esempio

import colorama
from colorama import Fore, Back
from pathlib import Path
from os import startfile, system

SCRIPT_DIR = Path(__file__).resolve().parent
TEMPLATE_DIR = SCRIPT_DIR.joinpath('.')


def main(args):
    ...


if __name__ == '__main__':
    colorama.init(autoreset=True)

    from argparse import ArgumentParser, RawTextHelpFormatter

    format_text = FormatText([(20, '<'), (60, '<')])
    yellow_dc = format_text.new_dc(fore_color=Fore.YELLOW)
    green_dc = format_text.new_dc(fore_color=Fore.GREEN)
    red_dc = format_text.new_dc(fore_color=Fore.RED, back_color=Back.LIGHTYELLOW_EX)

    script_description = \
        '\n'.join([desc for desc in
                   [f'\n{green_dc(f"python {Path(__file__).name} [REFERENCE TEMPLATE] [OUTPUT FILE NAME]")} to create template.',
                    f'{green_dc(f"python {Path(__file__).name} -l *")} to get all available template',
                    f'{green_dc(f"python {Path(__file__).name} -o open")} open template directory so that you can put your template file there.',
                    # <- add your own description
                    ]])
    arg_parser = ArgumentParser(description=yellow_dc('CREATE TEMPLATE TOOL'),
                                # conflict_handler='resolve',
                                usage=script_description, formatter_class=RawTextHelpFormatter)

    arg_parser.add_argument("ref", help="reference template", nargs='?')
    arg_parser.add_argument("outfile", help="output file name", nargs='?')
    arg_parser.add_argument("action_number", help="action number", nargs='?', type=int)
    arg_parser.add_argument('--list', "-l", dest='list',
                            help=f"example: {green_dc('-l *')} \n"
                                 "description: list current available template. (accept regex)")

    arg_parser.add_argument('--option', "-o", dest='option',
                            help='\n'.join([format_text(msg_data_list) for msg_data_list in [
                                ['example', 'description'],
                                [green_dc('-o open'), 'open template directory so that you can put your template file there.'],
                                [green_dc('-o run'), '...'],
                                [green_dc('-o ...'), '...'],
                                # <- add your own description
                            ]]))

    g_args = arg_parser.parse_args()
    task_run_list = [[False, lambda: startfile('.')] if g_args.option == 'open' else None,
                     [False, lambda: [print(template_file_path.stem) for template_file_path in TEMPLATE_DIR.glob(f'{g_args.list}.py')]] if g_args.list else None,
                     # <- add your own function
                     ]
    for leave_flag, func in [task_list for task_list in task_run_list if task_list]:
        func()
        if leave_flag:
            exit(0)

    # CHECK POSITIONAL ARGUMENTS
    for attr_name, value in vars(g_args).items():
        if attr_name.startswith('-') or value is not None:
            continue
        system('cls')
        print(f'error required values of {red_dc(attr_name)} is None')
        print(f"if you need help, please use help command to help you: {red_dc(f'python {__file__} -h')}")
        exit(-1)
    main(g_args)

Dove la classe di FormatTextè la seguente

class FormatText:
    __slots__ = ['align_list']

    def __init__(self, align_list: list, autoreset=True):
        """
        USAGE::

            format_text = FormatText([(20, '<'), (60, '<')])
            red_dc = format_text.new_dc(fore_color=Fore.RED)
            print(red_dc(['column 1', 'column 2']))
            print(red_dc('good morning'))
        :param align_list:
        :param autoreset:
        """
        self.align_list = align_list
        colorama.init(autoreset=autoreset)

    def __call__(self, text_list: list):
        if len(text_list) != len(self.align_list):
            if isinstance(text_list, str):
                return text_list
            raise AttributeError
        return ' '.join(f'{txt:{flag}{int_align}}' for txt, (int_align, flag) in zip(text_list, self.align_list))

    def new_dc(self, fore_color: Fore = Fore.GREEN, back_color: Back = ""):  # DECORATOR
        """create a device context"""
        def wrap(msgs):
            return back_color + fore_color + self(msgs) + Fore.RESET
        return wrap

inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.