Skip to content

Instantly share code, notes, and snippets.

@ncaq
Created August 31, 2023 14:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ncaq/bbc41e1d6b05c125515bf833293a5370 to your computer and use it in GitHub Desktop.
Save ncaq/bbc41e1d6b05c125515bf833293a5370 to your computer and use it in GitHub Desktop.
PythonでBがAを継承してもlist[B]変数をlist[A]を受け取る関数に渡せない理由の一つの具体例
#!/usr/bin/env python3
# Pythonのメジャーな型チェッカーは`B`が`A`を継承していても、
# `list[A]`を受け取る関数に`list[B]`を渡すとエラーを出力します。
# 型システム的な面倒な話は置いておいて、実際にこれがエラーじゃないと問題になってしまう例を示します。
# 別にdataclassでなくても構わないのですが、printのやりやすさで使っています。
from dataclasses import dataclass
# サブタイピング関係にあるクラスAとBを定義。
@dataclass
class A:
x: int
@dataclass
class B(A):
y: int
# リストを定義。
l0: list[B] = [B(0, 1)]
print(l0) # [B(x=0, y=1)]
# 実用的な例ではありませんが、リストの要素を全て固定値で埋めてしまう関数があるとします。
def fill(xs: list[A]) -> None:
xs.clear()
xs += [A(0)]
# この関数にそのままの`list[A]`を呼び出しても型的には問題ありません。
l1: list[A] = [A(1), A(2), A(3)]
fill(l1)
print(l1) # [A(x=0)]
# さて、`list[B]`を渡してみます。
l2: list[B] = [B(1, 2), B(3, 4), B(5, 6)]
fill(l2) # Pyrightなどが全力で警告しますが無視します。
print(l2) # [A(x=0)]
# さてl2要素にはB型のインスタンスが入っているはずなので、要素が存在していれば、以下は安全に実行できるはずです。
print(l2[0].y)
# しかし、実行すると以下のようなエラーが出ます。
# AttributeError: 'A' object has no attribute 'y'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment