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

2025-03-05 09:17:00
admin
原创
98
摘要:问题描述:我argparse在 Python 2.7 中使用它来解析输入选项。我的一个选项是多项选择。我想在其帮助文本中创建一个列表,例如from argparse import ArgumentParser parser = ArgumentParser(description='test') pars...

问题描述:

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
"
         " a = alpha
"
         " b = beta
"
         " g = gamma
"
         " d = delta
"
         " 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
                  = 埃普西隆

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


解决方案 1:

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

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

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

解决方案 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
"
         " a = alpha
"
         " b = beta
"
         " g = gamma
"
         " d = delta
"
         " e = epsilon")

parser.parse_args()

任何其他对.add_argument()帮助未以此开头的调用R|都将照常进行包装。

这是我对 argparse 的改进之一。完整的 SmartFormatter 还支持为所有选项添加默认值,以及实用程序描述的原始输入。完整版本有自己的_split_lines方法,因此对版本字符串进行的任何格式化都会被保留:

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

解决方案 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('''\n        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)。我尝试使用以下方法将描述部分分成几行RawTextHelpFormatter

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

无论如何,需要自定义格式化程序。我扩展了现有的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) + '

'
            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 显示此帮助消息并退出

解决方案 5:

我承认我发现这是一个非常令人沮丧的经历,因为从我看到的解决方案数量和网络上问这个问题的次数来看,似乎很多人都有这种感觉。但我发现大多数这些解决方案对我来说太复杂了,我想分享我对此最简洁的解决方案。

以下是用于演示的脚本:

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

class RawFormatter(HelpFormatter):
    def _fill_text(self, text, width, indent):
        return "
".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 = "
".join(text)                # Join the lines again
        return text

但我自己更喜欢用一行来表达。

解决方案 6:

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
'
                          ' a = alpha
'
                          ' b = beta
'
                          ' g = gamma
'
                          ' d = delta
'
                          ' 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

解决方案 7:

从上面描述的 SmartFomatter 开始,我最终得到了该解决方案:

class SmartFormatter(argparse.HelpFormatter):
    '''
         Custom Help Formatter used to split help text when '
' 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_parsers 继承,必须为每个创建的 sub_parser 再次传递它。

解决方案 8:

我希望在描述文本中同时有手动换行符和自动换行;但这里的建议都不适合我 - 所以我最终修改了这里答案中给出的 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 '
'.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

解决方案 9:

前言

对于这个问题,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'
{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 *')} 
"
                                 "description: list current available template. (accept regex)")

    arg_parser.add_argument('--option', "-o", dest='option',
                            help='
'.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

在此处输入图片描述

解决方案 10:

迟到了 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)

解决方案 11:

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 = "
".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("

Demo for {}
".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



## 解决方案 12:

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




## 解决方案 13:

 
我来这里是为了寻找一种方法来实现换行符和制表符的行为`ArgumentDefaultsHelpFormatter`。Troy 的代码让我很接近,但最终结果要简单一些:


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 \n      to use on the board. For example: \n      
 ip=192.0.2.100 \n      
 subnet_mask=255.255.255.0 \n      
 gateway=192.0.2.1')



## 解决方案 14:

 
这是我发现的最简单的解决方案,既能自动将文本换行到终端,又能保持手动换行。归根结底,就是`textwrap.wrap()`用保留手动换行的版本替换默认格式化程序的调用。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   4027  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2755  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   86  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   99  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   87  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用