2016-06-28 1 views
1

У меня есть два класса: A и B, каждый из которых имеет свой собственный парсер аргументов, определенный (с помощью argparse) Теперь я хочу добавьте функциональность в A, чтобы он вызывал класс B. Я делаю это, используя композицию (например, A имеет экземпляр объекта B)Как получить аргументы для двух разных объектов из командной строки при конфликте имен в Python argparse

Я спросил здесь, как объединить два объекта анализа аргумента arg, так что argparseA теперь будет включать аргументы в argparseB в вопросе Can two Python argparse objects be combined? Моя проблема заключается в следующем: оба A и B имеют аргументы с одинаковыми именами. BUT- Мне нужно ввести два разных значения для пользователя (т. Е. ArgpaseA.val1 должен получить значения argparseA.val1 и argParseB.val1)

(Очевидное решение переименовывает val1 либо в argparseA, либо в argpaseB, однако существует более 50 скриптов, которые уже наследуют класс A и 50 скриптов, которые наследуют класс B, поэтому я хочу, чтобы изменения A и B были как можно более минимальными.)

Я думал о добавлении нового именованного аргумента to argpaseA, называемый val2, который затем может быть передан в argparseB как val1.

Мой вопрос: какой правильный способ сделать такое преобразование или аргументы от argparseA до argparseB?

Или есть лучший способ спроектировать это?

+0

«собственно вещь "- это делать то, что имеет наибольшее значение для ваших пользователей. Дайте нам небольшой конкретный пример для начала. – hpaulj

+0

Я предложил различные альтернативы в связанном вопросе. Что вы используете? – hpaulj

+0

hpaulj- Я планировал использовать ваше «родительское» решение, но потом я увидел, что у меня все еще будет проблема с конфликтующими аргументами, поэтому я задал этот вопрос – MichalD

ответ

3

Я собираюсь предположить, что вы пытаетесь сделать мое предложение parents и проиллюстрировать, что может быть. Но даже если вы примете другой подход, это может помочь.

import argparse 

parserA=argparse.ArgumentParser() 
a1=parserA.add_argument('-f','--foo','--bar') 
print(a1) 
print() 

parserB=argparse.ArgumentParser() 
b1=parserB.add_argument('-g','--goo','--bar') 
print(b1) 
b1.dest='bar' # can change attributes like dest after creation 
print() 

# parser with parents; not the conflict_handler 
parserC=argparse.ArgumentParser(conflict_handler='resolve', 
    parents=[parserA, parserB]) 
print(parserC._actions) # the actions (arguments) of C 
print() 
parserA.print_help() 
print() 
parserC.print_help() # uses the C._actions 

который производит

1445:~/mypy$ python3 stack38071986.py 
_StoreAction(option_strings=['-f', '--foo', '--bar'], dest='foo',  
    nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None) 

_StoreAction(option_strings=['-g', '--goo', '--bar'], dest='goo', 
    nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None) 

[_StoreAction(option_strings=['-f', '--foo'], dest='foo', nargs=None, 
    const=None, default=None, type=None, choices=None, help=None, metavar=None), 
_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, 
    const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None), 
_StoreAction(option_strings=['-g', '--goo', '--bar'], dest='bar', 
    nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)] 

usage: stack38071986.py [-f FOO] 

optional arguments: 
    help    show this help message and exit 
    -f FOO, --foo FOO 

usage: stack38071986.py [-f FOO] [-h] [-g BAR] 

optional arguments: 
    -f FOO, --foo FOO 
    -h, --help   show this help message and exit 
    -g BAR, --goo BAR, --bar BAR 

Normal store поведения является хранение value на атрибуте dest в args пространства имен, setattr(namespace, action.dest, value). dest default - первая длинная строка опций (минус -), но может быть задана как параметр (для опций) или после создания.

Когда одна или несколько опций (флаг) нового действия конфликтуют с существующим действием, вызывается conflict_handler. Обработчик по умолчанию вызывает ошибку, но здесь я использую обработчик resolve. Он пытается удалить достаточно существующего аргумента для разрешения конфликта.

Все 3 анализатора создают действие -h (помощь). resolve в parserC удаляет все, кроме одного. Обратите внимание, что [-h] отсутствует в справке parserA.

--bar, определенный в parserB, конфликтует с той же строкой в ​​parserA. resolve удалил его из определения A, но -f и --foo остались.

Создание parserC испортило другие синтаксические анализаторы; поэтому я рекомендую использовать только parserC.parse_args() в этом прогоне.

Мы могли бы написать другой метод conflict_handler. resolve имеет некоторые грубые грани и часто не используется.

Я использую некоторые функции, которые не документированы. Некоторые считают, что это небезопасно. Но если вы хотите необычного поведения, вы должны принять некоторые риски. Кроме того, документация argparse не является последним словом для ее поведения, и ее легче изменить, чем сам код. При внесении изменений разработчики почти параноидальные по поводу обратных конфликтов.

==================

Вот удар при настройке обработчика resolve конфликта

import argparse 

def pp(adict): 
    for k in adict: 
     v=adict[k] 
     print('Action %10s:'%k,v.option_strings, v.dest) 

def new_resolve(self, action, conflicting_actions): 
    rename_dict={'--var':'--var1'} 
    for option_string, action in conflicting_actions: 
     new_string = rename_dict.get(option_string, None) 
     if new_string: 
      # rename rather than replace 
      print(action.option_strings) 
      action.option_strings = [new_string] 
      action.dest = new_string[2:] 

      pp(self._option_string_actions) 
      a1=self._option_string_actions.pop(option_string, None) 
      print(a1) 
      self._option_string_actions[new_string] = a1 
      pp(self._option_string_actions) 

     else: 
      # regular remove action 
      action.option_strings.remove(option_string) 
      self._option_string_actions.pop(option_string, None) 

      # if the option now has no option string, remove it from the 
      # container holding it 
      if not action.option_strings: 
       action.container._remove_action(action) 

argparse._ActionsContainer._handle_conflict_resolve=new_resolve 

parserA=argparse.ArgumentParser() 
a1=parserA.add_argument('-f','--foo') 
a1=parserA.add_argument('--var') 

parserB=argparse.ArgumentParser() 
b1=parserB.add_argument('-g','--goo') 
b1=parserB.add_argument('--var') 

parserC=argparse.ArgumentParser(conflict_handler='resolve', 
    parents=[parserA, parserB], 
    add_help=False) 

parserA.print_help() 
print() 
parserC.print_help() 
print(parserC.parse_args()) 

который производит

1027:~/mypy$ python3 stack38071986.py --var1 1 --var 3 
['--var'] 
Action  --var: ['--var1'] var1 
Action   -g: ['-g', '--goo'] goo 
Action  --foo: ['-f', '--foo'] foo 
Action   -h: ['-h', '--help'] help 
Action  --goo: ['-g', '--goo'] goo 
Action  --help: ['-h', '--help'] help 
Action   -f: ['-f', '--foo'] foo 
_StoreAction(option_strings=['--var1'], dest='var1', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None) 
Action   -g: ['-g', '--goo'] goo 
Action  --var1: ['--var1'] var1 
Action  --foo: ['-f', '--foo'] foo 
Action   -h: ['-h', '--help'] help 
Action  --goo: ['-g', '--goo'] goo 
Action  --help: ['-h', '--help'] help 
Action   -f: ['-f', '--foo'] foo 

usage: stack38071986.py [-f FOO] [--var1 VAR1] 

optional arguments: 
    help    show this help message and exit 
    -f FOO, --foo FOO 
    --var1 VAR1 

usage: stack38071986.py [-f FOO] [--var1 VAR1] [-h] [-g GOO] [--var VAR] 

optional arguments: 
    -f FOO, --foo FOO 
    --var1 VAR1 
    -h, --help   show this help message and exit 
    -g GOO, --goo GOO 
    --var VAR 

Namespace(foo=None, goo=None, var='3', var1='1') 

Функции обработчика конфликта определены в суперклассе, поэтому я не могу просто подкласса ArgumentParser добавить новый. Легко добавить пользовательские классы Action и FormatHandler, но это не так просто. Я предполагаю, что никто не пробовал эту настройку.

Так что мой kludge должен написать модификацию метода resolve и связать его - на лету. Не чистая, но достаточно для тестирования.

Этот обработчик знает идентификатор существующего действий (action аргумент), но не новый (т.е. --var от парсера, но не --var от ParserB). Поэтому я изменяю «имя» этого существующего. На данный момент этот метод должен знать через свою rename_dict строку опций, подлежащую замене, и новое имя.

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

parserC = argparse.ArgumentParser() 
parserC.add_argument(...) # C's own arguments 
copy_arguments(parserC, [parserA, parserB], rename_dict={...}) 

==========================

Я лучше это нравится - это custom parents механизм, который позволяет мне указать skip_list и replace_dict. (Я могу удалить о решении).

import argparse 

def add_actions(parser, other, skip_list=None, rename_dict=None): 
    # adapted from _add_container_actions (used for parents) 
    # copy (by reference) selected actions from other to parser 
    # can skip actions (to avoid use of conflict_handler) 
    # can rename other actions (again to avoid conflict) 
    if skip_list is None: 
     skip_list = ['-h','--help'] 
    if rename_dict is None: 
     rename_dict = {} 

    # group handling as before 
    # collect groups by titles 
    title_group_map = {} 
    for group in parser._action_groups: 
     if group.title in title_group_map: 
      msg = _('cannot merge actions - two groups are named %r') 
      raise ValueError(msg % (group.title)) 
     title_group_map[group.title] = group 

    # map each action to its group 
    group_map = {} 
    for group in other._action_groups: 

     # if a group with the title exists, use that, otherwise 
     # create a new group matching the other's group 
     if group.title not in title_group_map: 
      title_group_map[group.title] = parser.add_argument_group(
       title=group.title, 
       description=group.description, 
       conflict_handler=group.conflict_handler) 

     # map the actions to their new group 
     for action in group._group_actions: 
      group_map[action] = title_group_map[group.title] 

    # add other's mutually exclusive groups 
    # NOTE: if add_mutually_exclusive_group ever gains title= and 
    # description= then this code will need to be expanded as above 
    for group in other._mutually_exclusive_groups: 
     mutex_group = parser.add_mutually_exclusive_group(
      required=group.required) 

     # map the actions to their new mutex group 
     for action in group._group_actions: 
      group_map[action] = mutex_group 

    # add all actions to this other or their group 

    # addition with skip and rename 
    for action in other._actions: 
     option_strings = action.option_strings 
     if any([s for s in option_strings if s in skip_list]): 
      print('skipping ', action.dest) 
      continue 
     else: 
      sl = [s for s in option_strings if s in rename_dict] 
      if len(sl): 
       mod = rename_dict[sl[0]] 
       action.dest = action.dest+mod 
       action.option_strings = [option_strings[0]+mod] 
     group_map.get(action, parser)._add_action(action) 

parserA=argparse.ArgumentParser() 
a1=parserA.add_argument('-f','--foo') 
a1=parserA.add_argument('--var') 

parserB=argparse.ArgumentParser() 
b1=parserB.add_argument('-g','--goo') 
b1=parserB.add_argument('--var') 

parserC=argparse.ArgumentParser() 
# parserC.add_argument('baz') 
add_actions(parserC, parserA, rename_dict={'--var':'A'}) 
add_actions(parserC, parserB, rename_dict={'--var':'B'}) 

parserC.print_help() 
print(parserC.parse_args()) 

и образец выполнения

2245:~/mypy$ python3 stack38071986_1.py --varA 1 --varB 3 
skipping help 
skipping help 

usage: stack38071986_1.py [-h] [-f FOO] [--varA VARA] [-g GOO] [--varB VARB] 

optional arguments: 
    -h, --help   show this help message and exit 
    -f FOO, --foo FOO 
    --varA VARA 
    -g GOO, --goo GOO 
    --varB VARB 
Namespace(foo=None, goo=None, varA='1', varB='3') 

=============================

Если добавить

print('parserC actions') 
for action in parserC._actions: 
    print(action.option_strings, action.dest) 

Я получаю эту распечатку

parserC actions 
['-h', '--help'] help 
['-f', '--foo'] foo 
['--varA'] varA 
['-g', '--goo'] goo 
['--varB'] varB 

_actions - это список действий (аргументов) анализатора. Это «скрыто», поэтому используйте с осторожностью, но я не ожидаю никаких изменений в фундаментальном свойстве, подобном этому.Вы можете изменить многие из атрибутов этих действий, таких как dest

Например, если переименовать некоторые dest:

for action in parserC._actions: 
    if action.dest.startswith('var'): 
     action.dest = action.dest+'_C' 
print(parserC.parse_args()) 

пространство имен будет выглядеть

Namespace(foo=None, goo=None, varA_C=None, varB_C='one') 
+0

Мне нравится идея написав настраиваемый метод conflict_handler. Метод разрешения не подходит для меня, так как мне нужно хранить оба parserA.bar AND parserB.bar. Считаете ли вы, что conflict_handler сможет решить такой конфликт? – MichalD

+0

Я написал пользовательскую функцию 'parents', которая позволяет мне указывать опции пропуска и переименования. – hpaulj

+0

Спасибо! Я использую это решение (второе) – MichalD

Смежные вопросы