タイプヒントを検証するコードを記述しようとしています。そのためには、注釈がどのような種類のオブジェクトであるかを調べる必要があります。たとえば、期待される値の種類をユーザーに伝えることになっている次のスニペットを考えてみます。
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().
typ
がtyping.Generic
であるかどうかを確認する正しい方法は何ですか?
可能な場合は、ドキュメントまたはPEPまたはその他のリソースに裏付けられたソリューションを参照してください。ドキュメント化されていない内部属性にアクセスすることで「機能する」「ソリューション」は簡単に見つけることができます。しかし、おそらくそれは実装の詳細であることが判明し、将来のバージョンで変更されるでしょう。 「正しい方法」を探しています。
この情報を取得する公式な方法はありません。 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以下のものを含む))で機能するはずです。
あなたは探しているかもしれません __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が変更される可能性があります。