Pythonのsuperについて調べた（mroとメタクラス）

# Pythonのsuperについて調べた（mroとメタクラス）

## 前置き

この記事は`super`の実践的な使い方を紹介するような記事ではなく、個人的に気になっていた`super`のよく分からない（おそらく非実践的な）挙動を調べてみたのでまとめた記事です。

## 継承の基本

まずは継承の基本的な事柄について再確認します。すでに分かっている場合はこの章を読み飛ばしてもらっても問題ありません。ここでは一般的な`super`の使い方を説明します。\
では早速クラスを継承して`super`を使ってみましょう。\
以下では親クラスには`Par`という名前を、子クラスには`Chi`という名前を優先的に使うこととします（ParentとChildより）[^1]。しかし、多重継承やメタクラスを扱う関係上、これに従うことは難しいと判断した場合はクラス名に`A`だとか`B`だとか使い始めると思います。

```python
class Par:
    def f(self):
        print("Par")


class Chi(Par):
    def f(self):
        super().f()


Chi().f()  # Par
```

特に言うことはない、`super`を使用する最小単位の例ですね。\
継承に関する話でよくある間違いとしては以下の様な物があると思います。

- `super.f()`って形で呼び出しちゃってる
- メソッドを定義しているブロックの外で暗黙的に`super`を呼んじゃってる（[後述](#superの明示的初期化暗黙的初期化)）
- そもそもよく見たら`Par`を継承してなかった

しかし実はこの使い方は端折った使い方であるということを述べていきたいと思います。多くの場合はこの書き方をそのまま覚えて、何が起きているのか分からないまま書いているのではないでしょうか。\
次の章からはこの使い方を1回忘れて、基礎的な使い方から解説していこうと思います。

## 多重継承と__mro__の順番

`super`の解説を行う前に、クリアにしなくてはいけないことがあります。もちろん継承です。\
Pythonには多重継承やメタクラスといった概念が存在し、我々を苦しめます。この章では`__mro__`を通して普通の継承と多重継承についてまとめていきます。\
一番最初に結論だけ述べておきましょう。\
***`Chi.__mro__`は`Chi`クラスから見たすべての親クラスを、参照する順番に格納しているタプル型オブジェクトである。***\
「親子関係の順番」ではなく「参照する順番」であることがポイントです。

それでは説明していきたいのですが、継承について触れる前にクラスとオブジェクトについて再確認しましょう。まず、Pythonではすべてがオブジェクトです。つまり、関数やクラスであっても変数に代入したり属性を呼び出したりできます。やってみましょう。

```python
def f():  # 関数もまたオブジェクト
    pass


g = f  # 代入できる
print(g.__class__)  # <class 'function'>
print(g.__name__)  # f
print(g.__code__)  # <code object f at 0x000001B527E3FCB0, file "d:\hogehoge.py", line 1>
```

代入はともかくとして、属性は色々ありますね（アンダーバーに囲まれた特殊属性しかありませんが）。`g`に代入されたオブジェクトは`function`クラスのオブジェクトで名前が`f`で`d:\hogehoge.py`の1行目で定義されたコードを持っている……といったことが見て取れます。\
オブジェクトの属性はそのオブジェクトを説明するにほとんど十分な情報が入っていますが、idなどは見当たらないので属性からオブジェクトを逆算できるほど十分という訳ではではないのかもしれないですね。\
今回は関数で試しましたが当然クラスもオブジェクトです。クラスの属性も見てみましょう。

```python
class MyClass:
    """It is MyClass"""

    pass


print(MyClass.__class__)  # <class 'type'>
print(MyClass.__doc__)  # It is MyClass
print(MyClass.__mro__)  # (<class '__main__.MyClass'>, <class 'object'>)
```

`MyClass`は`type`クラスのオブジェクトでDocstringが`It is MyClass`で`__mro__`とやらが`MyClass`と`object`だそうです。\
`type`クラスについては[後で扱います](#メタクラスを使った応用)。今大事なのは`__mro__`です。\
これはすべてのクラスが持つ属性で、そのクラスの親クラスの情報を持っています。\
ただし、この記事で親クラスと言った時は自分自身も含むこととします。

---

少し話が逸れますが、自作クラスだけではなく他のクラスについても見てみましょう。

```python
def f():
    pass


function = f.__class__

print(int.__mro__)  # (<class 'int'>, <class 'object'>)
print(str.__mro__)  # (<class 'str'>, <class 'object'>)
print(bool.__mro__)  # (<class 'bool'>, <class 'int'>, <class 'object'>)
print(function.__mro__)  # (<class 'function'>, <class 'object'>)
print(type.__mro__)  # (<class 'type'>, <class 'object'>)
```

どのクラスも`object`クラスを親クラスとして持っています。これがPythonはすべてがオブジェクトであると言われる理由の1つです。すべてはオブジェクトクラス（を継承しているクラス）のインスタンスなのだからオブジェクトクラスだ、という訳です。定義から明らかですね。\
`bool`が`int`を継承しているのは妥当といえば妥当ですが、言われてみないと普段は意識できませんね。Pythonには他にも`float`, `complex`, `list`, `tuple`, `set`, `frozenset`, `dict`などの組み込み型があります。これらはいずれも`object`クラスのみを継承するクラスになっています。~~つまらないですね。~~

```python
def f():
    pass


print(isinstance(0, object))  # True
print(isinstance("hoge", object))  # True
print(isinstance(f, object))  # True
print(isinstance(object, object))  # True
```

オブジェクトクラスすらオブジェクトクラスとなると数学的な定義のイメージで考えると定義の循環で矛盾が起きてしまいますね。

---

話を戻して`__mro__`の挙動について詳細に見ていきます。`__mro__`は親クラスをまとめているということでしたが、親クラスが複数ある場合はどうなるのでしょうか。

```python
class Par:
    pass


class Chi(Par):
    pass


print(Chi.__mro__)  # (<class '__main__.Chi'>, <class '__main__.Par'>, <class 'object'>)
```

このように、先の`bool`の例でも見ましたが、子から親に向かって順々に記述されています。\
さらに親クラスを増やしてみましょう。次のような継承関係を持つ場合です。

![pu1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/4cce9d8c-f681-0e3a-9d55-4ead271a5eb6.png)

```python
class Par:
    pass


class Chi(Par):
    pass


class GrandChi(Chi):
    pass


print(GrandChi.__mro__)  # (<class '__main__.GrandChi'>, <class '__main__.Chi'>, <class '__main__.Par'>, <class 'object'>)
```

予想通りの結果ですね。この感じだと`Chi.__mro__`は`Chi`クラスの親クラスを親子関係の順番に格納したタプル型オブジェクトであるように見えます。\
つまり、`__mro__`が`(..., <class 'Hoge'>, <class 'Fuga'>, ...)`のようになっていた場合、常に`Fuga`クラスは`Hoge`クラスの親クラスである様に見えます。\
しかしそれは誤りで、上述の様なケースでも`Fuga`クラスが`Hoge`クラスの親クラスではない場合があります。それはPythonが多重継承という概念を採用しているからです。\
では多重継承と`__mro__`について見ていきましょう。\
多重継承とは簡単に言えば親がたくさんいる子どもで、例えば以下のようなケースを指します。

![pu2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/1dfc3d18-acee-4e08-96e2-80ecc6962691.png)

```python
class Par0:
    pass


class Par1:
    pass


class Par2:
    pass


class Chi(Par0, Par1, Par2):  # 多重継承
    pass


print(Chi.__mro__)  # (<class '__main__.Chi'>, <class '__main__.Par0'>, <class '__main__.Par1'>, <class '__main__.Par2'>, <class 'object'>)
```

`Chi`クラスが`Par0`, `Par1`, `Par2`の3種のクラスを継承しています。この場合の`__mro__`はこのように継承する際に記述した順がそのまま入ります。以降ではこれ（継承する際に記述した順）を**継承順**と呼ぶことにします（造語）。上記の例だと、`Par0`は`Par1`よりも継承順が高いと記述します。\
もうちょっと複雑な例を見てみましょう。

![pu3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/b487ab80-2351-9496-b9e6-71a5d71c1b55.png)

```python
class Par:
    pass


class Chi0(Par):
    pass


class Chi1(Par):
    pass


class GrandChi(Chi0, Chi1):
    pass


print(GrandChi.__mro__)  # (<class '__main__.GrandChi'>, <class '__main__.Chi0'>, <class '__main__.Chi1'>, <class '__main__.Par'>, <class 'object'>)
```

どうやら`__mro__`に追加される順番としては、感覚的には上の階層に行く前に同階層にあるクラスをすべて網羅すると解釈すれば問題無さそうです。\
これに関する詳しい話は多重継承のひし形継承問題だとかダイアモンド継承だとかMethod Resolution Orderとかのキーワードで調べるとたくさん出てきます。Pythonに限った話をするなら、C3線形化アルゴリズムというアルゴリズムを使っているようです。\
しかし感覚的な理解では少し不安ですし、だからといってアルゴリズムを一通り読んだだけでは分かったような分からないような感覚になってしまいます。ということで、次の章ではそのアルゴリズムを直感的に理解できる形で説明することに挑戦してみます。

## __mro__の順番を直感的に理解しよう

[このページ（The Python 2.3 Method Resolution Order）](https://www.python.org/download/releases/2.3/mro/)ではよりアルゴリズミックな解説があるので、このアルゴリズムを実装したいという場合や、この記事よりも公式ドキュメントの方が頼れるぜと思う場合はこちらを（／も）参照した方が良いと思います。この記事では挙動を理解することを最優先とします。\
今更ですが、解説は以下のような図を元にして説明していきます。クラス名と継承関係しか記述されていないクラス図みたいなもんですね。子から親に向けて矢印が出ています。

![pu1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/0b34df4c-0200-3fe6-d722-7c2620abfe25.png)

また、矢印は継承順に沿って左から順に並ぶように描かれているものとします。

![pu2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/46abb445-abcd-8f17-51e7-c32e3096fa39.png)

まずは次のような図で表される継承を考えます。先程見た継承です。

![pu3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/7a1f0d53-f1a3-30e7-d7d9-ef1dcae056b8.png)

この時、`GrandChi`クラスが自分からスタートしてこれら4つのクラスを仲間にしていくという状況を考えます。仲間にする際に以下のルールを設けます。

- **優先度ルール** 仲間にするクラスの優先度は以下で定める。
  1. 上に移動する時は左側の矢印（継承順が高いクラス）を優先する。
  2. 古い仲間の親クラスよりも新しい仲間の親クラスを優先する。
- **制約ルール** クラスを仲間にする際は以下を守る。
  1. 子クラスが全員仲間になっていない親クラスは仲間にできない。
  2. そのクラスよりも継承順が高いクラスが仲間になっていない場合は仲間にできない。

ルールは少しややこしいので、図を元に具体的な動きを見てみましょう。ボードゲームでコマを動かす感覚で理解すると分かりやすいと思います。

最初は`GrandChi`一人ぼっちです。`GrandChi`は親クラスとして`Chi0`と`Chi1`の2クラスがあり、継承順は`Chi0`が先です。\
そこで優先度ルールの1に従い`Chi0`を仲間に入れるとしましょう。`Chi0`は子クラスが`GrandChi`しかいないので`GrandChi`に誘われたらすぐに仲間になってくれました。

![pu3-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/b35e11cf-d3a6-268d-84a3-c46095a09ef1.png)

次に優先度ルールの2に従い`Chi0`の親である`Par`も誘ってみましょう。しかし制約ルールの1によって`Par`は`Chi1`がハブにされているのが気に食わないようです。

![pu3-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/25a7127f-0661-fb4a-038e-1002d1b16097.png)

そこで次は`Chi1`を仲間に誘いました。`Chi1`も同じく子クラスが`GrandChi`しかいないのですぐに仲間になってくれました。

![pu3-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/8c3a1086-73d0-b1e2-9030-96fa152f9afa.png)

最後に`Par`です。`Par`の子クラスは既に全員仲間なので、`Par`も仲間に入ってくれました。

![pu3-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/d59acb39-5357-f5eb-4164-2d4814572a65.png)

この時仲間に入ったクラスの順番（=`GrandChi`, `Chi0`, `Chi1`, `Par`）こそが`GrandChi.__mro__`に格納されるクラスの順番になります。

別の例を見てみましょう。一番上の`G`を仲間にすることが目標です。

![pu4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/98ab9545-7999-7969-8a05-f1e44c7aa273.png)

まず`A`は`B`を仲間にし、その足で`D`も仲間にします。`B`は`A`のみ、`D`は`B`のみを子クラスとするので、ここまでは順調ですね。

![pu4-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/53c0314d-6277-b9f6-9cc2-ea812930fa35.png)

しかし、そのまま`G`に向かうと`G`の子クラス（`E`と`F`）がいないので戻ります。さらに`E`も`E`の子クラス（`C`）がいないという理由で仲間にできません。

![pu4-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/bdf6b22a-990a-128a-299d-3529da456032.png)

そこで、`C`を仲間にして、そのまま`E`も仲間にしました。それでも`G`は仲間になりません。`F`が残っているからです。

![pu4-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/bd724caf-db75-79c4-fb76-eb08e4b6d9ce.png)

そして遂に`F`が仲間になり、`G`が仲間になります。

![pu4-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/fbbde086-cefd-d660-d484-35a554cbe850.png)

よってこの場合、`A.__mro__`の順番は`A`, `B`, `D`, `C`, `E`, `F`, `G`です。

このように`__mro__`の順番は図さえイメージできれば、メモ帳や紙がなくても簡単に理解できるのです。

なお、このアルゴリズムは簡単な継承関係であれば順番付けが可能ですが、全てのクラスを仲間にする前にアルゴリズムが停止した場合に限り順番付けが不可能です。\
何が言いたいかというと、C3線形化アルゴリズムというアルゴリズムはすべての継承関係に対応している訳では無いということです。\
以下の例を見てください。

![pu5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/af2e98f9-2d0b-bae4-e35d-3b8a75d76abc.png)

この例の場合に上のアルゴリズムを試してみてください。すると`GrandChi`の次に`Chi`を仲間にしようとすると（`Chi`よりも継承順が高い`Par`がまだ仲間にできていないので）制約ルールの2に抵触し、`Par`を仲間にしようとすると（`Par`の子クラスである`Chi`をまだ仲間にできていないので）制約ルールの1に抵触します。\
このような場合はPythonのデフォルトのアルゴリズム（=C3線形化アルゴリズム）では扱えない継承関係なので、基本的にはどうしようもないです。

```python
class Par:
    pass


class Chi(Par):
    pass


class GrandChi(Par, Chi):  # TypeError: Cannot create a consistent method resolution order (MRO) for bases Par, Chi
    pass
```

どうしてもPythonでも扱えない継承関係を扱いたい時や、C3線形化アルゴリズム以外のアルゴリズムを使いたいといった場合は`mro`メソッドを変更することで実現できるらしいです。\
すべてのクラスは`__mro__`属性を持っていますが、これを解決しているのが`mro`メソッドです。そしてこれを書き換えることでC3線形化アルゴリズムではない方法での継承が可能であるといった旨の言葉が[公式のドキュメント](https://docs.python.org/ja/3/library/stdtypes.html#class.mro)に存在します。\
その方法にはメタクラスを使用する必要があるので、詳しくは[メタクラスに関する章](#メタクラスを使った応用)で行います。

最後に、1つ用語を定義しておきます（造語です）。\
上の例で`D`の親クラスはどれでしょうか。もちろん`G`です。\
しかし、`A.__mro__`において`D`の次のクラスはどれでしょうか。それは`C`です。\
これ（`A.__mro__`における`D`の次のクラス）を、**（`A`から見た）`D`の（相対）親クラス**と呼ぶこととします。括弧内は誤解の恐れがない限りは省略することもあります。\
同様に、`C`の相対親クラスは`E`です。

## superの基本

`super`はクラスです。`super`クラスです[^2]。コンストラクタは2つの引数を持ち、例えば`super(par, chi)`というふうに親子関係にあるクラスなどを入れることが出来ます。ここでもし第一引数`par`と第二引数`chi`の間に親子関係がない場合は以下の例外が発生します。

```text
TypeError: super(type, obj): obj must be an instance or subtype of type
```

そうして得られる`super`クラスのインスタンスは`chi`から見た`par`の相対的親クラスと同じ挙動をします。\
早速例を見ましょう。

```python
class Par:
    VAR = "Par"


class Chi(Par):
    VAR = "Chi"


class GrandChi(Chi):
    VAR = "GrandChi"


s = super(Chi, GrandChi)
print(s.VAR)  # Par
```

確かに`Chi`の親クラスである`Par`のような挙動をしていますね。では相対的かどうかも確認しておきましょう。

```python
class Par:
    VAR = "Par"


class Chi0(Par):
    VAR = "Chi0"


class Chi1(Par):
    VAR = "Chi1"


class GrandChi(Chi0, Chi1):
    VAR = "GrandChi"


s = super(Chi0, GrandChi)
print(s.VAR)  # Chi1
```

ちゃんと`Chi0`の親クラスである`Par`ではなく、相対的親クラスである`Chi1`の値が得られました。ではこの場合、`s`のクラスを確認すれば`Chi1`になって……

```python
class Par:
    VAR = "Par"


class Chi(Par):
    VAR = "Chi"


class GrandChi(Chi):
    VAR = "GrandChi"


s = super(Chi, GrandChi)
print(s.__class__)  # <class 'super'>
```

ならないですね。さっきも言いましたが`super(Chi, GrandChi)`はあくまで`super`クラスのインスタンスです。\
でも`super`クラスには`VAR`属性なんて存在しないはずなのですが、さっきの例ではたしかに例外を発生させずに`VAR`属性を呼び出すことができました。\
脱線になりますが念の為`super`オブジェクトの属性の一覧を取得して確認してみましょう。\
Pythonにはオブジェクトの属性の一覧を取得する方法があります。それを使って属性を表示してみましょう。\
ちなみに`inspect`モジュールの`getmembers`関数は`dir`関数を使って取得できるよりも多くの属性を取得できるらしいです（詳しくは知らない）。

```python
from inspect import getmembers


class Par:
    VAR = "Par"


class Chi(Par):
    VAR = "Chi"


class GrandChi(Chi):
    VAR = "GrandChi"


s = super(Chi, GrandChi)
print(list(dict(getmembers(s))))
# [
#   '__class__',
#   '__delattr__',
#   '__dir__',
#   '__doc__',
#   '__eq__',
#   '__format__',
#   '__ge__',
#   '__get__',
#   '__getattribute__',
#   '__gt__',
#   '__hash__',
#   '__init__',
#   '__init_subclass__',
#   '__le__',
#   '__lt__',
#   '__ne__',
#   '__new__',
#   '__reduce__',
#   '__reduce_ex__',
#   '__repr__',
#   '__self__',
#   '__self_class__',
#   '__setattr__',
#   '__sizeof__',
#   '__str__',
#   '__subclasshook__',
#   '__thisclass__',
# ]
```

やはり`super`クラスのオブジェクトの属性に`VAR`はないみたいですね。ですが振る舞っているクラスに定義してある属性なら呼び出しても例外は出ないみたいです。\
もし親クラスに定義してない属性である`hoge`を呼び出すとどうなるのでしょうか。以下のような例外が発生します。

```text
AttributeError: 'super' object has no attribute 'hoge'
（そんなこと言ったらVARだってsuperクラスのオブジェクトには無い属性なんですけれどね……）
```

予想するに、`super`インスタンス（`s`）の特殊メソッドである`__getattribute__`か`__getattr__`が呼びだされたタイミングで相対的親クラスにそのような属性があるかどうか探しに行って、もし見つからなければ`s`自身の属性を確認している……というアルゴリズムのではないでしょうか……？（よく分からない）

実は`super`はもう1つの使い方があります。というかむしろ通常はこっちを使うことを想定している気がします。\
それは`super`に2つの引数を渡すのは先程と同じなのですが、第二引数`chi`が第一引数`par`の子クラスのインスタンスである場合です。\
先程と異なる点を挙げるなら、第二引数がクラスではなくてインスタンスであるということですね。\
例を見てみましょう。

```python
class Par:
    VAR = "Par"


class Chi(Par):
    VAR = "Chi"


chi = Chi()
s = super(Chi, chi)
print(s.VAR)  # Par
```

動きましたね。別にさっきと変わらないというか、いわゆるシンタックスシュガーってやつでしょうか。\
実はそうではなく、この場合は先程までとは少しだけ異なる挙動をします。親クラスのインスタンスメソッドが呼び出せるのです。つまり、こうです。

```python
class Par:
    def method(self):
        print("Par")


class Chi(Par):
    def method(self):
        print("Chi")


chi = Chi()
s = super(Chi, chi)
s.method()  # Par
```

先程までの（第二引数にインスタンスではなくクラスを渡す）方法では当然これは例外を吐いていました。クラスからインスタンスメソッドを呼び出した時に吐く例外と同じ例外です。

```text
TypeError: Par.method() missing 1 required positional argument: 'self'
```

つまり`super`は、第二引数にクラスを入れたら相対的親クラス、インスタンスを入れたら相対的親クラスのインスタンスみたいな振る舞いをしてくれるということですね。

## superの明示的初期化/暗黙的初期化

ではそろそろ`super`の普段使っている使っている方法の話をしましょう。要は[これ](#継承の基本)です。\
引数なしでもOKなの？そうです、OKです。ここでは引数のない初期化の仕方を`superの`**暗黙的初期化**と呼ぶことにします。逆に引数を書く場合を**明示的初期化**と呼びます（造語）。\
引数を書かなくて良いのは楽ですが、その方法が使える場所には制限があります。

まず、class文のブロック外で引数なしで`super`を呼び出すと以下の例外が発生します。

```text
RuntimeError: super(): __class__ cell not found
```

また、class文のブロック内であっても、メソッド内に記述していないと以下の例外が発生します。\
より正確には、`super`が呼び出される行が`method`クラスの属性を定義するdef文のブロック内でないと暗黙的な初期化はできません。

```text
RuntimeError: super(): no arguments
```

あくまでメソッドでないとダメです。ゆえに、`staticmethod`でデコレートされたメソッドは`method`クラスではなく`function`クラスになるので、`RuntimeError: super(): no arguments`が発生してしまいます。

断言は出来ませんが`super`の暗黙的初期化はシンタックスシュガーだと思います。つまり、暗黙的初期化を明示的初期化に書き換えられます。やってみましょう。

```python
class Par:
    def method(self):
        print("Par@instance_method")

    @classmethod
    def cls_method(cls):
        print("Par@class_method")


class Chi(Par):
    def method(self):
        s = super(Chi, self)  # 明示的初期化！
        s.cls_method()  # クラスメソッドを呼び出してみる
        s.method()  # インスタンスメソッドを呼び出してみる

    @classmethod
    def cls_method(cls):
        s = super(Chi, cls)  # 明示的初期化！
        s.cls_method()  # クラスメソッドを呼び出してみる
        s.method()  # インスタンスメソッドを呼び出してみる


chi = Chi()
chi.method()  # 問題なし
Chi.cls_method()  # TypeError: Par.method() missing 1 required positional argument: 'self'
```

ちゃんと、クラスメソッドからインスタンスメソッドを呼び出す場合以外は動きますね。

## メタクラスを使った応用

`super`の主な使い方としては以上で終わりなのですが、さらに細かい挙動について記述するためにメタクラスというものを説明します。

2度目ですが、Pythonはすべてがオブジェクトです。[先に見たように](#多重継承と__mro__の順番)、クラスもオブジェクトです。つまり、何らかのクラスのインスタンスです。\
ではクラスは何クラスのインスタンスなのでしょうか。正解は`type`クラスです。\
おそらく多くのPython利用者は`type`と聞くと型を調べるための関数という印象があるんじゃないでしょうか。そう、これも関数じゃないんです。クラスです。\
そしてこのクラスのインスタンスはクラスなのです。……分かりづらいですね。

図を使ってまとめてみましょう。\
Pythonはすべてがオブジェクトです。\
任意のオブジェクト`x`は型（クラス）`T`を持ちます。ここにおいて、`x`と`T`は「`x`は`T`を実現したもの」という関係を持ちます。この関係を今までの図に組み込んでみましょう。継承関係とは異なる関係なので、区別するために赤い矢印を使います。

![pu7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/e04338ab-fad0-553b-ab00-10c0f81a346f.png)

そしてすべてのクラス`T`もオブジェクトなので型を持ちます。そしてその型こそが型の親分`type`なのです。

![pu8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/effe5245-88c7-f51a-148a-0ba962d2be66.png)

当然、`type`もクラスでありオブジェクトです！したがって、`type`の型は`type`です。

![pu9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/10386687-e548-fdef-6087-a6555c18e7fd.png)

ちなみに無駄にややこしくなりますが、`object`クラスも含めて継承関係も描くとこんな感じです。

![pu10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/dac0b66b-89c5-7b36-f124-7f38caeea30a.png)

`object`クラスは`type`クラスのインスタンスであり、`type`クラスは`object`クラスを継承しています。どちらが先に定義されたんでしょうか。卵と鶏は同時に生まれたんでしょうか。

`type`がクラスであるということは、`type(hogehoge)`と書けば`type`クラスのコンストラクタを呼び出して、新しく`type`クラスのインスタンス（=クラス）を生成できるということでしょうか。\
そうです。その場合、必要な引数は3つ。`__name`, `__bases`, `__dict`です。それぞれクラス名、継承したいクラス、属性を指定するのに使います。すなわち、以下の2つの例は同じクラスを定義しています。

```python
class MyInt(int):
    PI = 3
```

```python
MyInt = type("MyInt", (int,), {"PI": 3})
```

このように実はclass文を使わなくてもクラスは定義できるんです。\
もちろんメソッドも定義可能です。趣旨から外れるので詳しくは述べません。\
このようにインスタンスがクラスであるようなクラスを**メタクラス**と言います。

では次に、`type`を継承して新しいメタクラスを作ってみましょう。\
メタクラスを定義すれば、クラスが定義される瞬間に何らかの処理を挟むことができます。プライベートな属性を標準出力するメタクラスとかどうでしょうか。

```python
class MyType(type):
    def __init__(self, __name: str, __bases: tuple[type], __dict: dict):
        for name, value in __dict.items():
            if name.startswith("_"):
                print(value)
        super().__init__(__name, __bases, __dict)


attributes = {"PI": 3, "__PI": 3.14}
MyClass = MyType("MyClass", (int,), attributes)
# 3.14
```

ちゃんと動きましたね。でもやっぱりいつものclass文が恋しいです。何が定義してあるのか見辛い……。\
そこで、Pythonのclass文にはメタクラスを指定できる機能があります。使ってみましょう。

```python
class MyType(type):
    def __init__(self, __name: str, __bases: tuple[type], __dict: dict):
        for name, value in __dict.items():
            if name.startswith("_"):
                print(value)
        super().__init__(__name, __bases, __dict)


class MyClass(int, metaclass=MyType):
    PI = 3
    __PI = 3.14
# __main__
# MyClass
# 3.14
```

なんか余計なのが付いてきましたけど、とりあえず動きました。この余計なのが何なのかについては正直自分も分からないです。\
class文は`type`で代替できるシンタックスシュガーだと思ってたんですけれど、もしかして違うんでしょうか。

最後にメタクラスを使って`__mro__`を決定するアルゴリズムを変更するコードを紹介します。\
メタクラスに定義したメソッドが、そのメタクラスを使って作られたクラスにどう影響を及ぼすのかについては分からない事が多いので自分としてもまだまだ調べる必要がありますね。

```python
class MyType(type):
    def mro(cls):
        return [cls, object]


class Par:
    pass


class Chi(Par, metaclass=MyType):
    pass


print(Chi.__mro__)  # (<class '__main__.Chi'>, <class 'object'>)
```

## その他細かい状況

まずは親クラスのメソッドを暗黙的に継承している親クラスが存在する場合。例えば以下のような場合です。

```python
class Par:
    @classmethod
    def f(cls):
        print("Par")


class Chi0(Par):
    pass  # 暗黙的！


class Chi1(Par):
    @classmethod
    def f(cls):
        print("Chi1")


class GrandChi(Chi0, Chi1):
    @classmethod
    def f(cls):
        super().f()


GrandChi.f()
```

この場合、考えられるパターンとして以下の2通り存在します。

- `Chi0`が`Par.f`を実行しようとするパターン。
  この場合、`Chi1.f`の処理は飛ばされるので出力としては`"Par"`となる。
- `Chi0`が`super().f`を実行しようとするパターン。
  この場合、`Chi1.f`が呼ばれるので出力としては`"Chi1"`となる。

結論は後者になります。何も書かない場合は親クラスを`super()`で呼び出しているようです。\
まぁそうだと思ってましたが。

次に、メタクラスとそのインスタンスであるクラスが共通の親クラスを持っていて、さらに継承の関係が異なる形で、そのインスタンスから共通の親クラスを`super`で呼び出す場合（？）。\
言葉だと分かりづらいので図で表現するとこういう状況です。

![pu11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/865923/c063c602-046c-6552-e9d0-736bce6992d9.png)

```python
class C(type):
    VAR = "C"


class D(C):
    VAR = "D"


class B(C):
    pass


class A(B):
    pass


a = A("a", (B, D), dict())


print(super(B, a).VAR)
```

この場合も考えられるパターンとして2通り存在します。

- `a`は`A`のインスタンスなのだから`A.__mro__`（この場合`A`, `B`, `C`, `type`, `object`となる）を参照するという考え方で、出力としては`"C"`になる。
- `a`自身が`B`と`D`を継承するクラスなのだから、`a.__mro__`（この場合`a`, `B`, `D`, `C`, `type`, `object`となる）を参照するという考え方で、出力としては`"D"`になる。

正解は後者です。ちなみに最後の1行を`print(super(B, A).VAR)`に変えると出力は`"C"`になります。\
おそらく、`super(A, x)`は`x.__mro__`から`A`の有無を調べて、もしなければ`x.__class__.__mro__`から`A`の有無を調べるというアルゴリズム……だと思います。（よく分からない）

## 最後に

`super`ってどう考えても他のbuilt-inなクラスと比べてそれこそ`object`や`type`にも引けを取らないくらい不思議な挙動をしているのに、どうしてここまで調べても記事の1つもないのだろうかと思い立ち、コードを書き書き色々試しました。\
多分間違っている箇所が幾つかあるので、見つけたら教えて下さい。


[^1]: 昔良かれと思って`Sup`と`Sub`（SuperClassとSubClassより）を使っていたら、「似ていて分かりづらい！」と言われました。たしかに。
[^2]: Pythonに限った話じゃないかもしれませんが、関数に見えてクラスだったみたいなの多いですよね。実は`map`も`property`も関数じゃなくてクラスですしね。`callable`なオブジェクトと関数を区別するものは`function`クラスか否かくらいだと思ってます。すなわち命名と認識からくる扱いの問題で挙動は同じ。ですよね？