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

我使用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将删除所有换行符和连续空格。结果如下所示

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

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

134056 次浏览

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

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

它类似于RawDescriptionHelpFormatter,但不仅适用于description和epilog, RawTextHelpFormatter还适用于所有帮助文本(包括参数)。

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

我也遇到过类似的问题(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()

和有:

usage: play-with-argparse.py [OPTIONS]


First paragraph


Second paragraph


Third paragraph


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

所以RawTextHelpFormatter不是一个解决方案。因为它打印源代码中出现的描述,保留所有空白字符(为了可读性,我想在源代码中保留额外的制表符,但我不想全部打印它们。此外,raw格式化器不会在行太长时换行,例如超过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()

输出结果为:

usage: play-with-argparse.py [OPTIONS]


First paragraph


Second paragraph


Third paragraph


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

另一种简单的方法是包含< a href = " https://docs.python.org/2/library/textwrap.html " > 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 ...

我想在描述文本中有手动换行符,并自动换行;但是这里没有一个建议对我有用-所以我最终修改了答案中给出的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

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

前言

对于这个问题,argparse.RawTextHelpFormatter对我很有帮助。

现在,我想分享如何使用argparse

我知道这可能和问题无关,

但这些问题已经困扰我一段时间了。

所以我想分享我的经验,希望对别人有所帮助。

开始吧。

第三方模块

彩色光:用于更改文本颜色: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

enter image description here

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

下面是演示的脚本:

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

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

如果存在默认值,以下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())

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


我来这里寻找方法来获得ArgumentDefaultsHelpFormatter的行为,但有换行符和制表符。特洛伊的代码让我接近了,但最终的结果更简单一些:

class CustomArgumentFormatter(argparse.ArgumentDefaultsHelpFormatter):
"""
Formats help text to honor newlines and tabs (and show default values).
"""


# Match multiples of regular spaces only.
_SPACE_MATCHER = re.compile(r' +', re.ASCII)


def _split_lines(self, text, width):
new_text = []
for line in text.splitlines():
# For each newline in the help message, replace any multiples of
# whitespaces (due to indentation in source code) with one space.
line = self._SPACE_MATCHER.sub(' ', line).rstrip()
# Fit the line length to the console width
new_text.extend(textwrap.wrap(line, width))
return new_text

然后换行符和制表符将按预期出现:

parser = argparse.ArgumentParser(formatter_class=CustomArgumentFormatter)


parser.add_argument(
'--ethernet_config', type=str, required=False, default=None,
help='Path to a text file that specifies Ethernet network IP settings \
to use on the board. For example: \
\n\t ip=192.0.2.100 \
\n\t subnet_mask=255.255.255.0 \
\n\t gateway=192.0.2.1')

迟到了12年,但我也需要这个。

OP要求在帮助(不是描述)中添加新行,因此这里的解决方案实际上不完全工作,因为如果一行比屏幕宽度长,那么它在包装时失去缩进(被包装到列1而不是保留帮助文本的缩进),这看起来真的很难看,或者空行被占用,这是我不想要的,因为我有时需要在长帮助文本中使用空行。

工作方案如下:

import textwrap


class CustomHelpFormatter(argparse.ArgumentDefaultsHelpFormatter):
def _split_lines(self, text, width):
wrapper = textwrap.TextWrapper(width=width)
lines = []
for line in text.splitlines():
if len(line) > width:
lines.extend(wrapper.wrap(line))
else:
lines.append(line)
return lines


parser = argparse.ArgumentParser(formatter_class=CustomArgumentFormatter)

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
```