web-dev-qa-db-ja.com

インスタンスを返すクラスメソッドのMyPyアノテーション

clsのインスタンスを返す@classmethodに注釈を付けるにはどうすればよいですか?ここに悪い例があります:

class Foo(object):
    def __init__(self, bar: str):
        self.bar = bar

    @classmethod
    def with_stuff_appended(cls, bar: str) -> ???:
        return cls(bar + "stuff")

これはFooを返しますが、より正確には、これが呼び出されたFooのサブクラスを返します。そのため、-> "Foo"による注釈は十分ではありません。

20
taway

トリックは、clsパラメータにTypeVarと組み合わせて genericsTypeの注釈を明示的に追加することです、 は、インスタンス自体ではなくクラスを表す ため、次のようになります。

from typing import TypeVar, Type

# Create a generic variable that can be 'Parent', or any subclass.
T = TypeVar('T', bound='Parent')

class Parent:
    def __init__(self, bar: str) -> None:
        self.bar = bar

    @classmethod
    def with_stuff_appended(cls: Type[T], bar: str) -> T:
        # We annotate 'cls' with a typevar so that we can
        # type our return type more precisely
        return cls(bar + "stuff")

class Child(Parent):
    # If you're going to redefine __init__, make sure it
    # has a signature that's compatible with the Parent's __init__,
    # since mypy currently doesn't check for that.

    def child_only(self) -> int:
        return 3

# Mypy correctly infers that p is of type 'Parent',
# and c is of type 'Child'.
p = Parent.with_stuff_appended("10")
c = Child.with_stuff_appended("20")

# We can verify this ourself by using the special 'reveal_type'
# function. Be sure to delete these lines before running your
# code -- this function is something only mypy understands
# (it's meant to help with debugging your types).
reveal_type(p)  # Revealed type is 'test.Parent*'
reveal_type(c)  # Revealed type is 'test.Child*'

# So, these all typecheck
print(p.bar)
print(c.bar)
print(c.child_only())

通常、cls(およびself)は注釈なしのままにしておくことができますが、特定のサブクラスを参照する必要がある場合は、 明示的な注釈を追加できます 。この機能はまだ実験段階であり、バグがある場合があることに注意してください。また、pypiで利用できるものではなく、Githubからクローンを作成した最新バージョンのmypyを使用する必要がある場合があります。そのバージョンがクラスメソッドでこの機能をサポートしているかどうかは覚えていません。

29
Michael0x2a

完全を期すために、Python 3.7では、ファイルの先頭にpostponed evaluation of annotationsをインポートすることにより、 PEP56 で定義されているfrom __future__ import annotationsを使用できます。

次に、あなたのコードは次のようになります

from __future__ import annotations

class Foo(object):
    def __init__(self, bar: str):
        self.bar = bar

    @classmethod
    def with_stuff_appended(cls, bar: str) -> Foo:
        return cls(bar + "stuff")