Создание компонуемых / иерархических парсеров командной строки с использованием Python/argparse

(Упрощенная форма проблемы.) Я пишу API, включающий некоторые компоненты Python. Это могут быть функции, но для конкретности, скажем, они объекты. Я хочу иметь возможность анализировать параметры для различных компонентов из командной строки.

from argparse import ArgumentParser

class Foo(object):
    def __init__(self, foo_options):
        """do stuff with options"""

    """..."""

class Bar(object):
    def __init__(sef, bar_options):
        """..."""

def foo_parser():
    """(could also be a Foo method)"""
    p = ArgumentParser()
    p.add_argument('--option1')
    #...
    return p

def bar_parser(): "..."

Но теперь я хочу иметь возможность создавать более крупные компоненты:

def larger_component(options):
    f1 = Foo(options.foo1)
    f2 = Foo(options.foo2)
    b  = Bar(options.bar)
    # ... do stuff with these pieces

Хорошо. Но как написать соответствующий парсер? Мы могли бы пожелать что-то вроде этого:

def larger_parser(): # probably need to take some prefix/ns arguments
    # general options to be overridden by p1, p2
    # (this could be done automagically or by hand in `larger_component`):
    p  = foo_parser(prefix=None,          namespace='foo')
    p1 = foo_parser(prefix='first-foo-',  namespace='foo1')
    p2 = foo_parser(prefix='second-foo-', namespace='foo2')
    b  = bar_parser()
    # (you wouldn't actually specify the prefix/namespace twice: )
    return combine_parsers([(p1, namespace='foo1', prefix='first-foo-'),
                            (p2,...),p,b])

larger_component(larger_parser().parse_args())
# CLI should accept --foo1-option1, --foo2-option1, --option1  (*)

который выглядит как argparse"s parents Если вы забудете, что мы хотим использовать префиксы (чтобы можно было добавлять несколько парсеров одного типа) и, возможно, пространство имен (чтобы мы могли строить пространства имен с древовидной структурой, отражающие структуру компонентов).

Конечно, мы хотим, чтобы более крупные_компоненты и более крупные_парсеры можно было компоновать одинаково, а объект пространства имен, передаваемый определенному компоненту, должен всегда иметь одинаковую внутреннюю структуру формы / именования.

Беда в том, что argparse API в основном заключается в мутировании ваших парсеров, но запрашивать их сложнее - если вы превратили тип данных в парсер напрямую, вы можете просто пройтись по этим объектам. Мне удалось взломать что-то, что несколько работает, если пользователь пишет кучу функций для добавления аргументов в парсеры вручную, но каждый add_argument call должен затем взять префикс, и все это становится довольно непостижимым и, вероятно, несложным. (Вы можете абстрагироваться от этого за счет дублирования некоторых частей внутренних структур данных...). Я также пытался подкласс parser а также group объекты...

Вы можете представить, что это возможно при использовании более алгебраического API-интерфейса для анализа CLI, но я не думаю, что переписать argparse здесь хорошее решение

Есть ли известный / простой способ сделать это?

2 ответа

Некоторые мысли, которые могут помочь вам создать больший парсер:

parser = argparse.ArgumentParser(...)
arg1 = parser.add_argument('--foo',...)

Сейчас arg1 это ссылка на Action объект, созданный add_argument, Я бы предложил сделать это в интерактивной оболочке и посмотреть на ее атрибуты. Или хотя бы распечатать его repr, Вы также можете поэкспериментировать с изменением атрибутов. Большая часть того, что парсер "знает" об аргументах, содержится в этих actions, В некотором смысле парсер - это объект, который "содержит" кучу "действий".

Посмотрите также на:

parser._actions

Это основной список действий парсера, который будет включать справку по умолчанию, а также те, которые вы добавляете.

parents механизм копирования Action ссылки от родителя к ребенку. Обратите внимание, что он не делает копии объектов Action. Он также воссоздает группы аргументов - но эти группы служат только для группировки строк справки. Они не имеют ничего общего с разбором.

args1, extras = parser.parse_known_args(argv, namespace)

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

Мы говорили о композитных Namespace объекты в более ранних ТАК вопросах. По умолчанию argparse.Namespace класс является простым классом объекта с repr метод. Парсер просто использует hasattr, getattr а также setattrпытаясь быть настолько неспецифичным, насколько это возможно. Вы можете создать более сложный класс пространства имен.

подкоманды argparse с вложенными пространствами имен

Вы также можете настроить Action классы. Вот где большинство значений вставляются в пространство имен (хотя значения по умолчанию установлены в другом месте).

IPython использования argparse, как для основного звонка, так и внутри для magic команды. Он строит много аргументов из config файлы. Таким образом, можно установить много значений либо с помощью стандартных настроек, пользовательских настроек, либо в последний момент с помощью аргументов командной строки.

Возможно, вы сможете использовать концепцию составления действий для достижения необходимой вам функциональности. Вы можете создавать действия, которые изменяют пространство имен, dest и т. Д. По мере необходимости, а затем составлять их с помощью:

def compose_actions(*actions):
    """Compose many argparse actions into one callable action.

    Args:
        *actions: The actions to compose.

    Returns:
        argparse.Action: Composed action.
    """
    class ComposableAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            for action in actions:
                action(option_string, self.dest).__call__(parser,
                                                          namespace,
                                                          values,
                                                          option_string)
    return ComposableAction

Смотрите пример: https://gist.github.com/mnm364/edee068a5cebbfac43547b57b7c842f1

Другие вопросы по тегам