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


410

私は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の問題だと思います。警告の情報とコード補完を実際に使用します

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

回答:


574

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

あなたはこの例外を得たと思います:

NameError: name 'Position' is not defined

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

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ので、もしあればimport typing、あなたが表示されます時間は、パフォーマンスの増加7まであなたが3.7にアップグレードする場合。

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

PEP 484よると、クラス自体ではなく文字列を使用する必要があります:

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

Djangoフレームワークを使用する場合、Djangoモデルも前方参照に文字列を使用するため、これはよく知られている可能性があります(外部モデルがselfまだ宣言されているか、まだ宣言されていない外部キー定義)。これはPycharmやその他のツールで動作するはずです。

出典

旅行を節約するためのPEP 484およびPEP 563の関連部分:

前方参照

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

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

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問題なく見えるかもしれません:

>>> 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 from __future__ import annotations以前を使用している場合は、クラス名を含む文字列リテラルを使用してください。3.7では、それが機能します。


2
そう、これはPyCharmの問題ではなく、Python 3.5 PEP 484の問題です。mypyタイプのツールで実行すると、同じ警告が表示されると思います。
Paul Everitt、2015年

23
> Python 4.0を使用している場合、それは単に動作しますが、Sarah Connorを見たことはありますか?:)
scrutari

@JoelBerkeley私はそれをテストしたばかりで、型パラメーターは3.6で機能しましたtyping。文字列を評価するときは、使用する型がスコープ内になければならないので、インポートすることを忘れないでください。
Paulo Scardine

ああ、私の間違い、私は''クラスを丸めるだけで、タイプパラメータは入れませんでした
joelb '20

5
使用from __future__ import annotationsするすべての人への重要な注意-これは他のすべてのインポートの前にインポートする必要があります。
Artur

16

タイプを文字列として指定することは問題ありませんが、基本的にパーサーを迂回しているので、いつも少しばかり私に感謝しています。したがって、次のリテラル文字列のいずれかをスペルミスしないようにしてください。

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)

8
Pythonがtyping.Selfこれを明示的に指定することを望んでいた。
Alexander Huszagh

2
私はあなたのようなものがtyping.Self存在するかどうかを確認するためにここに来ました。ハードコードされた文字列を返すと、ポリモーフィズムを利用するときに正しい型が返されません。私の場合、逆シリアル化クラスメソッドを実装したいと思いました。私は辞書(kwargs)を返し、を呼び出すことにしましたsome_class(**some_class.deserialize(raw_data))
スコットP.

ここで使用される型注釈は、これを正しく実装してサブクラスを使用する場合に適しています。ただし、実装はPositionクラスではなくを返すため、上記の例は技術的に正しくありません。実装はのPosition(ように置き換える必要がありself.__class__(ます。
Sam Bull

さらに、注釈は戻り値の型がに依存すると述べていますがother、おそらく実際にはに依存していselfます。したがって、self正しい動作を説明するために注釈を付ける必要があります(おそらく、それが戻り値の型に関連付けられていないotherことPositionを示すためだけにすべきです)。これは、だけで作業している場合にも使用できますself。例def __aenter__(self: T) -> T:
Sam Bull

15

「Position」という名前は、クラス本体自体が解析される時点では使用できません。型宣言をどのように使用しているかはわかりませんが、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言語自体はこれらの注釈を何も行わないことに注意してください。これらは通常、静的コード分析用であるか、実行時に型チェックを行うためのライブラリ/フレームワークを持つことができますが、明示的に設定する必要があります)。

updateまた、Python 3.8以降では、pep-563を確認してください。Python3.8以降ではfrom __future__ import annotations、注釈の評価を延期するように記述できます。前方参照クラスは簡単に機能するはずです。


9

文字列ベースの型ヒントが許容できる場合、__qualname__アイテムも使用できます。これはクラスの名前を保持し、クラス定義の本体で使用できます。

class MyClass:
    @classmethod
    def make_new(cls) -> __qualname__:
        return cls()

これを行うことで、クラスの名前を変更しても、型のヒントが変更されるわけではありません。しかし、個人的には、スマートコードエディタがこのフォームを適切に処理することを期待していません。


1
これは、クラス名をハードコーディングしないため、特に役立ちます。サブクラスでも引き続き機能します。
Florian Brucker

これがアノテーションの延期された評価(PEP 563)で機能するかどうかわからないので、質問しました
フロリアン・ブラッカー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.