LoginSignup
7
2

More than 5 years have passed since last update.

List[Foo]を返すメソッドは継承しにくい

Last updated at Posted at 2018-05-01

前提

引数のList[T]は論外

Pythonのtypingを使ってお型付けをしているときの話。

from typing import List


class Bar:
    def list_to_none(self, foo: List[Foo]) -> None:  # Bad
        for f in foo:
            self.something(f)

こういうメソッドを書くのはよろしくない。
中でやっていることはiterationでしかないのに、listを継承しているものしか受け取れないようになっているからだ。
この場合は、できるだけlistに近い構造を期待している場合はSequence[T]を、単にiterationさえできればいいならCollection[T]を受け取るようにすればよい。

from typing import Sequence


class Bar:
    def seq_to_none(foo: Sequence[Foo]) -> None:  # Good!
        for f in foo:
            self.something(f)

本題

返り値のList[T]も状況によってはよろしくない

from typing import List


class Bar:
    def __init__(self, N: int) -> None:
        self._foo = [Foo() for i in range(N)]

    def none_to_list(self) -> List[Foo]:  # Good...?
        return self._foo

返り値は自分で決めるものなので、受け取る側の引数と違ってできるだけ表す内容が狭い方がいい。
なのでCollectionSequenceといった広い型ではなく、Listを使って表したくなる。

だが、このクラスを継承する場合を考えると、話は変わってくる。

from typing import List


class Bar:
    def __init__(self, N: int) -> None:
        self._foo = [Foo() for i in range(N)]

    def none_to_list(self) -> List[Foo]:
        return self._foo


class MyFoo(Foo):
    pass


class BarOfMyFoo(Bar):
    def __init__(self, N: int) -> None:
        self._myfoo = [MyFoo() for i in range(N)]

    def none_to_list(self) -> List[MyFoo]:
        # Mypy Error
        # Return type of "none_to_list" incompatible with supertype "Foo"
        return self._myfoo

この継承はエラーになる。というのも、Python の Generics の引数には covariant (共変) と contravariant (反変) と invariant (不変) があり、Collection[T]Sequence[T]が共変なのに対してList[T]は不変だからである。なので、これをSequenceに変えるとエラーは消える。

from typing import Sequence


class Bar:
    def __init__(self, N: int) -> None:
        self._foo = [Foo() for i in range(N)]

    def none_to_seq(self) -> Sequence[Foo]:
        return self._foo


class MyFoo(Foo):
    pass


class BarOfMyFoo(Bar):
    def __init__(self, N: int) -> None:
        self._myfoo = [MyFoo() for i in range(N)]

    def none_to_seq(self) -> Sequence[MyFoo]:  # No error
        return self._myfoo

ちなみに、Listのまま返り値の中身をFooであると宣言してもエラーは消えてくれない。

from typing import List


class Bar:
    def __init__(self, N: int) -> None:
        self._foo = [Foo() for i in range(N)]

    def none_to_list(self) -> List[Foo]:
        return self._foo


class MyFoo(Foo):
    pass


class BarOfMyFoo(Bar):
    def __init__(self, N: int) -> None:
        self._myfoo = [MyFoo() for i in range(N)]

    def none_to_list(self) -> List[Foo]:
        return self._myfoo
        # Invokes another mypy Error
        # Incompatible return value type (got "List[MyFoo]", expected "List[Foo]")

MyFooFooの子だと認識されるが、List[MyFoo]List[Foo]の子にはなりえないという話である。

継承されることを想定したクラスを書く際は注意してほしい。

追記:
もう少しだけ詳しく書いておくと、ListCollections.abc.MutableSequenceのサブクラスだが、SequenceCollections.abc.ImmutableSequenceのサブクラスなので、返したリストを変更される予定がないならSequenceを返すのがよい。
返したリストを変更される予定が少しでもあるなら、継承でList[MyFoo]を返してはいけない。なぜなら、変更する側は中身をFooだと思って変更を行うので、例えばFooのインスタンスをリストに追加されてしまうかもしれないからだ。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2