Python argparse: Làm thế nào để chèn dòng mới vào văn bản trợ giúp?


340

Tôi đang sử dụng argparsetrong Python 2.7 để phân tích các tùy chọn nhập liệu. Một trong những lựa chọn của tôi là nhiều lựa chọn. Tôi muốn tạo một danh sách trong văn bản trợ giúp của nó, vd

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

Tuy nhiên, argparsedải tất cả các dòng mới và không gian liên tiếp. Kết quả trông như

~ / Tải xuống: 52 $ python2.7 x.py -h
cách sử dụng: x.py [-h] [-g {a, b, g, d, e}]

kiểm tra

đối số tùy chọn:
  -h, --hỗ trợ hiển thị thông báo trợ giúp này và thoát
  -g {a, b, g, d, e} Một số tùy chọn, trong đó a = alpha b = beta g = gamma d = delta e
                  = epsilon

Làm thế nào để chèn dòng mới trong văn bản trợ giúp?


Tôi không có python 2.7 với tôi để tôi có thể kiểm tra ý tưởng của mình. Làm thế nào về việc sử dụng văn bản trợ giúp trong ba dấu ngoặc kép ("" "" ""). Các dòng mới tồn tại bằng cách sử dụng này?
pyfunc

4
@pyfunc: Không. Việc tước được thực hiện trong thời gian chạy bằng cách argparse, không phải trình thông dịch, vì vậy chuyển sang """..."""không giúp đỡ.
kennytm

Điều này làm việc cho tôi
thảo quả

Câu trả lời:


393

Hãy thử sử dụng RawTextHelpFormatter:

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

6
Tôi nghĩ rằng nó không phải là. Bạn có thể phân lớp nó, nhưng thật không may Only the name of this class is considered a public API. All the methods provided by the class are considered an implementation detail. Vì vậy, có lẽ không phải là một ý tưởng tuyệt vời, mặc dù nó có thể không quan trọng, vì 2.7 có nghĩa là con trăn 2.x cuối cùng và bạn sẽ được dự kiến ​​sẽ tái cấu trúc nhiều thứ cho 3.x. Tôi thực sự đang chạy 2.6 với argparsecài đặt qua easy_installđể tài liệu có thể bị lỗi thời.
trực giác

3
Một số liên kết: cho python 2.7python 3. * . Gói 2.6, theo wiki của nó , tuân thủ chính thức 2.7. Từ tài liệu: "Truyền RawDescripHelpFormatter dưới dạng formatter_group = chỉ ra rằng mô tả và biểu đồ đã được định dạng chính xác và không nên được xếp hàng"
Stefano

83
Thay vào đó, hãy thử formatter_group = RawDescriptionHelpFormatterchỉ hoạt động trên mô tả và biểu đồ chứ không phải văn bản trợ giúp.
MarkHu

3
Tôi đã nhận thấy rằng ngay cả với RawTextHelpFormatter, các dòng mới dẫn và theo dõi được loại bỏ. Để giải quyết vấn đề này, bạn chỉ cần thêm hai hoặc nhiều dòng mới liên tiếp; tất cả trừ một dòng mới sẽ tồn tại
MrMas 18/03/2016

11
Bạn có thể kết hợp các trình định dạng quá, ví dụ class Formatter( argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): passvà sau đó formatter_class=Formatter.
Terry Brown

78

Nếu bạn chỉ muốn ghi đè một tùy chọn, bạn không nên sử dụng RawTextHelpFormatter. Thay vào đó, phân lớp HelpFormattervà cung cấp phần giới thiệu đặc biệt cho các tùy chọn nên được xử lý "thô" (tôi sử dụng "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)

Và sử dụng nó:

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

Bất kỳ cuộc gọi nào khác .add_argument()mà sự trợ giúp không bắt đầu R|sẽ được kết thúc như bình thường.

Đây là một phần trong những cải tiến của tôi trên argparse . SmartFormatter đầy đủ cũng hỗ trợ thêm mặc định cho tất cả các tùy chọn và nhập liệu thô của mô tả tiện ích. Phiên bản đầy đủ có _split_linesphương thức riêng của nó , do đó mọi định dạng được thực hiện đối với các chuỗi phiên bản được giữ nguyên:

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

Tôi muốn làm điều này cho một thông báo phiên bản, nhưng SmartFormatter này dường như chỉ hoạt động với văn bản trợ giúp, không phải văn bản phiên bản đặc biệt. parser.add_argument('-v', '--version', action='version',version=get_version_str()) Có thể mở rộng nó cho trường hợp đó?
mc_electron

@mc_electron phiên bản đầy đủ của SmartFormatter cũng có _split_linesngắt dòng riêng và bảo toàn ngắt dòng (không cần chỉ định "R |" ở đầu, nếu bạn muốn tùy chọn đó, hãy vá _VersionAction.__call__phương thức
Anthon

Tôi không hoàn toàn mò mẫm phần đầu tiên của bình luận của bạn, mặc dù tôi có thể thấy _VersionAction.__call__rằng tôi có thể muốn nó parser.exit(message=version)thay vì sử dụng phiên bản được định dạng. Có cách nào để làm điều đó mà không phát hành một bản sao của argparse không?
mc_electron

@mc_electron Tôi đang đề cập đến những cải tiến tôi đã xuất bản trên bitbucket (theo liên kết đến những cải tiến của tôi về argparse trong câu trả lời). Nhưng bạn cũng có thể vá các __call__trong _VersionActionbằng cách thực hiện argparse._VersionAction.__call__ = smart_versionsau khi xác địnhdef smart_version(self, parser, namespace, values, option_string=None): ...
Anthon

Ý tưởng tuyệt vời. Không giúp tôi vì phần ngoại truyện và mô tả dường như không chạy qua _split_lines :(
Pod

31

Một cách dễ dàng khác để làm điều đó là bao gồm textwrap .

Ví dụ,

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

Bằng cách này, chúng ta có thể tránh khoảng trống dài phía trước mỗi dòng đầu ra.

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

Tôi đã phải đối mặt với vấn đề tương tự (Python 2.7.6). Tôi đã cố chia phần mô tả thành nhiều dòng bằng cách sử dụng RawTextHelpFormatter:

parser = ArgumentParser(description="""First paragraph 

                                       Second paragraph

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

options = parser.parse_args()

Và có:

cách sử dụng: play-with-argparse.py [TÙY CHỌN]

Đoạn đầu 

                        Đoạn thứ hai

                        Đoạn thứ ba

đối số tùy chọn:
  -h, --hỗ trợ hiển thị thông báo trợ giúp này và thoát

Vì vậy, RawTextHelpFormatterkhông phải là một giải pháp. Bởi vì nó in mô tả như xuất hiện trong mã nguồn, bảo toàn tất cả các ký tự khoảng trắng (tôi muốn giữ các tab bổ sung trong mã nguồn của mình để dễ đọc nhưng tôi không muốn in tất cả chúng. quá dài, hơn 80 ký tự chẳng hạn).

Cảm ơn @Anton, người đã truyền cảm hứng cho hướng đi đúng đắn ở trên . Nhưng giải pháp đó cần sửa đổi một chút để định dạng phần mô tả .

Dù sao, định dạng tùy chỉnh là cần thiết. Tôi đã mở rộng HelpFormatterlớp hiện có và _fill_textphương thức overrode như thế này:

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

So sánh với mã nguồn ban đầu đến từ mô-đun 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)

Trong mã gốc, toàn bộ mô tả đang được bọc. Trong định dạng tùy chỉnh ở trên, toàn bộ văn bản được chia thành nhiều phần và mỗi phần được định dạng độc lập.

Vì vậy, với sự trợ giúp của định dạng tùy chỉnh:

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

options = parser.parse_args()

đầu ra là:

cách sử dụng: play-with-argparse.py [TÙY CHỌN]

Đoạn đầu

Đoạn thứ hai

Đoạn thứ ba

đối số tùy chọn:
  -h, --hỗ trợ hiển thị thông báo trợ giúp này và thoát

1
Điều này thật tuyệt vời --- đã xảy ra với điều này sau khi gần như bỏ cuộc và suy ngẫm chỉ cần thực hiện lại đối số trợ giúp hoàn toàn ... đã tiết kiệm cho tôi một số rắc rối tốt.
Paul Gowder

2
phân lớp HelpFormatterlà vấn đề vì các nhà phát triển argparse chỉ đảm bảo rằng tên lớp sẽ tồn tại trong các phiên bản tương lai của argparse. Về cơ bản, họ đã tự viết cho mình một tấm séc trống để họ có thể thay đổi tên phương thức nếu thuận tiện cho họ làm như vậy. Tôi thấy điều này bực bội; ít nhất họ có thể làm là phơi bày một vài phương thức trong API.
MrMas 18/03/2016

Không hoàn toàn những gì OP yêu cầu, nhưng chính xác những gì tôi muốn, cảm ơn!
Huw Walters

2

Tôi muốn có cả ngắt dòng thủ công trong văn bản mô tả và tự động gói nó; nhưng không có gợi ý nào ở đây có hiệu quả với tôi - vì vậy cuối cùng tôi đã sửa đổi lớp SmartFormatter được đưa ra trong các câu trả lời ở đây; các vấn đề với tên phương thức argparse không phải là API công khai, đây là những gì tôi có (dưới dạng tệp được gọi 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()

Đây là cách nó hoạt động trong 2.7 và 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

Bắt đầu từ SmartFomatter được mô tả ở trên, tôi đã kết thúc giải pháp đó:

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

Lưu ý rằng kỳ lạ là đối số formatter_group được truyền cho trình phân tích cú pháp cấp cao nhất không được kế thừa bởi sub_parsers, người ta phải truyền lại nó cho mỗi sub_parser được tạo.


0

Lời nói đầu

Đối với câu hỏi này, argparse.RawTextHelpFormatterlà hữu ích cho tôi.

Bây giờ, tôi muốn chia sẻ làm thế nào để tôi sử dụng argparse.

Tôi biết nó có thể không liên quan đến câu hỏi,

nhưng những câu hỏi này đã làm phiền tôi trong một thời gian.

Vì vậy, tôi muốn chia sẻ kinh nghiệm của mình, hy vọng điều đó sẽ hữu ích cho ai đó.

Chúng ta đi đây.

Mô-đun bên thứ 3

colorama : để thay đổi màu văn bản:pip install colorama

Làm cho chuỗi ký tự thoát ANSI (để tạo văn bản đầu cuối màu và định vị con trỏ) hoạt động trong MS Windows

Thí dụ

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)

Trường hợp của lớp FormatTextlà sau

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

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.