Python argparse:如何在帮助文本中插入换行符?


340

argparse在Python 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,--help显示此帮助消息并退出
  -g {a,b,g,d,e}某些选项,其中a = alpha b = beta g = gamma d = delta e
                  = epsilon

如何在帮助文本中插入换行符?


我没有python 2.7,因此可以测试我的想法。如何在三引号(“”“”“”)中使用帮助文本。新生产线是否可以以此生存?
pyfunc

4
@pyfunc:否。剥离是在运行时由argparse而不是解释器完成的,因此切换为"""..."""无济于事。
kennytm 2010年

对我
有用

Answers:


392

尝试使用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 python,无论如何您都应该为3.x重构很多东西。我实际上正在运行2.6,并且argparse安装了via,easy_install因此文档本身可能已过时。
直觉

3
一些链接:适用于python 2.7python 3. *。根据其Wiki,2.6软件包应符合官方2.7 软件包。从文档中:“将RawDescriptionHelpFormatter传递为formatter_class =表示说明和Epilog已正确格式化,不应进行换行”
Stefano

83
请改用formatter_class = RawDescriptionHelpFormatter,它仅适用于描述和结尾,而不适用于帮助文本。
MarkHu 2014年

3
我注意到,即使使用RawTextHelpFormatter,也删除了开头和结尾的换行符。要解决此问题,您只需添加两个或更多连续的换行符即可;除了一个换行符之外的所有代码都将生存。
MrMas

11
您也可以合并格式化程序,例如class Formatter( argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass然后formatter_class=Formatter
特里·布朗

78

如果您只想覆盖一个选项,则不应使用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 2014年

@mc_electron SmartFormatter的完整版本也有其自己的名称_split_lines并保留换行符(无需在开头指定“ R |”,如果需要该选项,请修补此_VersionAction.__call__方法
Anthon 2014年

我并没有完全忽略您的评论的第一部分,尽管我可以看到_VersionAction.__call__我可能希望它只是parser.exit(message=version)使用格式化版本。有什么办法可以在不发布argparse补丁补丁的情况下做到这一点?
mc_electron 2014年

@mc_electron我指的是我在bitbucket上发布的改进(根据答案中关于argparse的改进的链接)。但是你也可以修补__call___VersionActionargparse._VersionAction.__call__ = smart_version定义之后def smart_version(self, parser, namespace, values, option_string=None): ...
安森

好点子。没帮助我,因为结尾和描述似乎没有贯穿_split_lines :(

31

另一个简单的方法是包括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 [选项]

第一段 

                        第二段

                        第三段

可选参数:
  -h,--help显示此帮助消息并退出

所以RawTextHelpFormatter不是解决方案。因为它按源代码中的显示方式打印描述,所以保留了所有空白字符(我想在源代码中保留额外的制表符,以提高可读性,但我不想将它们全部打印出来。同样,原始格式化程序不会在换行时换行太长,例如超过80个字符)。

感谢@Anton启发了上面的正确方向。但是该解决方案需要稍作修改才能格式化描述部分。

无论如何,需要自定义格式化程序。我扩展了现有的HelpFormatter类并覆盖了这样的_fill_text方法:

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 [选项]

第一段

第二段

第三段

可选参数:
  -h,--help显示此帮助消息并退出

1
这是很棒的---在几乎放弃并考虑只是完全重新实现帮助参数之后发生的事情……为我节省了很多麻烦。
Paul Gowder,2015年

2
子类化HelpFormatter存在问题,因为argparse开发人员仅保证类名在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

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

其中的类别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

在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.