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

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会删除所有换行和连续的空格。结果如下所示

~/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

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


当前回答

从上面描述的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再次传递它。

其他回答

我承认我发现这是一个非常令人沮丧的经历,因为我看到很多解决方案,我在网上看到很多次这样的问题。但我发现这些解决方案对我来说太复杂了,我想分享我最简单的解决方案。

下面是演示的脚本:

#!/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

但我自己更喜欢写在一行上。

尝试使用RawTextHelpFormatter来保存所有的格式:

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

它类似于RawDescriptionHelpFormatter,但不是只应用于描述和epilog, RawTextHelpFormatter也应用于所有帮助文本(包括参数)。

Bernd的回答非常有用,但不适用于参数帮助字符串。下面是它的一个扩展,适用于所有的帮助文本(遵循RawTextHelpFormatter的例子)。

WrappedNewlineFormatter是他最初的RawFormatter, WrappedNewlineFormatter将额外包装参数。

import argparse
import textwrap

class DescriptionWrappedNewlineFormatter(argparse.HelpFormatter):
    """An argparse formatter that:
    * preserves newlines (like argparse.RawDescriptionHelpFormatter),
    * removes leading indent (great for multiline strings),
    * and applies reasonable text wrapping.

    Source: https://stackoverflow.com/a/64102901/79125
    """
    def _fill_text(self, text, width, indent):
        # Strip the indent from the original python definition that plagues most of us.
        text = textwrap.dedent(text)
        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


class WrappedNewlineFormatter(DescriptionWrappedNewlineFormatter):
    """An argparse formatter that:
    * preserves newlines (like argparse.RawTextHelpFormatter),
    * removes leading indent and applies reasonable text wrapping (like DescriptionWrappedNewlineFormatter),
    * applies to all help text (description, arguments, epilogue).
    """
    def _split_lines(self, text, width):
        # Allow multiline strings to have common leading indentation.
        text = textwrap.dedent(text)
        text = text.splitlines()
        lines = []
        for line in text:
            wrapped_lines = textwrap.fill(line, width).splitlines()
            lines.extend(subline for subline in wrapped_lines)
            if line:
                lines.append("")  # Preserve line breaks.
        return lines


if __name__ == "__main__":
    def demo_formatter(formatter):
        parser = argparse.ArgumentParser(
            description="""
                A program that does things.
                Lots of description that describes how the program works.

                very long lines are wrapped. very long lines are wrapped. very long lines are wrapped. very long lines are wrapped. very long lines are wrapped. very long lines are wrapped.

                existing wrapping will be preserved if within width. existing
                wrapping is preserved. existing wrapping will be preserved.
                existing wrapping is preserved. existing wrapping will be
                preserved. existing wrapping is preserved. existing wrapping
                will be preserved. existing wrapping is preserved unless it goes too long for the display width.
                """,
            formatter_class=formatter,
        )
        parser.add_argument(
            "--option",
            choices=[
                "red",
                "blue",
            ],
            help="""
                Lots of text describing different choices.
                    red: a warning colour
                    text on the next line

                    blue: a longer blah blah keeps going going going going going going going going going going
                """,
        )
        print("\n\nDemo for {}\n".format(formatter.__name__))
        parser.print_help()

    demo_formatter(DescriptionWrappedNewlineFormatter)
    demo_formatter(WrappedNewlineFormatter)

WrappedNewlineFormatter的演示输出

usage: arg.py [-h] [--option {red,blue}]

A program that does things.
Lots of description that describes how the program works.

very long lines are wrapped. very long lines are wrapped. very long lines are
wrapped. very long lines are wrapped. very long lines are wrapped. very long
lines are wrapped.

existing wrapping will be preserved if within width. existing
wrapping is preserved. existing wrapping will be preserved.
existing wrapping is preserved. existing wrapping will be
preserved. existing wrapping is preserved. existing wrapping
will be preserved. existing wrapping is preserved unless it goes too long for
the display width.

optional arguments:
  -h, --help           show this help message and exit
                       
  --option {red,blue}  Lots of text describing different choices.
                       
                           red: a warning colour
                       
                           text on the next line
                       
                           blue: a longer blah blah keeps going going going
                       going going going going going going going
                       

DescriptionWrappedNewlineFormatter的演示输出

usage: arg.py [-h] [--option {red,blue}]

A program that does things.
Lots of description that describes how the program works.

very long lines are wrapped. very long lines are wrapped. very long lines are
wrapped. very long lines are wrapped. very long lines are wrapped. very long
lines are wrapped.

existing wrapping will be preserved if within width. existing
wrapping is preserved. existing wrapping will be preserved.
existing wrapping is preserved. existing wrapping will be
preserved. existing wrapping is preserved. existing wrapping
will be preserved. existing wrapping is preserved unless it goes too long for
the display width.

optional arguments:
  -h, --help           show this help message and exit
  --option {red,blue}  Lots of text describing different choices. red: a
                       warning colour text on the next line blue: a longer
                       blah blah keeps going going going going going going
                       going going going going
                       ```

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

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