2016-10-12 13 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'キーも持っていたいと思いますが、手動で追加するのは問題ありません。 「既存の」部分は実際の制限です。私は、あなたの "dict-trick"を利用することができないと思う、あるいはシナリオを考えることができます。したがって、私は**既存の** 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パーサーを構築することができ:

このプロセスは非常に単純な正規表現で行うことができます。そのような辞書は、オプション名をキーとして、オプション構成を値として持つことができます。これを行うには、itertools.chainで平坦化dictionnaryキーでオプションのリストにアクセスすることができます

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

元の質問に実際には答えませんが、(2番目の解決策)は素晴らしい回避策です。他の人がこれを使うには、 '--bar'だけを許可したいなら、[this](http:// stackoverflow)のために' parser_config'に '( '--bar'、)'として追加する必要があります。 .com/a/16449189/2747160)。もっと優雅なものが現れなければ、私はこの答えを受け入れるだろう。 – m8mble

+0

独自のデータ構造からオプション文字列を収集することをお勧めします。しかし、 'help'文字列を集めることはなく、 '既存の'パーサーは扱いません。 – hpaulj

0

私はTryphの答えに同意する必要があります。

きれいではありませんが、あなたは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

まずノートを​​ドキュメントに - それは基本的にです使い慣れたドキュメントであり、正式なAPIではありません。​​の標準は、コード自体、単体テスト(test/test_argparse.py)、下位互換性のための麻痺の懸念です。

help/usageを読んでいる以外のユーザーは、通常、ユーザーがそれを知る必要がないため、allowed argumentsにアクセスする「公式の」方法はありません。

しかし、私は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メソッドがあります。しかし、それにはさらに多くの属性があります。これは、Ipythonでvars(parser)、またはparser.<tab>で見ることができます。

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アクション(_actionsに現れる同一のオブジェクト)へoption_stringsから辞書、マッピングです。これらのアクションオブジェクトへの参照は、すべて​​コードに表示されます。

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を解析して同じことを理解できます。それは「プライベート」属性の使用を避けるだけの多くの作業です。文書化されていない属性が変更されることを心配するならば、変更されるヘルプフォーマットについても心配する必要があります。これはドキュメントの一部でもありません。

helpレイアウトはparser.format_helpによって制御されます。 usageは、self._actionsの情報から作成されます。ヘルプラインからの情報

(あなたはaction groupsに入りたくないですか?)option_stringsを得るための別の方法があります

から_actionsからそれらを収集:

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

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

探求とコードの詳細にビット:

parser.add_argumentアクションを作成し、parser._add_actionに渡します。これは、.actionsaction.option_stringsの両方を入力する方法です。

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

私は 'add_argument'の戻り値を知っていました。しかし、1つのプライベートメンバーを別のメンバーと交換することは助けになりません... – 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

しかし、もっと重要なのは、この方法は、以下の興味深い​​"機能" とは、検出:OPの例では

  • では、​​も--foを可能にします。これはバグでなければなりません。
  • さらに、OPの例では、​​も-foとなります(つまり、スペースまたは何も設定しないでfoo~を設定してください)。これは文書化され、意図されていますが、わかりませんでした。

これにより、正しい解決策は少し長く、このようなものになりますので(のみ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の動作を正しく尊重する唯一の答えですが、バグがあるかもしれません。だからこそ、あなたは完全に無益な完璧な答えです。

関連する問題