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

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


尝试使用RawTextHelpFormatter

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


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


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

例如,

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


我想在描述文本中手动换行,并自动包装它;但是这里的建议对我来说都不起作用-所以我最终修改了这里答案中给出的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