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

Posted

技术标签:

【中文标题】如何在 argparse 帮助文本上插入换行符?【英文标题】:How to insert newlines on argparse help text? 【发布时间】:2011-04-20 16:53:10 【问题描述】:

我使用argparse in 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 = ε

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

【问题讨论】:

我没有 python 2.7,所以我可以测试我的想法。如何在三引号 (""" """) 中使用帮助文本。使用这个新线是否可以生存? @pyfunc: 不。剥离是由argparse 在运行时完成的,而不是解释器,所以切换到"""...""" 将无济于事。 This 为我工作 【参考方案1】:

尝试使用RawTextHelpFormatter

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

【讨论】:

我认为不是。您可以对其进行子类化,但不幸的是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,并通过 easy_install 安装了 argparse,因此文档本身可能已过时。 一些链接:python 2.7 和 python 3.*。根据its wiki,2.6 包应该符合官方的 2.7 之一。来自文档:“将 RawDescriptionHelpFormatter 作为 formatter_class= 传递表示描述和结语已经正确格式化,不应换行” 改用 formatter_class=RawDescriptionHelpFormatter,它只适用于描述和结语,而不是帮助文本。 我注意到即使使用RawTextHelpFormatter,前导和尾随换行符也会被删除。要解决这个问题,您可以简单地添加两个或多个连续的换行符;除了一个换行符之外,其他所有行都将保留。 您也可以组合格式化程序,例如class Formatter( argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass 然后formatter_class=Formatter【参考方案2】:

如果您只想覆盖一个选项,则不应使用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| 开头的,将正常包装。

这是my improvements on 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 SmartFormatter 的完整版也有自己的_split_lines 并保留换行符(无需在开头指定“R|”,如果需要该选项,请修补@987654333 @方法 我并不完全理解您评论的第一部分,尽管我可以在_VersionAction.__call__ 中看到我可能希望它只是parser.exit(message=version) 而不是使用格式化版本。有没有办法在不发布 argparse 的修补副本的情况下做到这一点? @mc_electron 我指的是我在 bitbucket 上发布的改进(根据答案中我对 argparse 的改进的链接)。但是您也可以在定义def smart_version(self, parser, namespace, values, option_string=None): ... 之后通过执行argparse._VersionAction.__call__ = smart_version 来修补_VersionAction 中的__call__ 好主意。没有帮助我,因为结语和描述似乎没有贯穿 _split_lines :(【参考方案3】:

另一种简单的方法是添加 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 ...

【讨论】:

【参考方案4】:

我遇到过类似的问题(Python 2.7.6)。我尝试使用 RawTextHelpFormatterdescription 部分分解为几行:

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,他启发了正确的方向above。但该解决方案需要稍作修改才能格式化 description 部分。

无论如何,需要自定义格式化程序。我扩展了现有的 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 显示此帮助信息并退出

【讨论】:

这太棒了 --- 在几乎放弃并考虑完全重新实现帮助参数之后发生这种情况......为我节省了很多麻烦。 子类化 HelpFormatter 是有问题的,因为 argparse 开发人员只保证类名将在 argparse 的未来版本中继续存在。他们基本上给自己写了一张空白支票,因此如果方便的话,他们可以更改方法名称。我觉得这很令人沮丧;他们至少可以在 API 中公开一些方法。 不完全符合 OP 的要求,但正是我想要的,谢谢!【参考方案5】:

我承认我发现这是一次非常令人沮丧的经历,因为我看到许多其他人都有过这种经历,因为我看到发布的解决方案的数量以及我在网络上看到这个问题的次数。但我发现这些解决方案中的大多数对我来说都太复杂了,我想分享一下我所拥有的最简单的解决方案。

下面是演示脚本:

#!/usr/bin/python3
import textwrap
from argparse import ArgumentParser, HelpFormatter

class RawFormatter(HelpFormatter):
    def _fill_text(self, text, width, indent):
        return "\n".join([textwrap.fill(line, width) for line in textwrap.indent(textwrap.dedent(text), indent).splitlines()])

program_descripton = f'''
    FunkyTool v1.0

    Created by the Funky Guy on January 1 2020
    Copyright 2020. All rights reserved.

    Licensed under The Hippocratic License 2.1
    https://firstdonoharm.dev/

    Distributed on an "AS IS" basis without warranties
    or conditions of any kind, either express or implied.

    USAGE:
    '''

parser = ArgumentParser(description=program_descripton, formatter_class=RawFormatter)
args = parser.parse_args()

这是test.py中的样子:

$ ./test.py --help
usage: test.py [-h]

FunkyTool v1.0

Created by the Funky Guy on January 1 2020
Copyright 2020. All rights reserved.

Licensed under The Hippocratic License 2.1
https://firstdonoharm.dev/

Distributed on an "AS IS" basis without warranties
or conditions of any kind, either express or implied.

USAGE:

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

因此,原始描述中的所有基本格式都被整齐地保留下来,唉,我们不得不使用自定义格式器,但它是一个单行器。可以更清楚地写成:

class RawFormatter(HelpFormatter):
    def _fill_text(self, text, width, indent):
        text = textwrap.dedent(text)          # Strip the indent from the original python definition that plagues most of us.
        text = textwrap.indent(text, indent)  # Apply any requested indent.
        text = text.splitlines()              # Make a list of lines
        text = [textwrap.fill(line, width) for line in text] # Wrap each line 
        text = "\n".join(text)                # Join the lines again
        return text

但我自己更喜欢一行。

【讨论】:

【参考方案6】:

从上面描述的 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_parser 继承,必须为每个创建的 sub_parser 再次传递它。

【讨论】:

【参考方案7】:

我想在描述文本中手动换行,并自动换行;但这里的任何建议都不适合我——所以我最终修改了这里的答案中给出的 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

【讨论】:

【参考方案8】:

前言

对于这个问题,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'\ngreen_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:flagint_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

【讨论】:

【参考方案9】:

如果存在,则以下 python 3 格式化程序附加默认值并保留行长。

from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, \ 
                     RawTextHelpFormatter
import textwrap

class CustomArgumentFormatter(ArgumentDefaultsHelpFormatter, RawTextHelpFormatter):
    """Formats argument help which maintains line length restrictions as well as appends default value if present."""

    def _split_lines(self, text, width):
        text = super()._split_lines(text, width)
        new_text = []

        # loop through all the lines to create the correct wrapping for each line segment.
        for line in text:
            if not line:
                # this would be a new line.
                new_text.append(line)
                continue

            # wrap the line's help segment which preserves new lines but ensures line lengths are
            # honored
            new_text.extend(textwrap.wrap(line, width))

        return new_text

然后使用新的格式化程序创建参数解析器:

my_arg_parser = ArgumentParser(formatter_class=CustomArgumentFormatter)
# ... add your arguments ...
print(my_arg_parser.format_help())

【讨论】:

【参考方案10】:

另一种使用RawTextHelpFormatter 获取新行并处理缩进的简单方法是

import argparse

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

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

输出是

$ python2 x.py -h
usage: x.py [-h] [-g a,b,g,d,e]

test

optional arguments:
  -h, --help      show this help message and exit
  -g a,b,g,d,e  Some option, where
                   a = alpha
                   b = beta
                   g = gamma
                   d = delta
                   e = epsilon

【讨论】:

以上是关于如何在 argparse 帮助文本上插入换行符?的主要内容,如果未能解决你的问题,请参考以下文章

java中使用pdfbox对pdf文件进行操作时,如何实现插入文本的自动换行操作?

如何在视频的 vtt 中强制换行

如何在textarea文本输入区内实现换行

如何强制换行表条目

如何使在使用word制表格时自动换行但不增加行距???

Oracle PL/SQL查询结果如何自动换行