web-dev-qa-db-ja.com

メソッドの戻り値の型がPythonのクラス自体と同じであることを指定するにはどうすればよいですか?

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)は、(_ add__メソッドで)参照位置を解決できないと言っています。戻り値の型がPosition型であることをどのように指定する必要がありますか?

編集:これは実際にはPyCharmの問題だと思います。実際には、警告の情報とコード補完を使用します

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

196

TL; DR:Python 4.0を使用している場合、機能します。今日(2019)の3.7以降では、futureステートメント(from __future__ import annotations)を使用してこの機能を有効にする必要があります-Python 3.6以下では文字列を使用します。

この例外が発生したと思います:

NameError: name 'Position' is not defined

これは、Pythonを使用していない限り、Positionをアノテーションで使用する前に定義する必要があるためです。

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 4.0でデフォルトになる予定です。 Pythonはまだ動的に型付けされた言語なので、実行時に型チェックは行われないため、注釈を入力してもパフォーマンスに影響はありませんか?違う! python 3.7の前は、タイピングモジュールは コア内の最も遅いpythonモジュールの1つ soif import typing you パフォーマンスの最大7倍の増加 3.7にアップグレードすると表示されます。

Python <3.7:文字列を使用

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

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

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

ソース

PEP 484 および PEP 56 の関連部分は、旅行の手間を省くためのものです。

前方参照

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

これが一般的に発生する状況は、コンテナクラスの定義です。ここでは、定義されるクラスがいくつかのメソッドのシグネチャで発生します。たとえば、次のコード(単純なバイナリツリー実装の開始)は機能しません。

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')は有効なコードオブジェクトである必要があります)、モジュールが完全にロードされるとエラーなしで評価されます。評価されるローカルおよびグローバル名前空間は、同じ関数のデフォルト引数が評価される同じ名前空間でなければなりません。

およびPEP 563:

Python 4.0では、関数と変数の注釈は定義時に評価されなくなりました。代わりに、文字列形式はそれぞれの__annotations__辞書に保存されます。静的型チェッカーでは動作に違いはありませんが、実行時に注釈を使用するツールは延期された評価を実行する必要があります。

...

上記の機能は、次の特別なインポートを使用して、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

おそらくあまりにも多くのトラブル。

結論

3.6以下を使用している場合、クラス名を含む文字列リテラルを使用し、3.7ではfrom __future__ import annotationsを使用します。

292
Paulo Scardine

名前「位置」は、クラス本体自体が解析される時点では使用できません。どのように型宣言を使用しているかはわかりませんが、PythonのPEP 484-これらのタイピングヒントを使用する場合、ほとんどのモードで使用すべきものです。

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

https://www.python.org/dev/peps/pep-0484/#forward-references を確認してください。これに準拠するツールは、そこからクラス名を展開し、それを使用することを認識します。 (Python言語自体はこれらの注釈を何もしないことに留意することが常に重要です-通常、これらは静的コード分析を目的としています。または、実行時の型チェック用のライブラリ/フレームワークがあります。時間-ただし、明示的に設定する必要があります)

11
jsbueno

型を文字列として指定することは問題ありませんが、基本的にパーサーを回避していることを常にうれしく思います。したがって、これらのリテラル文字列のいずれかのスペルを間違えないことをお勧めします。

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

わずかなバリエーションは、バインドされたtypevarを使用することです。少なくともtypevarを宣言するときは、文字列を1回だけ記述する必要があります。

from typing import TypeVar

T = TypeVar('T', bound='Position')

class Position:

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

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