2016-10-12 8 views
7

Вопрос: Каков предназначенный/официальный способ доступа к возможным аргументам из существующего объекта argparse.ArgumentParser?Правильный способ получения разрешенных аргументов от ArgumentParser

Пример: Давайте предположим следующий контекст:

import argparse 
parser = argparse.ArgumentParser() 
parser.add_argument('--foo', '-f', type=str) 

Здесь я хотел бы получить следующий список разрешенных аргументов:

['-h', '--foo', '--help', '-f'] 

Я нашел следующий обходной путь, который делает то трюк для меня

parser._option_string_actions.keys() 

Но я не доволен этим, поскольку он включает в себя доступ к _-членам, которые официально не документированы. Какая правильная альтернатива для этой задачи?

+0

Насколько важна эта первоначальная часть «существующего парсера» вопроса? Вы делаете парсер с нуля или импортируете его из другого модуля? Вам нужно включить автоматические ключи «help»? – hpaulj

+0

Я также хотел бы иметь ключи «help», но я в порядке, добавляя их вручную. «Существующая» часть - это реальное ограничение. Я мог подумать или сценарии, когда пользователь не может использовать ваш «трюк». Поэтому, чтобы подчеркнуть, я действительно получаю от ** существующего ** 'ArgumentParser'. – m8mble

+0

'_option_string_actions' будет самым надежным источником информации для существующего анализатора. Просто имейте в виду, как это было заселено. Префиксные символы, помощь SUPPRESS и группы могут изменять макет 'help' и беспорядок с поиском справки. – hpaulj

ответ

3

Я не думаю, что существует «лучший» способ достичь того, чего вы хотите.


Если вы действительно не хотите использовать атрибут _option_string_actions, можно обработать parser.format_usage() получить варианты, но делая это, вы получите только короткие имена опций.

Если вы хотите как короткие, так и длинные имена опций, вместо этого вы можете обработать parser.format_help().

Этот процесс может быть сделано с очень простым регулярным выражением: -+\w+

import re 

OPTION_RE = re.compile(r"-+\w+") 
PARSER_HELP = """usage: test_args_2.py [-h] [--foo FOO] [--bar BAR] 

optional arguments: 
    -h, --help   show this help message and exit 
    --foo FOO, -f FOO a random options 
    --bar BAR, -b BAR a more random option 
""" 

options = set(OPTION_RE.findall(PARSER_HELP)) 

print(options) 
# set(['-f', '-b', '--bar', '-h', '--help', '--foo']) 

Или вы могли бы сделать первый dictionnary, который содержит конфигурацию аргумент синтаксический анализатор, а затем построить argmuent анализатор от него. Такой тип словаря может иметь имена опций в качестве ключа и конфигурацию опций в качестве значения. Делая это, вы можете получить доступ к списку параметров с помощью клавиш dictionnary уплощенных с itertools.chain:

import argparse 
import itertools 

parser_config = { 
    ('--foo', '-f'): {"help": "a random options", "type": str}, 
    ('--bar', '-b'): {"help": "a more random option", "type": int, "default": 0} 
} 

parser = argparse.ArgumentParser() 
for option, config in parser_config.items(): 
    parser.add_argument(*option, **config) 

print(parser.format_help()) 
# usage: test_args_2.py [-h] [--foo FOO] [--bar BAR] 
# 
# optional arguments: 
# -h, --help   show this help message and exit 
# --foo FOO, -f FOO a random options 
# --bar BAR, -b BAR a more random option 

print(list(itertools.chain(*parser_config.keys()))) 
# ['--foo', '-f', '--bar', '-b'] 

Этот последний способ является то, что я хотел бы сделать, если бы я не хотел использовать _option_string_actions.

+0

Хороший совет о том, как создать парсер аргументов, значительно улучшает читаемость. – berna1111

+0

Не отвечает на исходный вопрос, но (второе решение) является хорошим обходным решением. Для других, чтобы использовать это, если вы только хотите разрешить '--bar', вам нужно добавить его как' ('--bar',) 'в' parser_config' из-за [this] (http: // stackoverflow .com/а/16449189/2747160). Я приму этот ответ, если ничего более элегантного не появится. – m8mble

+0

Сбор строки параметров из вашей собственной структуры данных - хорошая идея. Но он не собирает строки 'help' и не обрабатывает« существующий »парсер. – hpaulj

0

Я должен согласиться с ответом Трифа.

Не очень, но вы можете получить их от parser.format_help():

import argparse 

parser = argparse.ArgumentParser() 
parser.add_argument('--foo', '-f', type=str) 
goal = parser._option_string_actions.keys() 

def get_allowed_arguments(parser): 
    lines = parser.format_help().split('\n') 
    line_index = 0 
    number_of_lines = len(lines) 
    found_optional_arguments = False 
    # skip the first lines until the section 'optional arguments' 
    while line_index < number_of_lines: 
     if lines[line_index] == 'optional arguments:': 
      found_optional_arguments = True 
      line_index += 1 
      break 
     line_index += 1 
    result_list = [] 
    if found_optional_arguments: 
     while line_index < number_of_lines: 
      arg_list = get_arguments_from_line(lines[line_index]) 
      if len(arg_list) == 0: 
       break 
      result_list += arg_list 
      line_index += 1 
    return result_list 

def get_arguments_from_line(line): 
    if line[:2] != ' ': 
     return [] 
    arg_list = [] 
    i = 2 
    N = len(line) 
    inside_arg = False 
    arg_start = 2 
    while i < N: 
     if line[i] == '-' and not inside_arg: 
      arg_start = i 
      inside_arg = True 
     elif line[i] in [',',' '] and inside_arg: 
      arg_list.append(line[arg_start:i+1]) 
      inside_arg = False 
     i += 1 
    return arg_list 

answer = get_allowed_arguments(parser) 

Там, наверное, альтернативный регулярных выражений к вышеупомянутому бардаку ...

+1

Существует действительно простое регулярное выражение, которое облегчает его: '- + \ w +'. используется с findall(), он возвращает все параметры в справочном сообщении. – Tryph

0

Первого примечания на argparse документов - это в основном как документ, а не формальный API. Стандартом для того, что делает argparse, является сам код, модульные тесты (test/test_argparse.py) и парализующая проблема обратной совместимости.

Нет никакого «официального» способа доступа к allowed arguments, поскольку пользователям обычно не нужно знать это (кроме чтения help/usage).

Но позвольте мне проиллюстрировать с помощью простого синтаксического анализа в iteractive сессии:

In [247]: parser=argparse.ArgumentParser() 
In [248]: a = parser.add_argument('pos') 
In [249]: b = parser.add_argument('-f','--foo') 

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

Объект parser имеет метод repr, который отображает основные параметры. Но у него есть много других атрибутов, которые вы можете увидеть с помощью vars(parser), или parser.<tab> в Ipython.

In [250]: parser 
Out[250]: ArgumentParser(prog='ipython3', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True) 

Действия также имеют repr; подкласс Action определяется параметром action.

In [251]: a 
Out[251]: _StoreAction(option_strings=[], dest='pos', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None) 
In [252]: b 
Out[252]: _StoreAction(option_strings=['-f', '--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None) 

vars(a) и т. Д. Можно использовать для просмотра всех атрибутов.

Атрибут ключа parser: _actions, список всех определенных действий. Это основа для всего разбора. Обратите внимание, что оно включает действие help, которое было создано автоматически. Посмотрите на option_strings; который определяет, является ли действие позиционным или необязательным.

In [253]: parser._actions 
Out[253]: 
[_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=[], dest='pos',....), 
_StoreAction(option_strings=['-f', '--foo'], dest='foo', ...)] 

_option_string_actions представляет собой словарь, отображение option_strings к действиям (те же объекты, которые появляются в _actions). Ссылки на те объекты Action отображаются повсюду в коде argparse.

In [255]: parser._option_string_actions 
Out[255]: 
{'--foo': _StoreAction(option_strings=['-f', '--foo'],....), 
'--help': _HelpAction(option_strings=['-h', '--help'],...), 
'-f': _StoreAction(option_strings=['-f', '--foo'], dest='foo',...), 
'-h': _HelpAction(option_strings=['-h', '--help'], ....)} 

In [256]: list(parser._option_string_actions.keys()) 
Out[256]: ['-f', '--help', '-h', '--foo'] 

Обратите внимание, что есть ключ для каждой строки -, длинной или короткой; но нет ничего для pos, позиционер имеет пустой параметр option_strings.

Если этот список ключей является тем, что вы хотите, используйте его, и не беспокойтесь о _. У него нет «общедоступного» псевдонима.

Я могу понять разбор help, чтобы обнаружить то же самое; но это большая работа, чтобы просто не использовать атрибут «private». Если вы беспокоитесь об изменении недокументированного атрибута, вам также следует беспокоиться о том, что формат справки меняется. Это тоже не часть документов.

help расположение: parser.format_help. usage создан на основе информации в self._actions. Помощь линии от информации в

for action_group in self._action_groups: 
     formatter.add_arguments(action_group._group_actions) 

(вы не хотите, чтобы попасть в action groups да?).

Существует еще один способ получения option_strings - собрать из них _actions:

In [258]: [a.option_strings for a in parser._actions] 
Out[258]: [['-h', '--help'], [], ['-f', '--foo']] 

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

Delving в деталях кода:

parser.add_argument создает действие, а затем передает его parser._add_action. Это метод, который заполняет как .actions, так и action.option_strings.

self._actions.append(action) 
    for option_string in action.option_strings: 
     self._option_string_actions[option_string] = action 
+0

Мне было известно о возвращаемом значении 'add_argument'. Но обмен одним частным членом с другим не поможет ... – m8mble

+0

Хотя я бы сказал, что список '_action' является более простым. Не то, чтобы либо частный атрибут будет изменен в течение следующих 10 лет релизов. Они слишком важны для функциональности 'parser'. – hpaulj

1

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

Предположим, мы знаем, что максимальная длина разрешенной опции. Вот хороший ответ на вопрос в этой ситуации:

from itertools import combinations 

def parsable(option): 
    try: 
     return len(parser.parse_known_args(option.split())[1]) != 2 
    except: 
     return False 

def test(tester, option): 
    return any([tester(str(option) + ' ' + str(v)) for v in ['0', '0.0']]) 

def allowed_options(parser, max_len=3, min_len=1): 
    acceptable = [] 
    for l in range(min_len, max_len + 1): 
     for option in combinations([c for c in [chr(i) for i in range(33, 127)] if c != '-'], l): 
      option = ''.join(option) 
      acceptable += [p + option for p in ['-', '--'] if test(parsable, p + option)] 
    return acceptable 

Конечно, это очень педантичный, как вопрос не требует какого-либо конкретного выполнения. Поэтому я проигнорирую это здесь. Я также проигнорирую, что приведенная выше версия создает беспорядок вывода, потому что one can get rid of it easily.

Но что еще более важно, этот метод обнаружен следующая интересная argparse «особенность»:

  • В в примере ОП, argparse также позволяет --fo. Это должно быть ошибкой.
  • Но далее, в примере OP снова, argparse также разрешил бы -fo (то есть установить foo на o без пробела или что-нибудь еще). Это документировано и предназначено, но я этого не знал.

Из-за этого, правильным решением будет немного дольше и будет выглядеть примерно так (только parsable изменения, я буду опускать другие методы):

def parsable(option): 
    try: 
     default = vars(parser.parse_known_args(['--' + '0' * 200])[0]) 
     parsed, remaining = parser.parse_known_args(option.split()) 
     if len(remaining) == 2: 
      return False 
     parsed = vars(parsed) 
     for k in parsed.keys(): 
      try: 
       if k in default and default[k] != parsed[k] and float(parsed[k]) != 0.0: 
        return False # Filter '-fx' cases where '-f' is the argument and 'x' the value. 
      except: 
       return False 
     return True 
    except: 
     return False 

Резюме: Кроме того, все ограничения (время выполнения и фиксированная максимальная длина опций), это единственный ответ, который правильно оценивает реальное поведение parser - но, по-видимому, это может быть неэффективно. Итак, вот вы, идеальный ответ, абсолютно бесполезный.