Python argparse: วิธีแทรกบรรทัดใหม่ในข้อความช่วยเหลือได้อย่างไร


341

ฉันใช้argparsePython 2.7ในการแยกวิเคราะห์ตัวเลือกอินพุต หนึ่งในตัวเลือกของฉันคือทางเลือกที่หลากหลาย ฉันต้องการสร้างรายการในข้อความช่วยเหลือของมันเช่น

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

อย่างไรก็ตามตัดargparseบรรทัดใหม่ทั้งหมดและช่องว่างที่ต่อเนื่องกัน ผลลัพธ์ดูเหมือนว่า

~ / ดาวน์โหลด: 52 $ python2.7 x.py -h
การใช้งาน: x.py [-h] [-g {a, b, g, d, e}]

ทดสอบ

อาร์กิวเมนต์ตัวเลือก:
  -h, - ช่วยแสดงข้อความช่วยเหลือนี้และออก
  -g {a, b, g, d, e} ตัวเลือกบางตัวโดยที่ a = alpha b = beta g = gamma d = delta e
                  = epsilon

วิธีแทรกบรรทัดใหม่ในข้อความช่วยเหลือได้อย่างไร


ฉันไม่มี python 2.7 กับฉันดังนั้นฉันสามารถทดสอบความคิดของฉันได้ วิธีการเกี่ยวกับการใช้ข้อความช่วยเหลือในเครื่องหมายคำพูดสามคำ ("" "" "") บรรทัดใหม่มีชีวิตรอดโดยใช้สิ่งนี้หรือไม่?
pyfunc

4
@pyfunc: ไม่การลอกเกิดขึ้นในขณะรันไทม์argparseไม่ใช่ล่ามดังนั้นการเปลี่ยนไปใช้"""..."""จะไม่ช่วย
kennytm

สิ่งนี้ใช้ได้กับฉัน
กระวาน

คำตอบ:


394

ลองใช้RawTextHelpFormatter:

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

6
ฉันคิดว่ามันไม่ใช่ คุณสามารถซับคลาสมันได้ แต่น่าเสียดาย Only the name of this class is considered a public API. All the methods provided by the class are considered an implementation detail. ดังนั้นอาจไม่ใช่ความคิดที่ดีแม้ว่ามันอาจไม่สำคัญเนื่องจาก 2.7 หมายถึงไพ ธ อน 2.x ตัวสุดท้ายและคุณคาดว่าจะรีฟิลจำนวนมากสำหรับ 3.x อยู่ดี จริง ๆ แล้วฉันใช้งาน 2.6 พร้อมกับการargparseติดตั้งผ่านeasy_installเพื่อให้เอกสารของตัวเองอาจล้าสมัย
intuited

3
บางลิงค์: สำหรับหลาม 2.7และงูหลาม 3. * 2.6 ควรแพคเกจตามที่วิกิพีเดียของตนปฏิบัติตามอย่างเป็นทางการ 2.7 หนึ่ง จากเอกสาร: "การส่งผ่าน RawDescriptionHelpFormatter เป็น formatter_class = ระบุว่าคำอธิบายและ epilog มีรูปแบบที่ถูกต้องแล้วและไม่ควรมีการห่อบรรทัด"
Stefano

83
ลองใช้ formatter_class = RawDescriptionHelpFormatterซึ่งใช้ได้กับคำอธิบายและ epilog แทนที่จะเป็นข้อความช่วยเหลือ
MarkHu

3
ฉันสังเกตเห็นว่าถึงแม้จะมีการRawTextHelpFormatterขึ้นบรรทัดใหม่และนำขึ้นบรรทัดใหม่ก็ตาม ในการแก้ไขปัญหานี้คุณสามารถเพิ่มบรรทัดใหม่ติดต่อกันสองบรรทัดขึ้นไป ทั้งหมดยกเว้นหนึ่งบรรทัดจะอยู่รอด
MrMas

11
คุณสามารถรวม formatters เกินไปเช่นนั้นclass Formatter( argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass formatter_class=Formatter
เทอร์รี่บราวน์

79

RawTextHelpFormatterถ้าคุณเพียงต้องการที่จะแทนที่หนึ่งตัวเลือกที่คุณไม่ควรใช้ แทนคลาสย่อยHelpFormatterและให้คำแนะนำพิเศษสำหรับตัวเลือกที่ควรจัดการ "ดิบ" (ฉันใช้"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)

และใช้มัน:

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

การโทรอื่น ๆ ไปยัง.add_argument()ที่ที่ความช่วยเหลือไม่ได้เริ่มต้นR|จะถูกห่อตามปกติ

นี้เป็นส่วนหนึ่งของการปรับปรุงของฉันใน argparse SmartFormatter แบบเต็มยังรองรับการเพิ่มค่าเริ่มต้นให้กับตัวเลือกทั้งหมดและการป้อนข้อมูลดิบของคำอธิบายยูทิลิตี้ เวอร์ชันเต็มมี_split_linesวิธีการของตนเองดังนั้นการจัดรูปแบบใด ๆ ที่กระทำกับสตริงสตริงจะถูกเก็บรักษาไว้:

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

ฉันต้องการทำสิ่งนี้เพื่อรับข้อความรุ่น แต่ SmartFormatter นี้ดูเหมือนจะทำงานกับข้อความช่วยเหลือไม่ใช่ข้อความรุ่นพิเศษ parser.add_argument('-v', '--version', action='version',version=get_version_str()) เป็นไปได้ไหมที่จะขยายออกไปเป็นกรณีนั้น?
mc_electron

@mc_electron เวอร์ชันเต็มของ SmartFormatter ยังมีของตัวเอง_split_linesและเก็บรักษาตัวแบ่งบรรทัด (ไม่จำเป็นต้องระบุ "R |" ที่จุดเริ่มต้นถ้าคุณต้องการตัวเลือกที่แก้ไข_VersionAction.__call__วิธีการ
Anthon

ฉันไม่ได้อ่านส่วนแรกของความคิดเห็นของคุณทั้งหมดแม้ว่าฉันจะเห็น_VersionAction.__call__ว่าฉันอาจต้องการให้parser.exit(message=version)แทนที่จะใช้รูปแบบที่จัดรูปแบบ มีวิธีใดที่จะทำเช่นนั้นโดยไม่ปล่อยสำเนา argparse ที่ผ่านการปะแก้แล้วหรือไม่?
mc_electron

@mc_electron ฉันหมายถึงการปรับปรุงที่ฉันเผยแพร่ใน bitbucket (ตามลิงค์ไปยังการปรับปรุงของฉันในการโต้แย้งในคำตอบ) แต่คุณยังสามารถแพทช์__call__ใน_VersionActionโดยการทำargparse._VersionAction.__call__ = smart_versionหลังจากที่กำหนดdef smart_version(self, parser, namespace, values, option_string=None): ...
โธ

ความคิดที่ดี. ไม่ได้ช่วยฉันในฐานะ epilog และคำอธิบายดูเหมือนจะไม่ทำงานผ่าน _split_lines :(
Pod

31

อีกวิธีที่ง่ายในการทำคือรวมtextwrap textwrap

ตัวอย่างเช่น,

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

ด้วยวิธีนี้เราสามารถหลีกเลี่ยงพื้นที่ว่างที่ยาวต่อหน้าแต่ละบรรทัดเอาต์พุต

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

ฉันประสบปัญหาที่คล้ายกัน (Python 2.7.6) ฉันพยายามแบ่งส่วนคำอธิบายออกเป็นหลายบรรทัดโดยใช้RawTextHelpFormatter:

parser = ArgumentParser(description="""First paragraph 

                                       Second paragraph

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

options = parser.parse_args()

และได้รับ:

การใช้งาน: play-with-argparse.py [OPTIONS]

ย่อหน้าแรก 

                        วรรคสอง

                        วรรคสาม

อาร์กิวเมนต์ตัวเลือก:
  -h, - ช่วยแสดงข้อความช่วยเหลือนี้และออก

ดังนั้นจึงRawTextHelpFormatterไม่ใช่ทางออก เพราะมันพิมพ์คำอธิบายตามที่ปรากฏในซอร์สโค้ดรักษาอักขระช่องว่างทั้งหมด (ฉันต้องการเก็บแท็บพิเศษในซอร์สโค้ดของฉันเพื่อให้อ่านได้ แต่ฉันไม่ต้องการพิมพ์ทั้งหมดนอกจากนี้ฟอร์แมตฟอร์แมตไม่ตัดบรรทัดเมื่อมันเป็น ตัวอย่างยาวเกินไปเกิน 80 อักขระ)

ขอขอบคุณที่เป็นแรงบันดาลใจ @Anton ทิศทางที่ถูกต้องดังกล่าวข้างต้น แต่โซลูชันนั้นต้องการการปรับเปลี่ยนเล็กน้อยเพื่อจัดรูปแบบคำอธิบายส่วน

อย่างไรก็ตามจำเป็นต้องใช้ตัวจัดรูปแบบที่กำหนดเอง ฉันขยายHelpFormatterชั้นเรียนที่มีอยู่และ_fill_textวิธีการoverrode เช่นนี้

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

เปรียบเทียบกับซอร์สโค้ดต้นฉบับที่มาจากโมดูล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)

ในรหัสต้นฉบับคำอธิบายทั้งหมดจะถูกห่อ ในตัวจัดรูปแบบที่กำหนดเองด้านบนข้อความทั้งหมดจะถูกแบ่งออกเป็นหลายชิ้นและแต่ละรูปแบบจะถูกจัดรูปแบบอย่างอิสระ

ดังนั้นด้วยความช่วยเหลือของตัวจัดรูปแบบที่กำหนดเอง:

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

options = parser.parse_args()

ผลลัพธ์คือ:

การใช้งาน: play-with-argparse.py [OPTIONS]

ย่อหน้าแรก

วรรคสอง

วรรคสาม

อาร์กิวเมนต์ตัวเลือก:
  -h, - ช่วยแสดงข้อความช่วยเหลือนี้และออก

1
นี่เป็นสิ่งที่วิเศษ --- เกิดขึ้นหลังจากการยอมแพ้และไตร่ตรองเพียงแค่ปรับใช้อาร์กิวเมนต์ความช่วยเหลือไปพร้อม ๆ กัน ... ช่วยให้ฉันได้รับความยุ่งยาก
Paul Gowder

2
การแบ่งคลาสย่อยHelpFormatterเป็นปัญหาเนื่องจากผู้พัฒนา argparse รับประกันได้ว่าชื่อคลาสจะอยู่รอดได้ในเวอร์ชันอนาคตของอาร์กิวเมนต์ โดยทั่วไปพวกเขาเขียนเช็คว่างเปล่าเพื่อให้พวกเขาสามารถเปลี่ยนชื่อเมธอดได้หากสะดวกสำหรับพวกเขา ฉันพบว่ามันน่าผิดหวัง อย่างน้อยที่สุดพวกเขาสามารถทำได้คือการเปิดเผยวิธีการบางอย่างใน API
MrMas

ไม่ใช่สิ่งที่ OP ขอมา แต่เป็นสิ่งที่ฉันต้องการขอบคุณ!
Huw Walters

2

ฉันต้องการให้ทั้งตัวแบ่งบรรทัดด้วยตนเองในข้อความคำอธิบายและการตัดคำอัตโนมัติ แต่ไม่มีคำแนะนำใดที่เหมาะกับฉัน - ดังนั้นฉันจึงสิ้นสุดการแก้ไขชั้น SmartFormatter ที่ให้ไว้ในคำตอบที่นี่ ปัญหาเกี่ยวกับชื่อเมธอด argparse ไม่ใช่ API สาธารณะแม้ว่านี่คือสิ่งที่ฉันมี (เป็นไฟล์ที่เรียกว่า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()

นี่คือวิธีการทำงานใน 2.7 และ 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

เริ่มต้นจาก SmartFomatter ตามที่อธิบายไว้ข้างต้นฉันสิ้นสุดการแก้ปัญหานั้น:

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

โปรดทราบว่าอาร์กิวเมนต์ formatter_class ที่ส่งผ่านไปยังตัวแยกวิเคราะห์ระดับบนสุดนั้นไม่ได้รับการสืบทอดโดย sub_parsers อย่างใดอย่างหนึ่งต้องผ่านอีกครั้งสำหรับแต่ละ sub_parser ที่สร้างขึ้น


0

คำนำ

สำหรับคำถามนี้argparse.RawTextHelpFormatterมีประโยชน์กับฉัน

argparseตอนนี้ผมต้องการที่จะแบ่งปันวิธีการทำผมใช้

ฉันรู้ว่าอาจไม่เกี่ยวข้องกับคำถาม

แต่คำถามเหล่านี้รบกวนฉันมาระยะหนึ่งแล้ว

ดังนั้นฉันต้องการแบ่งปันประสบการณ์ของฉันหวังว่าจะเป็นประโยชน์สำหรับใครบางคน

ไปเลย.

โมดูลของบุคคลที่สาม

colorama : สำหรับเปลี่ยนสีข้อความ:pip install colorama

ทำให้ลำดับอักขระ escape ของ ANSI (สำหรับการผลิตข้อความเทอร์มินัลและการวางตำแหน่งเคอร์เซอร์) ทำงานภายใต้ MS Windows

ตัวอย่าง

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)

โดยที่ class ของFormatTextมีดังต่อไปนี้

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

ป้อนคำอธิบายรูปภาพที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.