【TypeHint】list[SubClass]がlist[SuperClass]に受け付けられない【Python】
SubClassのlistはSuperClassのlistには代入できない
class SuperClass:
pass
class ChildClass(SuperClass):
pass
def process_children(children: list[SuperClass]):
pass
def process_child(child: ChildClass):
pass
child = ChildClass()
children = [child]
process_child(child) # works
process_children(children) # Argument of type "list[ChildClass]" cannot be assigned to parameter "children" of type "list[SuperClass]"
Pythonでタイプセーフなコーディングを楽しんでいた今日気づきました。
SubClassのインスタンスをSuperClass型の変数に代入することはできるのにlist[SubClass]はlist[SuperClass]方の変数に代入することはできないんです!
これはtupleやSequenceでは起こりません。listや一部のジェネリック型だけです。
list[SubClass]とlist[SuperClass]のような、ジェネリクス型引数の親子関係がそのまま受け継がれないような性質を不変(Invariant)といいます。
逆にtuple[Subclass]はtuple[SuperClass]に代入できるので共変(Covariant)といいます。
先に結論:Sequence or Iterableを使う
from typing import Iterable
class SuperClass:
pass
class ChildClass(SuperClass):
pass
def process_children(children: Iterable[SuperClass]):
pass
def process_child(child: ChildClass):
pass
child = ChildClass()
children = [child]
process_child(child) # works
process_children(children) # works!
listのさらに上位概念であるSequenceやIterableをtypingモジュールからインポートして使いましょう。大抵の場合受け取った変数をfor文で回したりするだけなので、Iterableであることがわかっていれば十分です。
でもlist固有のappendメソッドが使いたいんですけど?
と思ったあなた。それはデザインからして間違いなのです。
それが許されるとすると次のようなコードが合法になります。
class SuperClass:
pass
class ChildA(SuperClass):
def cry(self):
print("I am child a!")
class ChildB(SuperClass):
pass
def process_children(children: list[SuperClass]):
children.append(ChildB()) # SuperClassの子クラスなのでChildBを追加できる
child = ChildA()
children = [child]
process_children(children)
for cld in children:
cld.cry() # AttributeError 'ChildB' object has no attribute 'cry'
process_children関数の中でChildBをappendするのは合法です。なぜってSuperClassを継承したクラスのインスタンスだからですよね。
しかし呼び出し元のコードではchildrenはlist[ChildA]として定義されているのでその要素にはChildBには存在しないChildAとしての機能を期待してしまいます。
このような事態を避けるためlist[SuperClass]を受け取る変数にlist[ChildClass]を渡してはいけないのです。それはlistが可変オブジェクトだからです。tupleなら許される理由もわかりますね?tupleが変更不可能だからです。
まとめ
理屈はわかったけどSequenceとかIterableインポートするの面倒なのでデフォルトで使えるようにしてほしい。
Discussion