Python組み込み関数7 -isinstance・issubclass編-
Pythonおさらい第7段
制約として公式ドキュメントのみ参照*1として、読む癖をつける訓練も兼ねる。
今回はisinstance関数
とissubclass関数
の2種類について。
前回に引き続き、特定の条件を満たすかどうか判定し、bool
を返す関数。
重要な関数ではあるものの、True
かFalse
が返るだけなので実に地味!
とはいえ、よく使うのは間違いないのでしっかり押さえておきたい。
読み込むドキュメントはこちら[isinstance, issubclass]
構成
今回の関数は以下のようになっている。
isinstance(object, classinfo)
issubclass(class, classinfo)
classinfo
とはなんぞや?
ドキュメントを読んでみると、型オブジェクト
, 型オブジェクトのタプル
あるいは再帰的に複数のタプル
だそうだが……再帰的に複数のタプルとはなんぞや?
謎は深まるばかりであるが使ってみた段階でわかることに期待したい。
関数 | 内容 |
---|---|
isinstance | object がclassinfo のインスタンスかどうか |
issubclass | class がclassinfo のサブクラスかどうかクラスはそれ自身のサブクラスとみなされる |
使ってみる
ひとまず、普通に単体のクラスで試してみて、classinfo
については専用の項目で見ていくこととする。
また、クラス定義などの共通部分については前提コードを使いまわす。
使ってみる-前提コード
from abc import ABC, abstractmethod class Creature(ABC): def __init__(self): self.alive = True @abstractmethod def desperate(self): pass def death(self): self.desperate() self.alive = False class Runner(ABC): def run(self): print(f'{self}は走った!') class Person(Creature): def __init__(self, name: str, age: int): self.name = name self.age = age super().__init__() def __repr__(self) -> str: me = f'{self.name}({self.age})' return me class Man(Person): def desperate(self): print(f'{self}: グワーッ!') class Woman(Person): def desperate(self): print(f'{self}: キャーッ!') class RunningMan(Runner, Man): pass man = Man('Otsuhachi', 28) rman = RunningMan('Taro', 55) woman = Woman('Hanako', 20) check_classes = [ABC, Creature, Person, Man, Woman, Runner, RunningMan] member = [man, rman, woman]
どうしようもないクソコードが爆誕してしまった……。
クラスの繋がりは以下の通り。
ABC ├ Creature │ └ Person │ ├ Woman │ └ Man │ ├─ RunningMan └───────── Runner
使ってみる-isinstance
前提コードで定義したmember
とcheck_classes
それぞれの組み合わせでisinstance
を行い、その結果を出力する。
実行するコードは以下の通り。
# ここより上の行に前提コードがある for m in member: print(f'{m}<{type(m).__name__}>') print('-' * 30) for cc in check_classes: print(f'{cc.__name__}: {isinstance(m,cc)}') print()
Otsuhachi(28)<Man> ------------------------------ ABC: True Creature: True Person: True Man: True Woman: False Runner: False RunningMan: False Taro(55)<RunningMan> ------------------------------ ABC: True Creature: True Person: True Man: True Woman: False Runner: True RunningMan: True Hanako(20)<Woman> ------------------------------ ABC: True Creature: True Person: True Man: False Woman: True Runner: False RunningMan: False
子クラスのインスタンスが親クラスをclassinfo
としてisinstance
したときにはTrue
が返っているのがわかる。
例えばman
はABC
->Creature
->Person
->Man
という繋がりを持つ、Man
クラスのインスタンスなので、classinfo
がABC
, Creature
, Person
, Man
のいずれでもTrue
となっている。
逆に同じ親クラスを持つWoman
は直接継承関係にないのでFalse
になるし、
Man
クラスの子クラスであるRunningMan
もFalse
となる。
object
のクラスが、どこかの段階でclassinfo
クラスを継承しているかを調べる関数だということが理解できた。
使ってみる-issubclass
check_classes[i]
がcheck_classes[j]
のサブクラスであるかを調べて出力する。
0 <= i,j < len(check_classes)
実行するコードは以下の通り。
# ここより上の行に前提コードがある for subcls in check_classes: print(subcls.__name__) print('-' * 30) for cls in check_classes: print(f'{cls.__name__}: {issubclass(subcls,cls)}') print()
ABC ------------------------------ ABC: True Creature: False Person: False Man: False Woman: False Runner: False RunningMan: False Creature ------------------------------ ABC: True Creature: True Person: False Man: False Woman: False Runner: False RunningMan: False Person ------------------------------ ABC: True Creature: True Person: True Man: False Woman: False Runner: False RunningMan: False Man ------------------------------ ABC: True Creature: True Person: True Man: True Woman: False Runner: False RunningMan: False Woman ------------------------------ ABC: True Creature: True Person: True Man: False Woman: True Runner: False RunningMan: False Runner ------------------------------ ABC: True Creature: False Person: False Man: False Woman: False Runner: True RunningMan: False RunningMan ------------------------------ ABC: True Creature: True Person: True Man: True Woman: False Runner: True RunningMan: True
自身と、自身のクラスが継承しているクラスがclassinfo
の場合True
が返るのがわかる。
isinstance
と違うのは第一引数がclass
な点くらいだろうか……。
メタプログラミングとかの際には必要になるのかもしれないが、今のところisinstance
ほど魅力を感じない。
使ってみる-classinfo
2つの関数共通の第二引数であるclassinfo
について。
受け取る形式は以下の3通り。
型オブジェクト
型オブジェクトのタプル
再帰的に複数の型オブジェクトのタプル
1.
, 2.
についてはわかりやすいのでさらっと以下のコードで確かめる。
# ここより上の行に前提コードがある classinfo = [str, (str, Runner)] for m in member: print(f'{m}<{type(m).__name__}>') print('-' * 30) for ci in classinfo: if type(ci) is tuple: c_name = tuple(map(lambda x: x.__name__, ci)) else: c_name = ci.__name__ print(f'{c_name}: {isinstance(m,ci)}') print()
Otsuhachi(28)<Man> ------------------------------ str: False ('str', 'Runner'): False Taro(55)<RunningMan> ------------------------------ str: False ('str', 'Runner'): True Hanako(20)<Woman> ------------------------------ str: False ('str', 'Runner'): False
型オブジェクトのタプルを渡した場合、いずれかのクラスがTrue
を返せばTrue
を返しているのがわかる。
以下のようなイメージすればわかりやすいだろう。
def tupled_isinstance(object, classinfo: tuple) -> bool: for ci in classinfo: if isinstance(object, ci): return True return False
問題なのは3.
だが、再帰的に複数のタプルとはなんなのか?
確信はないが、入れ子構造のタプルであると思われる。
以下のコードで確認してみる。
# ここより上の行に前提コードがある def convert_classnames(ct: tuple): res = [] for c in ct: if type(c) is tuple: res.append(convert_classnames(c)) else: res.append(c.__name__) return tuple(res) classinfo = [(str, Man), (str, (Woman, (Runner, RunningMan)))] for m in member: print(f'{m}<{type(m).__name__}>') print('-' * 60) for ci in classinfo: c_name = convert_classnames(ci) print(f'{c_name}: {isinstance(m,ci)}') print()
Otsuhachi(28)<Man> ------------------------------------------------------------ ('str', 'Man'): True ('str', ('Woman', ('Runner', 'RunningMan'))): False Taro(55)<RunningMan> ------------------------------------------------------------ ('str', 'Man'): True ('str', ('Woman', ('Runner', 'RunningMan'))): True Hanako(20)<Woman> ------------------------------------------------------------ ('str', 'Man'): False ('str', ('Woman', ('Runner', 'RunningMan'))): True
これが正しい再帰的に複数のタプル
であるかはわからないが、入れ子構造のタプルを渡した場合も問題なく判定していることは分かった。
締め
今回の2つの関数についてはクラスの継承
がどんなものか、インスタンスとクラスオブジェクトの違い
の2点がなんとなくでもわかっていれば、問題なく使うことができるだろう。
時々やらかすのが、classinfo
にインスタンスを指定してしまうことぐらいだろうか。
タプルの再帰に気を遣うぐらいならclassinfo
がインスタンスの時にtype(instance)
してくれる方がよほどありがたく感じる。
タプルはともかく、再帰的なタプルは本当にわからん。
*1:わかりやすく解説している他サイトを見ない