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

Python argparse: How to insert newline in the help text?

我在python 2.7中使用argparse来解析输入选项。我的选择之一是多项选择。我想在它的帮助文本中列出一个列表,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from argparse import ArgumentParser

parser = ArgumentParser(description='test')

parser.add_argument('-g', choices=['a', 'b', 'g', 'd', 'e'], default='a',
    help="Some option, where
"

        " a = alpha
"

        " b = beta
"

        " g = gamma
"

        " d = delta
"

        " e = epsilon")

parser.parse_args()

但是,argparse去掉了所有换行符和连续空格。结果看起来像

1
2
3
4
5
6
7
8
9
~/Downloads:52$ python2.7 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

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

  • 我没有python 2.7,所以我可以测试我的想法。如何使用三引号("")中的帮助文本。新的生产线能用这个生存吗?
  • @Pyfunc:不,剥离是在运行时由argparse完成的,而不是由解释器完成的,所以切换到"""..."""不会有帮助。
  • 这对我有用


尝试使用RawTextHelpFormatter

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

  • 很好,谢谢。是否可以只适用于1个选项?
  • 我想不是。您可以对它进行子类化,但不幸的是,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.*。根据其wiki,2.6包应符合官方的2.7包。来自文档:"将rawdescriptionHelpFormatter作为格式化程序传递"_class=表示说明和epilog已正确格式化,不应换行。"
  • 改为尝试格式化程序_class=RawDescriptionHelpFormatter,它只对描述和epilog有效,而不是帮助文本。
  • 我注意到,即使使用RawTextHelpFormatter,也会删除前后换行。为了解决这个问题,您可以简单地添加两个或多个连续的换行符;除了一个换行符之外,其他所有换行符都将继续存在。
  • 您也可以组合格式化程序,例如class Formatter( argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): passformatter_class=Formatter


如果您只想覆盖一个选项,则不应使用RawTextHelpFormatter。相反,将HelpFormatter子类化,并为应"原始"处理的选项提供一个特殊的介绍(我使用"R|rest of help"):

1
2
3
4
5
6
7
8
9
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)

并使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
"

        " a = alpha
"

        " b = beta
"

        " g = gamma
"

        " d = delta
"

        " e = epsilon")

parser.parse_args()

.add_argument()的任何其他呼叫,如果帮助不是从R|开始,将按正常方式进行包装。

这是我在argparse上改进的一部分。完整的SmartFormatter还支持添加默认为所有选项,以及实用程序说明的原始输入。完整版本有自己的_split_lines方法,因此对版本字符串等所做的任何格式化都将保留:

1
2
3
parser.add_argument('--version', '-v', action="version",
                    version="version...
   42!"
)

  • 我想为版本消息执行此操作,但此SmartFormatter似乎只用于帮助文本,而不用于特殊版本文本。parser.add_argument('-v', '--version', action='version',version=get_version_str())是否可以将其扩展到这种情况?
  • @mc electrone完整版的smartformatter也有自己的_split_lines并保留了换行符(不需要在开始时指定"r",如果您需要该选项,请修补_VersionAction.__call__方法
  • 我并不完全是在摸索你的评论的第一部分,尽管我可以在_VersionAction.__call__中看到,我可能希望它只是parser.exit(message=version),而不是使用格式化版本。但是,有没有办法在不释放argparse的补丁副本的情况下做到这一点呢?
  • @我指的是我在BitBucket上发布的改进(根据我在答案中对argparse的改进的链接)。但是你也可以在定义def smart_version(self, parser, namespace, values, option_string=None): ...之后,通过执行argparse._VersionAction.__call__ = smart_version来修补_VersionAction中的__call__
  • 好主意。没有帮助我,因为结尾和描述似乎没有贯穿于分界线:(
  • 嘿,这太好了!虽然完整版本的源代码中没有,但是在_split_lines顶部的注释中说# this is the RawTextHelpFormatter._split_lines有点误导性。如果您将注释移到if语句的下方,则该注释可能不太容易混淆,因为这是原始的RawTextHelpFormatter返回。不管怎样,这个答案给了我所需要的,再次感谢!
  • @SJM324感谢您指出,已更改。
  • 确实很喜欢这个解决方案,但是我觉得只有在帮助文本中找到新行时才应用它更为优雅,大致如下:class SmartFormatter(argparse.HelpFormatter): def _split_lines(self, text, width): if '
    ' in text: return text.splitlines() return argparse.HelpFormatter._split_lines(self, text, width)
    抱歉,不确定如何在答复中保留空格:)


另一个简单的方法是包括文本包装。

例如,

1
2
3
4
5
6
7
8
9
10
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 ... '''
))

这样,就可以避免在每个输出行前面都有很长的空白。

1
2
3
4
5
6
7
8
9
10
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 ...

我也遇到过类似的问题(python2.7.6)。我试着用RawTextHelpFormatter把描述部分分成几行:

1
2
3
4
5
6
7
8
9
parser = ArgumentParser(description="""First paragraph

                                       Second paragraph

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

options = parser.parse_args()

得到:

1
2
3
4
5
6
7
8
9
10
usage: play-with-argparse.py [OPTIONS]

First paragraph

                        Second paragraph

                        Third paragraph

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

所以RawTextHelpFormatter不是一个解决方案。因为它按源代码显示的方式打印描述,保留所有空白字符(为了可读性,我想在源代码中保留额外的制表符,但不想全部打印)。另外,原始格式设置工具在太长时不换行,例如超过80个字符)。

感谢@anton,他给了我们正确的方向。但为了格式化描述部分,该解决方案需要稍作修改。

无论如何,需要自定义格式化程序。我对现有的HelpFormatter类进行了扩展,采用了这样的方法:

1
2
3
4
5
6
7
8
9
10
11
12
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) + '

'

            multiline_text = multiline_text + formatted_paragraph
        return multiline_text

与来自argparse模块的原始源代码进行比较:

1
2
3
4
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)

在原始代码中,整个描述被包装起来。在上面的自定义格式化程序中,整个文本被分割成几个块,每个块都是独立格式化的。

因此,借助自定义格式化程序:

1
2
3
4
5
6
7
8
9
parser = ArgumentParser(description="""First paragraph
                                        |n                              
                                        Second paragraph
                                        |n
                                        Third paragraph"""
,  
                usage='%(prog)s [OPTIONS]',
                formatter_class=MultilineFormatter)

options = parser.parse_args()

输出是:

1
2
3
4
5
6
7
8
9
10
usage: play-with-argparse.py [OPTIONS]

First paragraph

Second paragraph

Third paragraph

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

  • 这太好了---发生在这之后,几乎放弃了,并考虑重新实现帮助的论点。省去了我很多麻烦。
  • 子类化HelpFormatter是有问题的,因为argparse开发人员只保证类名在argparse的未来版本中仍然存在。他们基本上给自己写了一个空白支票,这样他们可以在方便的情况下更改方法名。我觉得这很令人沮丧;至少他们可以在API中公开一些方法。
  • 不完全是手术要求的,但正是我想要的,谢谢!


我想在描述文本中手动换行,并自动包装它;但是这里的建议对我来说都不起作用-所以我最终修改了这里答案中给出的smartformatter类;尽管argparse方法名不是公共API,但这里是我所拥有的(作为名为test.py的文件())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 '
'
.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中的工作原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ 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