web-dev-qa-db-ja.com

typing.Genericの型引数にアクセスする方法は?

typing モジュールは、ジェネリック型ヒントの基本クラスを提供します。 typing.Generic クラス。

Genericのサブクラスは、角括弧内の型引数を受け入れます。次に例を示します。

list_of_ints = typing.List[int]
str_to_bool_dict = typing.Dict[str, bool]

私の質問は、これらの型引数にどのようにアクセスできますか?

つまり、str_to_bool_dictを入力として、どのようにしてstrboolを出力として取得できますか?

基本的に私はそのような関数を探しています

>>> magic_function(str_to_bool_dict)
(<class 'str'>, <class 'bool'>)
22
Aran-Fey

可能性1

Python 3.6。現在、パブリック___args___および(___parameters___)フィールドがあります。たとえば:

_print( typing.List[int].__args__ )
_

これには、ジェネリックパラメーター(つまり、int)が含まれていますが、___parameters___には、ジェネリック自体(つまり、_~T_)が含まれています。

可能性2

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で何をするにしても、当面の間は最新の状態に保つ必要があります。前方互換性のある変更が必要な場合は、独自の注釈クラスを作成することをお勧めします。

19
c z

私の知る限り、ここには幸せな答えはありません。

頭に浮かぶのは、この情報を格納する__args__文書化されていない属性です。

list_of_ints.__args__
>>>(<class 'int'>,)

str_to_bool_dict.__args__
>>>(<class 'str'>, <class 'bool'>)

ただし、typingモジュールのドキュメントには記載されていません。

ただし、ドキュメントでは 言及されることは非常に近い であったことに注意してください。

おそらく、GenericMeta.__new__のすべてのキーワード引数を文書化する必要があるかどうかについても検討する必要があります。 tvarsargsOriginextra、およびorig_basesがあります。最初の3つについて何か言うことができると思います(これらは__parameters____args__、および__Origin__に対応しており、これらはタイピングでほとんどのもので使用されます)。

しかし、 それはまったくできませんでした

GenericMeta__all__に追加し、ドキュメントストリングをGenericMetaおよびGenericMeta.__new__に追加しました。 __Origin__と友人をdocstringで記述しないことにしました。代わりに、最初に使用する場所にコメントを追加しました。

そこから、まだ相互に排他的ではない3つのオプションがあります。

  • typingモジュールが完全に成熟するのを待ち、これらの機能がまもなくドキュメント化されることを願っています

  • Python ideasメーリングリスト に参加して、これらの内部を公開/ APIの一部にするために十分なサポートを集めることができるかどうかを確認します

  • その間、文書化されていない内部構造を使用して、これらに変更が加えられない、または変更がマイナーであるというギャンブルを行います。

APIは変更される可能性があります であるため、3番目の点はほとんど回避できないことに注意してください。

タイピングモジュールは、暫定的に標準ライブラリに含まれています。コア開発者が必要と判断した場合、マイナーリリース間でも新しい機能が追加され、APIが変更される可能性があります

5
Jacques Gaudin

この内部メソッドはトリックを行うようです

typing.List[int]._subs_tree()

タプルを返します:

(typing.List, <class 'int'>)

しかし、これはプライベートAPIであり、おそらくより良い答えがあります。

5
Petros Makris

使用 .__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.testassertステートメントは、JUnit派生テストフレームワークを時代遅れに見せます。 JavaScript/Elm/Clojureのような言語でさえ、Pythonのdirのような簡単なものはありません。 Pythonの命名規則により、ドキュメントを実際に読むことなく(これらのような場合にはグロッキングすることなく)言語を発見できます。

イントロスペクションを使用してハントし、ドキュメント/メーリングリストを読んで、結果を確認します。

追伸OPへ-このメソッドはあなたの質問にも答えます オブジェクトがタイピングされているかどうかを確認する正しい方法は何ですか?Generic? メーリングリストにコミットできないか、忙しい開発者である場合、ディスカバリーを使用します-それはPythonでそれを行う方法。

0
RinkyPinku

質問はtyping.Genericについて具体的に尋ねますが、(少なくとも以前のバージョンのtypingモジュールでは)すべての添え字可能型がGenericのサブクラスではないことがわかります。新しいバージョンでは、すべての添え字可能型は引数を__args__属性に格納します:

>>> List[int].__args__
(<class 'int'>,)
>>> Tuple[int, str].__args__
(<class 'int'>, <class 'str'>)

python 3.5では、ただし、typing.Tupletyping.Uniontyping.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'>)
0
Aran-Fey