Сортировать argparse help по алфавиту

Я использую средство Python (2.7) argparse и хотел бы автоматически сортировать справку, которую он производит, в алфавитном порядке по параметрам.

По умолчанию записи справки сортируются в порядке их добавления*, например:

p = argparse.ArgumentParser(description='Load duration curves and other plots')
p.add_argument('--first', '-f', type=int, default=1, help='First Hour')
p.add_argument('--dur', '-d', type=int, default=-1, help='Duration in Hours. Use -1 for all')
p.add_argument('--title', '-t', help='Plot Title (for all plots), default=file name')
p.add_argument('--interp', '-i', action="store_true", default=True, 
                help='Use linear interpolation for smoother curves')
...
args = p.parse_args()

Который при вызове python script -h производит:

usage: script.py [-h] [--first FIRST] [--dur DUR] [--title TITLE] [--interp]

Load duration curves and other plots

optional arguments:
  -h, --help            show this help message and exit
  --first FIRST, -f FIRST
                        First Hour
  --dur DUR, -d DUR     Duration in Hours. Use -1 for all
  --title TITLE, -t TITLE
                        Plot Title (for all plots), default=file name
  --interp, -i          Use linear interpolation for smoother curves

Можно ли вместо этого автоматически сортировать их по алфавиту? Это будет dur, first, h, interp, title.

* Очевидно, обходной путь заключается в ручном обслуживании путем добавления записей с использованием p.add_argument в алфавитном порядке добавления, но я стараюсь этого не делать.


person Bryan P    schedule 04.09.2012    source источник
comment
Я думаю, вы можете подключить p.show_help или что-то в этом роде и вручную проанализировать список аргументов... Я посмотрю, смогу ли я найти документы по нему...   -  person Joran Beasley    schedule 04.09.2012


Ответы (5)


Это можно сделать, предоставив собственный HelpFormatter класс; внутренности которого официально не задокументированы. Это означает, что вы сами по себе, когда дело доходит до совместимости от версии к версии Python, но я считаю интерфейс довольно стабильным:

from argparse import HelpFormatter
from operator import attrgetter

class SortingHelpFormatter(HelpFormatter):
    def add_arguments(self, actions):
        actions = sorted(actions, key=attrgetter('option_strings'))
        super(SortingHelpFormatter, self).add_arguments(actions)


p = argparse.ArgumentParser(...
    formatter_class=SortingHelpFormatter,
)

Здесь я сортирую по строкам опций (('--dur', '-d') и т. д.), но вы можете сами выбрать, по чему сортировать. Этот простой параметр сортировки ставит варианты с одним тире последними, например параметр -h.

который выводит:

usage: [-h] [--first FIRST] [--dur DUR] [--title TITLE] [--interp]

Load duration curves and other plots

optional arguments:
  --dur DUR, -d DUR     Duration in Hours. Use -1 for all
  --first FIRST, -f FIRST
                        First Hour
  --interp, -i          Use linear interpolation for smoother curves
  --title TITLE, -t TITLE
                        Plot Title (for all plots), default=file name
  -h, --help            show this help message and exit
person Martijn Pieters    schedule 04.09.2012
comment
super(HelpFormatter должно быть super(SortingHelpFormatter - person jterrace; 04.09.2012
comment
Используется ли когда-либо вызов add_argument (единственное число)? - person grieve; 04.09.2012
comment
@grieve: Да, он используется методом HelpFormatter.add_arguments(), который я отменил. - person Martijn Pieters; 04.09.2012
comment
@MartijnPieters Ну, это имеет смысл. Просто интересно, обходит ли когда-нибудь недокументированный API add_arguments в пользу add_argument. - person grieve; 04.09.2012
comment
@grieve: В настоящее время это не так. - person Martijn Pieters; 04.09.2012
comment
Почему help всегда оказывается последним? В моем решении этого нет, и я ожидал, что они будут примерно эквивалентны... - person mgilson; 04.09.2012
comment
Объяснение было достаточно ясным. Я должен был понять это. Мне нравится сортировать по dest больше, чем по option_strings, но я полагаю, что это совершенно произвольно... Вы часто возитесь с внутренностями argparse? Мне потребовалось довольно много времени, чтобы найти решение, которое не так хорошо, как ваше ... Кроме того, знаете ли вы о каких-либо планах когда-либо раскрывать эти функции в API? Поскольку это чистый python, я бы не подумал, что другие реализации python, делающие это по-другому, будут серьезной проблемой... - person mgilson; 04.09.2012
comment
@mgilson: Нет, я не часто в этом заморачиваюсь, но я взял общую структуру из предыдущих набегов. Я понятия не имею о дальнейших планах модуля. - person Martijn Pieters; 04.09.2012

При создании класса ArgumentParser вы можете передать средство форматирования справки: http://docs.python.org/library/argparse.html#formatter-class

Таким образом, очевидно, вы можете использовать один из поставляемых форматтеров, но не можете переопределить и заменить их без реверс-инжиниринга:

>>> h = argparse.ArgumentDefaultsHelpFormatter
>>> print h.__doc__
Help message formatter which adds default values to argument help.

    Only the name of this class is considered a public API. All the methods
    provided by the class are considered an implementation detail.
person grieve    schedule 04.09.2012
comment
Я часто думал, что больше этого должно быть доступно пользователю :-/ (кроме предоставления нескольких средств форматирования и, по сути, запрета пользователю каким-либо образом возиться с ними) - я думаю, это потому, что код довольно уродлив и сложно писать, и разработчики argparse не хотели подвергать пользователя этому. - person mgilson; 04.09.2012
comment
Что иронично, поскольку argparse был мотивирован уродством и сложностью расширения кодовой базы optparse. - person chepner; 06.09.2012

Альтернативный, определенно более уродливый способ сделать это, чем предложенный @MartijnPieters:

p = argparse.ArgumentParser()

#add arguements here

for g in p._action_groups:
    g._group_actions.sort(key=lambda x:x.dest)

Может быть, было бы неплохо поместить это в предложение try/except, так как это только помощь в форматировании, поэтому для выполнения программы не должно иметь большого значения, если этот фрагмент кода не работает на AttributeError или что-то в этом роде...

person mgilson    schedule 04.09.2012
comment
Это то, о чем я тоже думал, и, похоже, это сработало. Но мне было неудобно предлагать это, потому что мы не можем легко сказать, какие побочные эффекты может иметь изменение порядка p._group_actions. А как насчет групп действий в p._actions? Может ли порядок в них быть каким-то образом связан с этим и его тоже нужно изменить? Так что не голосовать за ответ, а мысленно +1 за раскопки кода и значение взлома ;-) - person Lukas Graf; 04.09.2012
comment
@LukasGraf -- я не гарантирую, что это решение ничего не испортит, но думаю, что оно довольно безопасно. Кажется, что все анализируется с использованием словарей, которые в любом случае неупорядочены (мне кажется, что порядок сохраняется только с целью форматирования справочного сообщения). Я пробовал сортировать p._actions, но это, похоже, не имеет никакого эффекта... - person mgilson; 04.09.2012
comment
Если все, что вы хотите изменить, это порядок строк справки, то логично отсортировать списки _group_actions. - person hpaulj; 28.09.2015

Это похоже на ответ @mgilson. Я думал, что уже публиковал это раньше, но, видимо, нет.

d = dict()
d['--first'] = ('-f', "type=int", "default=1", "help='First Hour'")
d['--dur'] = ('-d', type=int, default=-1, help='Duration in Hours. Use -1 for all')
# etc

for prim_option in sorted(d):
    p.add_arguments(prim_option, *d[prim_option])

Вы можете настроить то, что именно используется в качестве ключа в словаре, а также аргументы sorted и точную структуру вызова add_arguments, чтобы получить желаемый порядок сортировки. Это соответствует общедоступному документированному интерфейсу argparse, но добавляет слой к процессу определения вашего синтаксического анализатора. (В зависимости от вашей философии такое отделение информации об опциях от реализации парсера может быть полезным.)

person chepner    schedule 05.09.2012

Порядок аргументов в справке определяется методом parser.format_help:

Definition:  parser.format_help(self)
Source:
    def format_help(self):
        formatter = self._get_formatter()
        ...
        # positionals, optionals and user-defined groups
        for action_group in self._action_groups:
            formatter.start_section(action_group.title)
            formatter.add_text(action_group.description)
            formatter.add_arguments(action_group._group_actions)
            formatter.end_section()

help создается путем извлечения объекта formatter и последующего добавления к нему «разделов». Здесь он перебирает _action_groups, помещая каждый в свой раздел и добавляя свои действия (аргументы) с помощью метода add_arguments. Средство форматирования является временным и существует только для создания строк (обычно нескольких строк).

Группы действий включают стандартные postionals и optionals, а также все, что создает пользователь. Эти группы используются только для справки, а не для синтаксического анализа. Таким образом, список action_group._group_actions можно было переупорядочить, не влияя на синтаксический анализ. (парсер имеет свой список действий, parser._actions).

Это подтверждает наблюдение @mgilson о том, что сортировка p._actions не влияет на справку, а сортировка _group_actions влияет.

Сортировка _actions повлияет на usage (будь то часть справки или отдельно):

    # usage
    formatter.add_usage(self.usage, self._actions,
                        self._mutually_exclusive_groups)

Обратите внимание, что action_groups не передаются в раздел использования. Раздел использования меняет порядок своих действий, отображая сначала optionals, затем positionals.

Сортируйте аргументы до/во время этапа add_argument, если вы хотите контролировать порядок синтаксического анализа позиционных выражений и их порядок в использовании.

Если вы просто хотите контролировать порядок в группах помощи, не стесняйтесь переупорядочивать элементы в списке ._group_actions либо перед вызовом средства форматирования, либо внутри него.

Были и другие вопросы SO об управлении порядком действий в файле usage. Некоторые, например, не хотят, чтобы positionals располагалось после optionals.

Я согласен, что класс Formatter громоздок. Но по большей части он отделен от класса Parser. Таким образом, его можно было бы переписать с минимальным влиянием на синтаксический анализ. Существующие подклассы Formatter просто настраивают низкоуровневые методы, которые управляют переносом строк и форматированием строк справки. Важным интерфейсом между синтаксическим анализатором и средством форматирования являются методы format_usage и format_help, которые являются относительно более простыми и высокоуровневыми.

подклассы

Несмотря на предупреждение, которое цитирует @grieve, люди создают подклассы HelpFormatter в соответствии со своими потребностями. Единственное, что удерживает людей от этого, — это какая-то политика компании. Все предупреждение говорит нам о том, что разработчики argparse не пытались представить или задокументировать все изменения, которые пользователи могут захотеть внести. Я даже не смог перечислить те, которые я предложил за последние несколько лет.

person hpaulj    schedule 27.09.2015