Python静的アナライザー(たとえば、PyCharm、他のIDE))がargparse.Namespace
オブジェクトのTypehintsを取得する方法はありますか?例:
parser = argparse.ArgumentParser()
parser.add_argument('--somearg')
parsed = parser.parse_args(['--somearg','someval']) # type: argparse.Namespace
the_arg = parsed.somearg # <- Pycharm complains that parsed object has no attribute 'somearg'
インラインコメントの型宣言を削除すると、PyCharmは文句を言わなくなりますが、無効な属性を取得しません。例えば:
parser = argparse.ArgumentParser()
parser.add_argument('--somearg')
parsed = parser.parse_args(['--somearg','someval']) # no typehint
the_arg = parsed.somaerg # <- typo in attribute, but no complaint in PyCharm. Raises AttributeError when executed.
何か案は?
以下の オースティンの答え に触発されて、私が見つけることができる最も簡単な解決策はnamedtuples
を使用するものです:
from collections import namedtuple
ArgNamespace = namedtuple('ArgNamespace', ['some_arg', 'another_arg'])
parser = argparse.ArgumentParser()
parser.add_argument('--some-arg')
parser.add_argument('--another-arg')
parsed = parser.parse_args(['--some-arg', 'val1', '--another-arg', 'val2']) # type: ArgNamespace
x = parsed.some_arg # good...
y = parsed.another_arg # still good...
z = parsed.aint_no_arg # Flagged by PyCharm!
これで十分ですが、引数名を繰り返す必要はありません。引数リストがかなり大きくなると、両方の場所を更新するのが面倒になります。理想的なのは、次のようにparser
オブジェクトから引数を抽出することです。
parser = argparse.ArgumentParser()
parser.add_argument('--some-arg')
parser.add_argument('--another-arg')
MagicNamespace = parser.magically_extract_namespace()
parsed = parser.parse_args(['--some-arg', 'val1', '--another-arg', 'val2']) # type: MagicNamespace
これを可能にするargparse
モジュールで何も見つけることができませんでしたが、anyかどうかまだわかりません静的分析ツールは、これらの値を取得するのに十分賢く、IDEを粉砕停止させないでください。
まだ探している...
Hpauljのコメントによれば、パースされたオブジェクトの属性を「魔法のように」抽出する上記の方法に最も近いものは、パーサーの_action
sからdest
属性を抽出するものです。 。:
parser = argparse.ArgumentParser()
parser.add_argument('--some-arg')
parser.add_argument('--another-arg')
MagicNamespace = namedtuple('MagicNamespace', [act.dest for act in parser._actions])
parsed = parser.parse_args(['--some-arg', 'val1', '--another-arg', 'val2']) # type: MagicNamespace
ただし、これでも静的分析で属性エラーのフラグが立てられることはありません。これは、namespace=MagicNamespace
呼び出しでparser.parse_args
を渡した場合にも当てはまります。
型付き引数パーサー はまさにこの目的のために作成されました。 argparse
をラップします。あなたの例は次のように実装されます:
from tap import Tap
class ArgumentParser(Tap):
somearg: str
parsed = ArgumentParser().parse_args(['--somearg', 'someval'])
the_arg = parsed.somearg
完全な開示:私はこのライブラリの作成者の1人です。
必要なタイプヒントを提供するargparse.Namespace
への拡張クラスの定義を検討してください。
class MyProgramArgs(argparse.Namespace):
def __init__():
self.somearg = 'defaultval' # type: str
次に、namespace=
を使用して、それをparse_args
に渡します。
def process_argv():
parser = argparse.ArgumentParser()
parser.add_argument('--somearg')
nsp = MyProgramArgs()
parsed = parser.parse_args(['--somearg','someval'], namespace=nsp) # type: MyProgramArgs
the_arg = parsed.somearg # <- Pycharm should not complain
PyCharmがこれらのタイプヒントをどのように処理するかについては何も知りませんが、Namespace
コードを理解しています。
argparse.Namespace
は単純なクラスです。基本的に、属性の表示を容易にするいくつかのメソッドを持つオブジェクト。ユニットテストを簡単にするために、__eq__
メソッドがあります。 argparse.py
ファイルで定義を読み取ることができます。
parser
は、最も一般的な方法で名前空間と対話します-getattr
、setattr
、hasattr
。したがって、.dest
構文ではアクセスできないものも含め、ほとんどすべてのdest
文字列を使用できます。
add_argument
type=
パラメータを混同しないように注意してください。それは関数です。
他の回答で提案されているように、独自のnamespace
クラスを使用する(ゼロから、またはサブクラス化した)のが最善のオプションです。これはドキュメントで簡単に説明されています。 名前空間オブジェクト 。特別なストレージのニーズに対処することを何度か提案しましたが、これはあまり行われていません。したがって、実験する必要があります。
サブパーサーを使用している場合、カスタムの名前空間クラスを使用すると壊れる可能性があります http://bugs.python.org/issue27859
デフォルトの処理に注意してください。ほとんどのargparse
アクションのデフォルトのデフォルトはNone
です。ユーザーがこのオプションを提供しなかった場合、解析後にこれを使用して特別なことを行うと便利です。
if args.foo is None:
# user did not use this optional
args.foo = 'some post parsing default'
else:
# user provided value
pass
それは型のヒントを邪魔するかもしれません。どのような解決策を試しても、デフォルトに注意してください。
namedtuple
はNamespace
としては機能しません。
まず、カスタム名前空間クラスの適切な使用法は次のとおりです。
nm = MyClass(<default values>)
args = parser.parse_args(namespace=nm)
つまり、そのクラスのインスタンスを初期化し、それをパラメーターとして渡します。返されるargs
は同じインスタンスになり、解析によって新しい属性が設定されます。
次に、namedtupleは作成のみが可能で、変更はできません。
In [72]: MagicSpace=namedtuple('MagicSpace',['foo','bar'])
In [73]: nm = MagicSpace(1,2)
In [74]: nm
Out[74]: MagicSpace(foo=1, bar=2)
In [75]: nm.foo='one'
...
AttributeError: can't set attribute
In [76]: getattr(nm, 'foo')
Out[76]: 1
In [77]: setattr(nm, 'foo', 'one') # not even with setattr
...
AttributeError: can't set attribute
名前空間はgetattr
およびsetattr
と連携する必要があります。
namedtuple
のもう1つの問題は、type
情報がまったく設定されないことです。フィールド/属性名を定義するだけです。したがって、静的型付けをチェックする必要はありません。
parser
から予想される属性名を取得するのは簡単ですが、予想される型を取得することはできません。
単純なパーサーの場合:
In [82]: parser.print_usage()
usage: ipython3 [-h] [-foo FOO] bar
In [83]: [a.dest for a in parser._actions[1:]]
Out[83]: ['foo', 'bar']
In [84]: [a.type for a in parser._actions[1:]]
Out[84]: [None, None]
アクションdest
は通常の属性名です。ただし、type
は、その属性の予期される静的タイプではありません。入力文字列を変換する場合と変換しない場合がある関数です。ここでNone
は、入力文字列がそのまま保存されることを意味します。
静的型付けとargparse
は異なる情報を必要とするため、一方を他方から簡単に生成する方法はありません。
あなたができる最善のことは、おそらく辞書に独自のパラメータのデータベースを作成し、独自のユーティリティ関数を使用してNamespaceクラスとそこからパーサーの両方を作成することだと思います。
dd
が必要なキーを持つ辞書であるとしましょう。次に、次のように引数を作成できます。
parser.add_argument(dd['short'],dd['long'], dest=dd['dest'], type=dd['typefun'], default=dd['default'], help=dd['help'])
あなたや他の誰かが、そのような辞書からdefault
(簡単)と静的型(難しい?)を設定する名前空間クラス定義を考え出す必要があります。