「このクラス、親子関係はどうなっているんだろう?」と迷ったことはありませんか。
Pythonでは、クラス同士の関係(継承)を簡単に調べるための関数としてissubclass()
が用意されています。
プログラムの規模が大きくなるほど、正しい継承関係を把握することは、バグを避けるうえでもとても大切です。ここでは Python 初心者・IT 初心者の方にもわかりやすく、issubclass() の基本から実用的な使い方、つまずきやすいポイントまで丁寧に解説します。
issubclass() はあるクラスが別のクラスのサブクラス(子クラス)かどうかを True/False で判定します。第二引数には単一のクラスだけでなく、クラスのタプルも渡せます。
つまり「このどれかの親に当たる?」という複数チェックも一度にできます。
構文は次のとおりです。
issubclass(subclass, superclass_or_tuple)
「クラスの関係を安全に確認したい」「条件分岐でクラスごとに処理を分けたい」といった場面で、とても頼りになる関数です。
まずはシンプルな継承関係で動作を確認しましょう。
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
# DogはAnimalのサブクラスか?
print(issubclass(Dog, Animal)) # True
# CatはAnimalのサブクラスか?
print(issubclass(Cat, Animal)) # True
# AnimalはDogのサブクラスか?
print(issubclass(Animal, Dog)) # False
Animal を親に持つ Dog と Cat は、どちらも Animal のサブクラスなので True が返ります。一方で、親である Animal は子である Dog のサブクラスではありませんから False になります。ここで覚えておきたいのは、直接の親子関係だけでなく「間接的な親子関係」でも True になることです。例えば「A → B → C」と継承が続くとき、C は A のサブクラスとみなされます。
Python は多重継承をサポートしています。issubclass() は多重継承や継承が何段にも連なる場合でも、正しく関係をたどって判定してくれます。
class Pet:
pass
class Trainable:
pass
class Dog(Pet, Trainable):
pass
class ServiceDog(Dog):
pass
print(issubclass(ServiceDog, Dog)) # True(直接の親)
print(issubclass(ServiceDog, Pet)) # True(祖先クラス)
print(issubclass(ServiceDog, Trainable)) # True(もう一方の祖先)
print(issubclass(Dog, ServiceDog)) # False(親は子のサブクラスではない)
ServiceDog は Dog を継承しており、Dog は Pet と Trainable を同時に継承しています。 このような複合的な関係でも、issubclass() は系譜をさかのぼって True/False を返します。複雑な階層でも見失わないのが心強いですね。
issubclass() はユーザー定義クラスだけでなく、list や dict といった組み込み型にも使えます。 Python のすべての新スタイルクラスは最終的に object を継承しているため、次のように判定できます。
print(issubclass(list, object)) # True
print(issubclass(dict, list)) # False
もう一つ、初心者が驚きやすいポイントとして「bool は int のサブクラス」という仕様があります。
print(issubclass(bool, int)) # True
True/False は内部的には 1/0 としてふるまえるため、bool は int の特別なサブクラスとして定義されています。型チェックや条件分岐で「なぜ通ってしまうの?」と悩んだときに役立つ知識です。
「このクラス、A か B のどちらかのサブクラスなら OK」といった複数条件を、一度に判定したくなることはありませんか。第二引数にタプルを渡すと、いずれかに当てはまれば True になります。
class Bird: pass
class Flyer: pass
class Eagle(Bird, Flyer): pass
print(issubclass(Eagle, (Bird, Flyer))) # True(どちらにも当てはまる)
print(issubclass(Bird, (Flyer, dict))) # False(どちらにも当てはまらない)
複数の型を許容する API を作るときなどに、シンプルなコードで堅牢なチェックが書けます。
issubclass() はクラス同士を比較する関数です。もしインスタンス(生成したオブジェクト)を第一引数に渡すと、TypeError が発生します。
初学者が最初に引っかかりやすいポイントなので注意しましょう。
class Animal: pass
class Dog(Animal): pass
d = Dog()
# 間違い:インスタンス d を渡している
# issubclass(d, Animal) # TypeError: issubclass() arg 1 must be a class
# 正しくはクラスを渡す
print(issubclass(Dog, Animal)) # True
「インスタンスが特定のクラス(またはそのサブクラス)かどうか」を調べたい場合は isinstance() を使います。このあと別セクション「isinstance() を使ったインスタンスのチェック」で詳しく扱いますので、併せて確認してみてください。
抽象基底クラス(Abstract Base Class、ABC)を使うと、明示的に継承していなくても「登録(register)」によって仮想的なサブクラスとみなせます。issubclass() はこの“仮想サブクラス”も True と判定します。
from abc import ABC, abstractmethod
class FileLike(ABC):
@abstractmethod
def read(self) -> bytes: ...
@abstractmethod
def write(self, data: bytes) -> int: ...
class SocketStream:
def read(self) -> bytes:
return b"..."
def write(self, data: bytes) -> int:
return len(data)
# 直接継承していないが、FileLike の契約を満たす前提で登録
FileLike.register(SocketStream)
print(issubclass(SocketStream, FileLike)) # True(仮想サブクラス)
「実装は満たしているが継承関係は持たせない」という設計でも、issubclass() は ABC の仕組みを理解して正しく判定してくれます。抽象クラスやプロトコル設計と一緒に使うと、型安全性と柔軟性を両立できます。
今回学習した内容をまとめます。
「このクラス、本当にこの親クラスの流儀に従っている?」と不安になったとき、まずは issubclass() で確かめてみましょう。継承関係の見取り図がクリアになれば、コードの可読性も保守性もグッと上がります。