Python argparse: Bagaimana cara memasukkan baris baru dalam teks bantuan?

341

Saya menggunakan argparsePython 2.7 untuk parsing opsi input. Salah satu opsi saya adalah pilihan ganda. Saya ingin membuat daftar dalam teks bantuannya, mis

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()

Namun, argparselepaskan semua baris baru dan spasi berurutan. Hasilnya terlihat seperti

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

uji

argumen opsional:
  -h, --help tampilkan pesan bantuan ini dan keluar
  -g {a, b, g, d, e} Beberapa opsi, di mana a = alpha b = beta g = gamma d = delta e
                  = epsilon

Bagaimana cara memasukkan baris baru dalam teks bantuan?

kennytm
sumber
Saya tidak punya python 2.7 dengan saya sehingga saya bisa menguji ide-ide saya. Bagaimana dengan menggunakan teks bantuan dalam tanda kutip tiga ("" "" ""). Apakah baris baru bertahan menggunakan ini?
pyfunc
4
@pyfunc: Tidak. Pengupasan dilakukan saat runtime oleh argparse, bukan interpreter, jadi beralih ke """..."""tidak akan membantu.
kennytm
Ini bekerja untuk saya
kapulaga

Jawaban:

394

Coba gunakan RawTextHelpFormatter:

from argparse import RawTextHelpFormatter
parser = ArgumentParser(description='test', formatter_class=RawTextHelpFormatter)
Michał Kwiatkowski
sumber
6
Saya pikir tidak. Anda dapat mensubklasifikasikannya, tapi sayangnya Only the name of this class is considered a public API. All the methods provided by the class are considered an implementation detail. Jadi mungkin bukan ide yang bagus, meskipun mungkin tidak masalah, karena 2,7 dimaksudkan untuk menjadi 2.x python terakhir dan Anda akan diharapkan untuk memperbaiki banyak hal untuk 3.x pula. Saya sebenarnya menjalankan 2.6 dengan argparsediinstal melalui easy_installsehingga dokumentasi itu sendiri mungkin sudah ketinggalan zaman.
intuited
3
Beberapa tautan: untuk python 2.7 , dan python 3. * . Paket 2.6 harus, menurut wiki-nya , sesuai dengan yang resmi 2.7. Dari dokumen: "Melewati RawDescriptionHelpFormatter sebagai formatter_class = menunjukkan bahwa deskripsi dan epilog sudah diformat dengan benar dan tidak boleh dibungkus garis"
Stefano
83
Coba sebaliknya formatter_class = RawDescriptionHelpFormatteryang hanya berfungsi pada deskripsi dan epilog daripada teks bantuan.
MarkHu
3
Saya perhatikan bahwa bahkan dengan RawTextHelpFormatter, memimpin dan mengikuti baris baru dihapus. Untuk mengatasinya, Anda cukup menambahkan dua atau lebih baris baru berturut-turut; semua kecuali satu baris baru akan bertahan.
MrMas
11
Anda juga bisa menggabungkan formatters, mis class Formatter( argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): passlalu formatter_class=Formatter.
Terry Brown
79

Jika Anda hanya ingin mengganti satu opsi, Anda tidak boleh menggunakan RawTextHelpFormatter. Alih-alih subklas HelpFormatterdan menyediakan intro khusus untuk opsi yang harus ditangani "mentah" (Saya menggunakan "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)

Dan gunakan itu:

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()

Panggilan lain ke .add_argument()tempat bantuan tidak dimulaiR| akan dibungkus seperti biasa.

Ini adalah bagian dari perbaikan saya pada argparse . SmartFormatter lengkap juga mendukung penambahan default ke semua opsi, dan input mentah dari deskripsi utilitas. Versi lengkap memiliki _split_linesmetode sendiri , sehingga pemformatan apapun yang dilakukan misalnya string versi dipertahankan:

parser.add_argument('--version', '-v', action="version",
                    version="version...\n   42!")
Anthon
sumber
Saya ingin melakukan ini untuk pesan versi, tetapi SmartFormatter ini tampaknya hanya berfungsi dengan teks bantuan, bukan teks versi khusus. parser.add_argument('-v', '--version', action='version',version=get_version_str()) Apakah mungkin memperluasnya ke kasus itu?
mc_electron
@mc_electron versi lengkap dari SmartFormatter juga memiliki sendiri _split_linesdan mempertahankan jeda baris (tidak perlu menentukan "R |" di awal, jika Anda ingin opsi itu, tambal _VersionAction.__call__metode
Anthon
Saya tidak sepenuhnya mengoceh bagian pertama dari komentar Anda, meskipun saya dapat melihat _VersionAction.__call__bahwa saya mungkin ingin hanya parser.exit(message=version)menggunakan versi yang diformat. Apakah ada cara untuk melakukan itu tanpa merilis salinan argparse yang ditambal?
mc_electron
@ mc_electron Saya merujuk pada peningkatan yang saya publikasikan di bitbucket (sesuai tautan ke peningkatan saya tentang argparse dalam jawabannya). Tapi Anda juga bisa menambal __call__di _VersionActiondengan melakukan argparse._VersionAction.__call__ = smart_versionsetelah mendefinisikandef smart_version(self, parser, namespace, values, option_string=None): ...
Anthon
Ide yang hebat. Tidak membantu saya karena epilog dan deskripsi sepertinya tidak berjalan melalui _split_lines :(
Pod
31

Cara mudah lain untuk melakukannya adalah dengan menyertakan textwrap .

Sebagai contoh,

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 ... '''))

Dengan cara ini, kita dapat menghindari ruang kosong yang panjang di depan setiap jalur output.

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 ...
Wang Zong'an
sumber
11

Saya telah menghadapi masalah serupa (Python 2.7.6). Saya telah mencoba memecah bagian deskripsi menjadi beberapa baris menggunakan RawTextHelpFormatter:

parser = ArgumentParser(description="""First paragraph 

                                       Second paragraph

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

options = parser.parse_args()

Dan mendapatkan:

penggunaan: play-with-argparse.py [OPSI]

Paragraf pertama 

                        Paragraf kedua

                        Paragraf ketiga

argumen opsional:
  -h, --help tampilkan pesan bantuan ini dan keluar

Jadi RawTextHelpFormatterbukan solusi. Karena ia mencetak deskripsi seperti yang muncul dalam kode sumber, menjaga semua karakter spasi putih (saya ingin menyimpan tab tambahan dalam kode sumber saya untuk keterbacaan tetapi saya tidak ingin mencetak semuanya. Juga formatter mentah tidak membungkus baris ketika itu terlalu panjang, lebih dari 80 karakter misalnya).

Terima kasih kepada @Anton yang menginspirasi arah yang benar di atas . Tetapi solusi itu perlu sedikit modifikasi untuk memformat bagian deskripsi .

Bagaimanapun, formatter khusus diperlukan. Saya memperpanjang metode HelpFormatterkelas dan overrode yang sudah ada _fill_textseperti ini:

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

Bandingkan dengan kode sumber asli yang berasal dari modul 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)

Dalam kode asli seluruh deskripsi sedang dibungkus. Dalam formatter khusus di atas, seluruh teks dibagi menjadi beberapa bagian, dan masing-masing diformat secara independen.

Jadi dengan bantuan formatter khusus:

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

options = parser.parse_args()

outputnya adalah:

penggunaan: play-with-argparse.py [OPSI]

Paragraf pertama

Paragraf kedua

Paragraf ketiga

argumen opsional:
  -h, --help tampilkan pesan bantuan ini dan keluar
flaz14
sumber
1
Ini luar biasa --- terjadi pada ini setelah hampir menyerah dan merenungkan hanya mengimplementasikan argumen bantuan sama sekali ... menyelamatkan saya dari kerumitan.
Paul Gowder
2
subclassing HelpFormatterbermasalah karena pengembang argparse hanya menjamin bahwa nama kelas akan bertahan di versi argparse yang akan datang. Mereka pada dasarnya menulis cek kosong sendiri sehingga mereka dapat mengubah nama metode jika akan lebih mudah bagi mereka untuk melakukannya. Saya menemukan ini frustasi; paling tidak yang bisa mereka lakukan adalah mengekspos beberapa metode dalam API.
MrMas
Bukan apa yang diminta OP, tapi persis apa yang saya inginkan, terima kasih!
Huw Walters
2

Saya ingin memiliki kedua jeda baris manual dalam teks deskripsi, dan membungkusnya secara otomatis; tetapi tidak ada saran di sini yang bekerja untuk saya - jadi saya akhirnya memodifikasi kelas SmartFormatter yang diberikan dalam jawaban di sini; masalah dengan nama metode argparse tidak menjadi API publik meskipun, di sini adalah apa yang saya miliki (sebagai file bernama 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()

Ini adalah cara kerjanya di 2.7 dan 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
sdbbs
sumber
1

Mulai dari SmartFomatter yang dijelaskan di atas, saya mengakhiri solusi itu:

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

Perhatikan bahwa anehnya argumen formatter_class yang diteruskan ke parser tingkat atas tidak diwariskan oleh sub_parser, seseorang harus meneruskannya lagi untuk setiap sub_parser yang dibuat.

ermitz
sumber
0

Kata pengantar

Untuk pertanyaan ini, argparse.RawTextHelpFormattersangat membantu saya.

Sekarang, saya ingin berbagi bagaimana cara menggunakan argparse.

Saya tahu itu mungkin tidak terkait dengan pertanyaan,

tetapi pertanyaan-pertanyaan ini telah mengganggu saya untuk sementara waktu.

Jadi saya ingin membagikan pengalaman saya, semoga bermanfaat bagi seseorang.

Kita mulai.

Modul Pihak ke-3

colorama : untuk mengubah warna teks:pip install colorama

Membuat urutan karakter pelarian ANSI (untuk menghasilkan teks terminal berwarna dan posisi kursor) berfungsi di bawah MS Windows

Contoh

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)

Dimana kelasnya FormatTextadalah sebagai berikut

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

masukkan deskripsi gambar di sini

Carson
sumber