前提
引数の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
返り値は自分で決めるものなので、受け取る側の引数と違ってできるだけ表す内容が狭い方がいい。
なのでCollection
やSequence
といった広い型ではなく、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]")
MyFoo
はFoo
の子だと認識されるが、List[MyFoo]
はList[Foo]
の子にはなりえないという話である。
継承されることを想定したクラスを書く際は注意してほしい。
追記:
もう少しだけ詳しく書いておくと、List
はCollections.abc.MutableSequence
のサブクラスだが、Sequence
はCollections.abc.ImmutableSequence
のサブクラスなので、返したリストを変更される予定がないならSequence
を返すのがよい。
返したリストを変更される予定が少しでもあるなら、継承でList[MyFoo]
を返してはいけない。なぜなら、変更する側は中身をFoo
だと思って変更を行うので、例えばFoo
のインスタンスをリストに追加されてしまうかもしれないからだ。