unittestパッケージのソースコードリーディングをする中で遭遇したisinstance(XXX, type)について整理します。Pythonのバージョンは3.6.3です。
isinstance関数とは
公式ドキュメントにあるように,あるオブジェクトの型(Class)を判定するときに利用します。
利用例
>>> a = 2
>>> isinstance(a, int)
True
>>> b = [1,2]
>>> isinstance(b, list)
True
unittestでの利用方法
unittestのmain.pyの236行目に以下のコードがあります。
236 if isinstance(self.testRunner, type):
この第2引数に指定されている"type"は何でしょうか。これは上記で示した"int","list"といったクラスではなく,メタクラスと呼ばれるものです。
メタクラスとは?
Pythonのオブジェクトはクラスのインスタンスとして生成されます。また,Pythonではクラスもオブジェクトです。そして,クラスはメタクラスのインスタンスとなります。このため,上記で示したunittestのコードは,"self.testRunner"オブジェクトがtypeのインスタンスであるかどうか,つまりクラスであるかどうかを判定しています。main.py:234行目の上を確認すると,isinstance関数の第1引数である"self.testRunner"にrunner.pyで定義されたTextTestRunnerクラスがif文を満たす場合に代入されることからもそれがわかります。
232 if self.testRunner is None:
233 self.testRunner = runner.TextTestRunner
234 if isinstance(self.testRunner, type):
isinstanceの例からも,そのことがわかります。
>>> isinstance(int, type)
True
>>> isinstance(list, type)
True
Pythonにおけるtypeとは
typeは型を返す組み込み関数として利用するのが一般的ですが,引き数を3つ指定した場合,クラスを生成することができます。
>>> type(list)
<class 'type'>
>>> a = list()
>>> type(a)
<class 'list'>
>>> X = type('X', (object,), dict(a=1))
>>> isinstance(X, type)
True
>>> X.__dict__
mappingproxy({'a': 1, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'X' objects>, '__weakref__': <attribute '__weakref__' of 'X' objects>, '__doc__': None})
>>> class Y(object):
... a = 1
...
>>> isinstance(Y, type)
True
>>> Y.__dict__
mappingproxy({'__module__': '__main__', 'a': 1, '__dict__': <attribute '__dict__' of 'Y' objects>, '__weakref__': <attribute '__weakref__' of 'Y' objects>, '__doc__': None})
typeからみれば,すべてのクラスはインスタンスです。これはすべてのクラスの親クラスとなるobjectも例外ではありません。また,type自身もtypeのインスタンスです。
>>> isinstance(object, type)
True
>>>
>>> isinstance(type, type)
True
typeとobjectの関係
typeはメタクラスなので,すべてのクラスのクラスとなります。このため,objectもtypeのインスタンスになりますが,継承関係においては,objectがtypeの親クラスとなります。
>>> issubclass(type, object)
True
>>> issubclass(object, type)
False