web-dev-qa-db-ja.com

Python 3.7:型注釈がジェネリックの「サブクラス」であるかどうかを確認します

型注釈が特定のジェネリック型の「サブクラス」であるかどうかを確認する、信頼性の高い/クロスバージョン(3.5以上)の方法を見つけようとしています(つまり、型アノテーションオブジェクトからジェネリック型を取得します)。

Python 3.5/3.6では、期待どおりに簡単に機能します。

>>> from typing import List

>>> isinstance(List[str], type)
True

>>> issubclass(List[str], List)
True

3.7では、ジェネリック型のインスタンスはtypeのインスタンスではなくなったため、失敗します。

>>> from typing import List

>>> isinstance(List[str], type)
False

>>> issubclass(List[str], List)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.7/typing.py", line 716, in __subclasscheck__
    raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks

頭に浮かぶ他のアイデアは、実際のインスタンスタイプをチェックすることですが、

Python 3.6/3.5:

>>> type(List[str])
<class 'typing.GenericMeta'>

Python 3.7:

>>> type(List[str])
<class 'typing._GenericAlias'>

しかし、これは実際のジェネリック型(リストではない可能性があります)についての詳細を示すものではありません。加えて、特に_GenericAliasが「プライベート」タイプになったため(特にアンダースコアに注意)、この方法でチェックを行うのは非常に間違っています。

チェックできるもう1つのことは、型の__Origin__引数ですが、それも正しい方法ではありません。

そして、それは3.7ではまだ異なります:

>>> List[str].__Origin__
<class 'list'>

3.5/3.6の間:

>>> List[str].__Origin__
typing.List

私はこれを行う「正しい」方法を探していましたが、Python docs/google search)でそれを見つけていません。

Mypyのようなツールは型チェックを行うためにこのツールに依存しているので、今、私はこのチェックを行うきれいな方法がなければならないと想定しています。

更新:ユースケースについて

ここでもう少しコンテキストを追加します。

したがって、この使用例では、関数のシグネチャ(引数の型/デフォルト、戻り値の型、docstring)のイントロスペクションを使用して、それらのGraphQLスキーマを自動的に生成します(これにより、ボイラープレートの量が削減されます)。

これが良いアイデアかどうかは、まだ少し悩んでいます。

私は、ユーザビリティの観点からそれが好きです(関数のシグネチャを宣言する別の方法を学ぶ必要はありません。通常の方法で型に注釈を付けるだけです)。私の意味を理解するには、ここの2つのコード例を参照してください: https://github.com/rshk/pyql

このようにtypingの型を使用してジェネリック型(リスト、辞書、ユニオンなど)をサポートすると、予期しない方法で壊れる可能性のある「ブラックマジック」が多すぎると思います。 (これは今のところ大きな問題ではありませんが、将来のPythonバージョン、3.7以降)はどうですか?これはメンテナンスの悪夢になるでしょうか?)。

もちろん、代わりの方法は、より信頼性の高い/将来性のあるチェックをサポートするカスタムタイプアノテーションを使用することです。例: https://github.com/rshk/pyql/blob/master/pyql/schema/ types/core.py#L337-L339

..しかし、欠点としては、カスタムタイプアノテーションを使用する必要があることを人々に思い出させます。さらに、私はmypyがそれをどのように処理するかわかりません(カスタムタイプがtyping.List ..?と完全に互換性があると宣言する必要があると思います)。

(私は主に2つのアプローチについての提案を求めています。最も重要なのは、私が見逃した可能性がある2つの代替案の長所/短所です。これがSOに対して「広すぎる」ことにならないことを願っています。).

18
redShadow

まず、typingモジュールで定義されているように、タイプヒントオブジェクトをイントロスペクトするためのAPIは定義されていません。型ヒントツールはソースコードを処理することが期待されているため、テキストではなく、Python実行時のオブジェクト; mypyは_List[str]_オブジェクトをイントロスペクトせず、代わりにソースコードの解析済み 抽象構文ツリー を扱います。

したがって、常に___Origin___のような属性にアクセスできますが、基本的には実装の詳細( internal bookkeeping を扱っています。 )、およびこれらの実装の詳細は、バージョンごとに変更される可能性があり、今後も変更されます。

とは言え、コアmypy /タイピングコントリビューターは _typing_inspect_ module を作成して、タイプヒントのイントロスペクションAPIを開発しました。プロジェクトはそれでもexperimentalとして文書化されており、実験的でなくなるまで時間とともに変化することも期待できます。 Python 3.5をサポートせず、get_Origin()関数が___Origin___属性が提供するものとまったく同じ値を返すため、問題はここでは解決しません。

これらすべての注意事項が解決されたので、Python 3.5/Python 3.6は___extra___属性です。これはライブラリが最初に実装したissubclass()/isinstance()サポートの駆動に使用される基本組み込み型(ただし、3.7で削除されたため):

_def get_type_class(typ):
    try:
        # Python 3.5 / 3.6
        return typ.__extra__
    except AttributeError:
        # Python 3.7
        return typ.__Origin__
_

これにより、Python 3.5以降、関係なく_<class 'list'>_が生成されます。内部実装の詳細が引き続き使用され、将来のバージョンPython。

6
Martijn Pieters

Python 3.8は、基本的なイントロスペクションをサポートするためにtyping.get_Origin()およびtyping.get_args()を追加することに注意してください。

2
Max Gasner