Аргумент Python: Як вставити новий рядок у довідковий текст?


341

Я використовую 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знімає всі нові рядки та послідовні пробіли. Результат виглядає приблизно так

~ / Завантажень: 52 $ python2.7 x.py -h
використання: x.py [-h] [-g {a, b, g, d, e}]

тест

необов'язкові аргументи:
  -h, --допоможіть показати це довідкове повідомлення та вийти
  -g {a, b, g, d, e} Деякий варіант, де a = alpha b = beta g = gamma d = delta e
                  = епсилон

Як вставити нові рядки в довідковий текст?


У мене немає python 2.7, щоб я міг перевірити свої ідеї. Як щодо використання довідкового тексту в потрійних лапки ("" "" ""). Чи виживають нові лінії, використовуючи це?
pyfunc

4
@pyfunc: Ні. Знімання проводиться під час виконання argparseне інтерпретатором, тому переключення """..."""не допоможе.
kennytm

Це спрацювало для мене
кардамон

Відповіді:


394

Спробуйте скористатися RawTextHelpFormatter:

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

6
Я думаю, що це не так. Ви можете підкласифікувати його, але, на жаль, 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 пітона, і ви, як очікується, переробляєте багато речей для 3.x у будь-якому випадку. Насправді я працюю 2.6 із argparseвстановленим через, easy_installщоб документація сама по собі застаріла.
інтуїтивно

3
Деякі посилання: для python 2.7 та python 3. * . Пакет 2,6 повинен, в відповідно до його вики , відповідають офіційним 2.7 один. З документа: "Передача RawDescriptionHelpFormatter як formatter_class = вказує, що опис та епілог вже правильно відформатовані та не повинні бути оберненими рядками"
Стефано,

83
Спробуйте замість formatter_class = RawDescriptionHelpFormatterякий працює лише на опис та епілог, а не на текст довідки.
MarkHu

3
Я помітив, що навіть з RawTextHelpFormatter, провідні та кінцеві нові рядки видаляються. Щоб обійти це, можна просто додати два чи більше послідовних нових рядків; всі, крім однієї нової лінії, виживуть.
MrMas

11
Ви також можете комбінувати формати, наприклад, class Formatter( argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): passі потім formatter_class=Formatter.
Террі Браун

79

Якщо ви просто хочете змінити один варіант, не слід використовувати RawTextHelpFormatter. Натомість підклас the HelpFormatterта надайте спеціальне вступ для параметрів, які слід обробляти "raw" (я використовую "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|будуть завершені як звичайні.

Це частина моїх удосконалень щодо аргументації . Повний SmartFormatter також підтримує додавання значень за замовчуванням до всіх параметрів та необмеженого введення опису утиліт. Повна версія має власний _split_linesметод, так що будь-яке форматування, зроблене, наприклад, рядки версії, зберігається:

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

Я хочу зробити це для повідомлення версії, але цей SmartFormatter, здається, працює лише з довідковим текстом, а не зі спеціальним текстом версії. parser.add_argument('-v', '--version', action='version',version=get_version_str()) Чи можна поширити це на той випадок?
mc_electron

@mc_electron повна версія SmartFormatter також має власні _split_linesі зберігає розриви рядків (не потрібно вказувати "R |" на початку, якщо ви хочете, щоб цей варіант, виправити _VersionAction.__call__метод
Anthon

Я не зовсім балакаю першу частину вашого коментаря, хоча я можу побачити, _VersionAction.__call__що я, мабуть, хотів би, щоб це просто parser.exit(message=version)замість того, щоб використовувати відформатовану версію. Чи є спосіб зробити це, не випускаючи виправлену копію argparse?
mc_electron

@mc_electron Я маю на увазі покращення, які я опублікував у bitbucket (відповідно до посилання на мої вдосконалення щодо argparse у відповіді). Але ви також можете пропатчити __call__в _VersionActionроблячи argparse._VersionAction.__call__ = smart_versionпісля визначенняdef smart_version(self, parser, namespace, values, option_string=None): ...
Anthon

Чудова ідея. Не допомогли мені як епілог і опис , здається, не проходять через _split_lines :(
стручок

31

Ще один простий спосіб зробити це - включити текстове обгортання .

Наприклад,

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

11

Я стикався з подібною проблемою (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, --допоможіть показати це довідкове повідомлення та вийти

Так що 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) + '\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()

вихід:

використання: play-with-argparse.py [ВАРІАНТИ]

Перший абзац

Абзац другий

Абзац третій

необов'язкові аргументи:
  -h, --допоможіть показати це довідкове повідомлення та вийти

1
Це чудово --- трапилось на цьому після майже відмови від роздумів і взагалі повторного втілення аргументу довідки ... врятувало мені велику кількість клопотів.
Пол Гоудер

2
підкласифікація HelpFormatterє проблематичною, оскільки розробники argparse гарантують лише те, що ім'я класу збережеться в майбутніх версіях argparse. В основному вони самі записали порожній чек, щоб вони могли змінити назви методів, якщо їм це буде зручно. Мені це здається неприємним; найменше, що вони могли зробити, це відкрити кілька методів в API.
MrMas

Не зовсім те, що просила ОП, але саме те , що я хотів, дякую!
Х'ю Уолтерс

2

Мені хотілося, щоб в тексті опису були як ручні розриви рядків, так і автоматичне загортання його; але жодна з пропозицій тут не працювала для мене - тому я змінив клас 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

1

Починаючи з описаного вище 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_parsers, його потрібно передати знову для кожного створеного sub_parser.


0

Передмова

Для цього питання 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

введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.