typing
モジュールは、ジェネリック型ヒントの基本クラスを提供します。 typing.Generic
クラス。
Generic
のサブクラスは、角括弧内の型引数を受け入れます。次に例を示します。
list_of_ints = typing.List[int]
str_to_bool_dict = typing.Dict[str, bool]
私の質問は、これらの型引数にどのようにアクセスできますか?
つまり、str_to_bool_dict
を入力として、どのようにしてstr
とbool
を出力として取得できますか?
基本的に私はそのような関数を探しています
>>> magic_function(str_to_bool_dict)
(<class 'str'>, <class 'bool'>)
Python 3.6。現在、パブリック___args__
_および(___parameters__
_)フィールドがあります。たとえば:
_print( typing.List[int].__args__ )
_
これには、ジェネリックパラメーター(つまり、int
)が含まれていますが、___parameters__
_には、ジェネリック自体(つまり、_~T
_)が含まれています。
typing_inspect.getargs を使用します
typing
が続く PEP8 。 PEP8とtyping
の両方がGuido van Rossumによって共著されています。二重の先頭および末尾のアンダースコアは、次のように定義されます: "" magic "オブジェクトまたは属性ユーザー制御の名前空間 「。
ダンダーもインラインでコメントされます。 typing の公式リポジトリから:* "___args__
_は、添え字で使用されるすべての引数のタプルです。たとえば、Dict[T, int].__args__ == (T, int)
「。
ただし、 著者も注意 :* "タイピングモジュールは暫定的なステータスを持っているため、下位互換性の高い標準ではカバーされていません(ただし、可能な限り)、これは特に___union_params__
_のような(まだ文書化されていない)dunder属性に当てはまります。ランタイムコンテキストで型を入力する場合は、_typing_inspect
_プロジェクト(partそのうち、後で入力することになります)。
私は一般的に、typing
で何をするにしても、当面の間は最新の状態に保つ必要があります。前方互換性のある変更が必要な場合は、独自の注釈クラスを作成することをお勧めします。
私の知る限り、ここには幸せな答えはありません。
頭に浮かぶのは、この情報を格納する__args__
文書化されていない属性です。
list_of_ints.__args__
>>>(<class 'int'>,)
str_to_bool_dict.__args__
>>>(<class 'str'>, <class 'bool'>)
ただし、typing
モジュールのドキュメントには記載されていません。
ただし、ドキュメントでは 言及されることは非常に近い であったことに注意してください。
おそらく、
GenericMeta.__new__
のすべてのキーワード引数を文書化する必要があるかどうかについても検討する必要があります。tvars
、args
、Origin
、extra
、およびorig_bases
があります。最初の3つについて何か言うことができると思います(これらは__parameters__
、__args__
、および__Origin__
に対応しており、これらはタイピングでほとんどのもので使用されます)。
しかし、 それはまったくできませんでした :
GenericMeta
を__all__
に追加し、ドキュメントストリングをGenericMeta
およびGenericMeta.__new__
に追加しました。__Origin__
と友人をdocstringで記述しないことにしました。代わりに、最初に使用する場所にコメントを追加しました。
そこから、まだ相互に排他的ではない3つのオプションがあります。
typing
モジュールが完全に成熟するのを待ち、これらの機能がまもなくドキュメント化されることを願っています
Python ideasメーリングリスト に参加して、これらの内部を公開/ APIの一部にするために十分なサポートを集めることができるかどうかを確認します
その間、文書化されていない内部構造を使用して、これらに変更が加えられない、または変更がマイナーであるというギャンブルを行います。
APIは変更される可能性があります であるため、3番目の点はほとんど回避できないことに注意してください。
タイピングモジュールは、暫定的に標準ライブラリに含まれています。コア開発者が必要と判断した場合、マイナーリリース間でも新しい機能が追加され、APIが変更される可能性があります。
この内部メソッドはトリックを行うようです
typing.List[int]._subs_tree()
タプルを返します:
(typing.List, <class 'int'>)
しかし、これはプライベートAPIであり、おそらくより良い答えがあります。
使用 .__args__
コンストラクト。必要な魔法の機能は次のようなものです-
get_type_args = lambda genrc_type: getattr(genrc_type, '__args__')
私の質問は、これらの型引数にどのようにアクセスできますか?
このような状況では、どのようにアクセスしますか...
Pythonの強力なイントロスペクション機能を使用します。
プロではないプログラマーであっても、私は何かを調べようとしていることを知っています。dir
は、ターミナルでIDE=のような関数です。
>>> import typing
>>> str_to_bool_dict = typing.Dict[str, bool]
あなたが望む魔法をする何かがあるかどうかを見たい
>>> methods = dir(str_to_bool_dict)
>>> methods
['__abstractmethods__', '__args__', .....]
情報が多すぎます。正しいかどうかを確認するには、確認します
>>> len(methods)
53
>>> len(dir(dict))
39
次に、ジェネリック型専用に設計されたメソッドを見つけましょう
>>> set(methods).difference(set(dir(dict)))
{'__slots__', '__parameters__', '_abc_negative_cache_version', '__extra__',
'_abc_cache', '__args__', '_abc_negative_cache', '__Origin__',
'__abstractmethods__', '__module__', '__next_in_mro__', '_abc_registry',
'__dict__', '__weakref__'}
これらのうち、__parameters__
、__extra__
、__args__
および__Origin__
役立つと思う。 __extra__
および__Origin__
は自己なしでは機能しないため、__parameters__
および__args__
。
>>> str_to_bool_dict.__args__
(<class 'str'>, <class 'bool'>)
したがって、答え。
イントロスペクションによりpy.test
のassert
ステートメントは、JUnit派生テストフレームワークを時代遅れに見せます。 JavaScript/Elm/Clojureのような言語でさえ、Pythonのdir
のような簡単なものはありません。 Pythonの命名規則により、ドキュメントを実際に読むことなく(これらのような場合にはグロッキングすることなく)言語を発見できます。
イントロスペクションを使用してハントし、ドキュメント/メーリングリストを読んで、結果を確認します。
追伸OPへ-このメソッドはあなたの質問にも答えます オブジェクトがタイピングされているかどうかを確認する正しい方法は何ですか?Generic? メーリングリストにコミットできないか、忙しい開発者である場合、ディスカバリーを使用します-それはPythonでそれを行う方法。
質問はtyping.Generic
について具体的に尋ねますが、(少なくとも以前のバージョンのtyping
モジュールでは)すべての添え字可能型がGeneric
のサブクラスではないことがわかります。新しいバージョンでは、すべての添え字可能型は引数を__args__
属性に格納します:
>>> List[int].__args__
(<class 'int'>,)
>>> Tuple[int, str].__args__
(<class 'int'>, <class 'str'>)
python 3.5では、ただし、typing.Tuple
、typing.Union
、typing.Callable
などの一部のクラスは、__Tuple_params__
、__union_params__
または一般的に__parameters__
で、完全を期すために、任意のpythonバージョンの任意の添え字可能型から型引数を抽出できる関数を次に示します。
import typing
if hasattr(typing, '_GenericAlias'):
# python 3.7
def _get_base_generic(cls):
# subclasses of Generic will have their _name set to None, but
# their __Origin__ will point to the base generic
if cls._name is None:
return cls.__Origin__
else:
return getattr(typing, cls._name)
else:
# python <3.7
def _get_base_generic(cls):
try:
return cls.__Origin__
except AttributeError:
pass
name = type(cls).__name__
if not name.endswith('Meta'):
raise NotImplementedError("Cannot determine base of {}".format(cls))
name = name[:-4]
try:
return getattr(typing, name)
except AttributeError:
raise NotImplementedError("Cannot determine base of {}".format(cls))
if hasattr(typing.List, '__args__'):
# python 3.6+
def _get_subtypes(cls):
subtypes = cls.__args__
if _get_base_generic(cls) is typing.Callable:
if len(subtypes) != 2 or subtypes[0] is not ...:
subtypes = (subtypes[:-1], subtypes[-1])
return subtypes
else:
# python 3.5
def _get_subtypes(cls):
if isinstance(cls, typing.CallableMeta):
if cls.__args__ is None:
return ()
return cls.__args__, cls.__result__
for name in ['__parameters__', '__union_params__', '__Tuple_params__']:
try:
subtypes = getattr(cls, name)
break
except AttributeError:
pass
else:
raise NotImplementedError("Cannot extract subtypes from {}".format(cls))
subtypes = [typ for typ in subtypes if not isinstance(typ, typing.TypeVar)]
return subtypes
def get_subtypes(cls):
"""
Given a qualified generic (like List[int] or Tuple[str, bool]) as input, return
a Tuple of all the classes listed inside the square brackets.
"""
return _get_subtypes(cls)
デモンストレーション:
>>> get_subtypes(List[int])
(<class 'int'>,)
>>> get_subtypes(Tuple[str, bool])
(<class 'str'>, <class 'bool'>)