web-dev-qa-db-ja.com

オブジェクトが型指定されているかどうかを確認する正しい方法は何ですか?

タイプヒントを検証するコードを記述しようとしています。そのためには、注釈がどのような種類のオブジェクトであるかを調べる必要があります。たとえば、期待される値の種類をユーザーに伝えることになっている次のスニペットを考えてみます。

import typing

typ = typing.Union[int, str]

if issubclass(typ, typing.Union):
    print('value type should be one of', typ.__args__)
Elif issubclass(typ, typing.Generic):
    print('value type should be a structure of', typ.__args__[0])
else:
    print('value type should be', typ)

これは「値の型は(int、str)のいずれかである必要があります」を出力するはずですが、代わりに例外がスローされます。

Traceback (most recent call last):
  File "untitled.py", line 6, in <module>
    if issubclass(typ, typing.Union):
  File "C:\Python34\lib\site-packages\typing.py", line 829, in __subclasscheck__
    raise TypeError("Unions cannot be used with issubclass().")
TypeError: Unions cannot be used with issubclass().

isinstanceも機能しません。

>>> isinstance(typ, typing.Union)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python34\lib\site-packages\typing.py", line 826, in __instancecheck__
    raise TypeError("Unions cannot be used with isinstance().")
TypeError: Unions cannot be used with isinstance().

typtyping.Genericであるかどうかを確認する正しい方法は何ですか?

可能な場合は、ドキュメントまたはPEPまたはその他のリソースに裏付けられたソリューションを参照してください。ドキュメント化されていない内部属性にアクセスすることで「機能する」「ソリューション」は簡単に見つけることができます。しかし、おそらくそれは実装の詳細であることが判明し、将来のバージョンで変更されるでしょう。 「正しい方法」を探しています。

25
Aran-Fey

この情報を取得する公式な方法はありません。 typingモジュールはまだ開発途上であり、公開するAPIはありません。 (実際には、おそらく1つもありません。)

私たちにできることは、モジュールの内部を調べ、目的の情報を取得するための最も簡単な方法を見つけることです。そして、モジュールはまだ作業中であるため、その内部は変更されます。たくさん。


python 3.5および3.6では、ジェネリックには、元のジェネリックベースクラスへの参照を保持する__Origin__属性がありました(つまり、List[int].__Origin__Listになります)が、3.7で変更されました。何かがジェネリックかどうかを確認する方法は、おそらく__parameters__および__args__属性を確認することです。

ジェネリックを検出するために使用できる一連の関数を次に示します。

import typing


if hasattr(typing, '_GenericAlias'):
    # python 3.7
    def _is_generic(cls):
        if isinstance(cls, typing._GenericAlias):
            return True

        if isinstance(cls, typing._SpecialForm):
            return cls not in {typing.Any}

        return False


    def _is_base_generic(cls):
        if isinstance(cls, typing._GenericAlias):
            if cls.__Origin__ in {typing.Generic, typing._Protocol}:
                return False

            if isinstance(cls, typing._VariadicGenericAlias):
                return True

            return len(cls.__parameters__) > 0

        if isinstance(cls, typing._SpecialForm):
            return cls._name in {'ClassVar', 'Union', 'Optional'}

        return False
else:
    # python <3.7
    if hasattr(typing, '_Union'):
        # python 3.6
        def _is_generic(cls):
            if isinstance(cls, (typing.GenericMeta, typing._Union, typing._Optional, typing._ClassVar)):
                return True

            return False


        def _is_base_generic(cls):
            if isinstance(cls, (typing.GenericMeta, typing._Union)):
                return cls.__args__ in {None, ()}

            if isinstance(cls, typing._Optional):
                return True

            return False
    else:
        # python 3.5
        def _is_generic(cls):
            if isinstance(cls, (typing.GenericMeta, typing.UnionMeta, typing.OptionalMeta, typing.CallableMeta, typing.TupleMeta)):
                return True

            return False


        def _is_base_generic(cls):
            if isinstance(cls, typing.GenericMeta):
                return all(isinstance(arg, typing.TypeVar) for arg in cls.__parameters__)

            if isinstance(cls, typing.UnionMeta):
                return cls.__union_params__ is None

            if isinstance(cls, typing.TupleMeta):
                return cls.__Tuple_params__ is None

            if isinstance(cls, typing.CallableMeta):
                return cls.__args__ is None

            if isinstance(cls, typing.OptionalMeta):
                return True

            return False


def is_generic(cls):
    """
    Detects any kind of generic, for example `List` or `List[int]`. This includes "special" types like
    Union and Tuple - anything that's subscriptable, basically.
    """
    return _is_generic(cls)


def is_base_generic(cls):
    """
    Detects generic base classes, for example `List` (but not `List[int]`)
    """
    return _is_base_generic(cls)


def is_qualified_generic(cls):
    """
    Detects generics with arguments, for example `List[int]` (but not `List`)
    """
    return is_generic(cls) and not is_base_generic(cls)

これらの関数はすべて、pythonバージョン3.7以降(typingモジュールバックポートを使用する3.5以下のものを含む))で機能するはずです。

13
Aran-Fey

あなたは探しているかもしれません __Origin__

# * __Origin__ keeps a reference to a type that was subscripted,
#   e.g., Union[T, int].__Origin__ == Union;`
import typing

typ = typing.Union[int, str]

if typ.__Origin__ is typing.Union:
    print('value type should be one of', typ.__args__)
Elif typ.__Origin__ is typing.Generic:
    print('value type should be a structure of', typ.__args__[0])
else:
    print('value type should be', typ)

>>>value type should be one of (<class 'int'>, <class 'str'>)

この文書化されていない属性の使用を提唱するために私が見つけることができる最高のものは、この安心なものです quote Guido Van Rossum(2年前)から:

私がお勧めできる最高の方法は__Origin__を使用することです-この属性を変更する場合でも、同じ情報にアクセスするには別の方法が必要であり、__Origin__の発生に対してコードをgrepするのは簡単です。 (__Origin__への変更よりも__extra__への変更の方が心配ではありません。)内部関数_gorg()および_geqv()も確認できます(これらの名前はパブリックAPIの一部にはなりません) 、明らかに、しかしそれらの実装は非常にシンプルで概念的には便利です)。

ドキュメントのこの警告は、まだ大理石には何も設定されていないことを示しているようです:

コア開発者が必要と判断した場合、新機能が追加され、マイナーリリース間でもAPIが変更される可能性があります。

21
Jacques Gaudin