発端
Djangoはモデル(django.db.models.Model)に対してメタクラスModelBaseを使い、いろいろと登録処理を行っている。
で、ソースを読んでいて、
class ModelBase(type):
def __new__(cls, name, bases, attrs):
parents = [b for b in bases if isinstance(b, ModelBase)]
みたいなコードがあったので「ふーん」とshellでisinstance(Question, Model)
1と打ってみたところ、
False
よく考えるとわかるのだが初め「あれ?」と思ったので理解したことをまとめておく。
isinstanceとは
isinstanceは第一引数のオブジェクトが第二引数のクラスのインスタンスならTrueを返す。例えば、
isinstance(123, int)
はTrueが返る。
先の入力は何がいけなかったのか
Questionはクラス、Modelもクラス。つまり、「QuestionクラスはModelクラスのインスタンスか?」と聞いてることになる。そんなことはない。Falseが返されるのは当然だ。サブクラスとインスタンスがごっちゃになってた。
ではどう聞けばよかったのか
isinstanceとは別にissubclassという関数がある。これを使えばいい。
issublass(Question, Model)
無事Trueが返る。
メタクラスとisinstance
Djangoのソースの話に戻ろう。Djangoのソースでは「これから作成されるクラス」のスーパークラス達がModelBaseのインスタンスかを調べていた2。第一引数はクラスだ。
何故これでいいのかというと、第二引数がtypeを継承したメタクラスだから。メタクラスのインスタンスはクラスなので、このチェックはModelクラスを継承したクラスであればTrueになる。つまり、以下の式はTrueが返される。
isinstance(Question, ModelBase)