1. ホーム
  2. パイソン

[解決済み】メソッドの型ヒントは、どのようにエンクロージャクラスの型を使用するのですか?

2022-03-23 10:22:56

質問

Python 3で以下のようなコードを書いています。

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: Position) -> Position:
        return Position(self.x + other.x, self.y + other.y)

しかし、私のエディタ (PyCharm) では、参照先である Position は解決できません。 __add__ メソッド) を使用します。の型であることを期待することをどのように指定すればよいのでしょうか? Position ?

編集部:これは実はPyCharmの問題だと思います。実際に警告やコード補完にその情報を使っています。

しかし、私が間違っていて、他の構文を使用する必要がある場合は、訂正してください。

どのように解決するのですか?

TL;DR : 本日(2019年)現在、Python 3.7+では、"future"ステートメントを使用してこの機能をオンにする必要があります。 from __future__ import annotations .

(で有効な動作)。 from __future__ import annotations かもしれない は将来のバージョンのPythonではデフォルトになり は行っていた は、Python 3.10でデフォルトになる予定でした。しかし、3.10での変更点である は元に戻された というのも、最後の最後で、全く実現しないかもしれないからです)。

Python 3.6以下では、文字列を使用する必要があります。


この例外が発生したのでしょう。

NameError: name 'Position' is not defined

これは、以下の理由からです。 Position を使用している場合を除き、アノテーションの中で使用する前に定義する必要があります。 PEP 563 の変更を有効にします。

Python 3.7+の場合。 from __future__ import annotations

Python 3.7 の紹介 PEP 563:アノテーションの評価の延期 . futureステートメントを使用するモジュールは from __future__ import annotations は、アノテーションを自動的に文字列として格納します。

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

Python 3.10でデフォルトになる予定でしたが、この変更は現在延期されています。Pythonはまだ動的型付け言語であり、実行時に型チェックは行われないので、型付けアノテーションはパフォーマンスに影響を与えないはずですよね?そうではありません。Python 3.7 以前では、 typing モジュールは コアで最も遅いPythonモジュールの一つです。 そこで をインポートするようなコードでは typing モジュールでは 最大7倍のパフォーマンスアップ 3.7にアップグレードした場合

Python <3.7: 文字列を使用する

PEP 484によると の場合、クラス自体の代わりに文字列を使用する必要があります。

class Position:
    ...
    def __add__(self, other: 'Position') -> 'Position':
       ...

Django フレームワークを使用している場合、これは馴染みがあるかもしれません。Django のモデルも前方参照 (外部キー定義で、外部モデルが self またはまだ宣言されていない)。これはPycharmなどでも動作するはずです。

ソース

PEP484とPEP563の関連部分です。

<ブロッククオート

前方参照

タイプヒントがまだ定義されていない名前を含んでいる場合、その定義は文字列リテラルとして表現され、後で解決されることがあります。

このような状況がよく起こるのは、コンテナ・クラスの定義で、いくつかのメソッドのシグネチャに定義されているクラスが登場する場合です。例えば、以下のコード(単純なバイナリツリー実装の開始点)は動作しない。

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

これに対応するために、次のように書きます。

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

文字列リテラルは有効な Python 式を含み (すなわち compile(lit, '', 'eval') は有効なコードオブジェクトでなければなりません)、モジュールが完全にロードされた後はエラーなしで評価されなければなりません。評価されるローカルおよびグローバルの名前空間は、同じ関数へのデフォルトの引数が評価されるのと同じ名前空間でなければなりません。

とPEP563に記載されています。

実装

Python 3.10では、関数と変数のアノテーションは定義時に評価されなくなりました。その代わり、文字列の形式がそれぞれの  __annotations__  辞書を使用します。静的なタイプチェッカーでは動作に違いはありませんが、実行時にアノテーションを使用するツールでは、評価を先延ばしにする必要があります。

...

Python 3.7で未来の動作を有効にする

Python 3.7から、以下の特別なインポートを使って、上記の機能を有効にすることができます。

from __future__ import annotations

代わりにやりたくなるようなこと

A. ダミーを定義する Position

クラス定義の前に、ダミー定義を配置します。

class Position(object):
    pass


class Position(object):
    ...

これで NameError で、見た目もOKかもしれません。

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

しかし、そうでしょうか?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

B. アノテーションを追加するためにモンキーパッチする。

Pythonのメタプログラミングのマジックを試して、デコレーターを書きたいかもしれません。 を使用して、アノテーションを追加するためにクラス定義を猿真似しています。

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

デコレーターはこれに相当する部分を担当する必要があります。

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

少なくとも正しいような気がします。

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

たぶん、面倒なんでしょうね。