オブジェクト指向のオブジェクトと型の関係が難しい

# 循環する型

いろいろな事情があり(どんな事情だ？)、Pythonのオブジェクトシステムについて調べていました。そうすると、結構知らないなぁ。ということがあったので、まとめます。

割と自明な話ですが、pythonでは値の型を調べるのに`type`関数を使います。

```shell-session
>>> a=1
>>> type(a)
<class 'int'>
```

ここで`1`の型が`int型`だと分かりました。では、`type(1)`の型は何でしょうか？

```shell-session
>>> ta = type(a) #type(a)の結果を代入
>>> type(ta)
<class 'type'>
```

ここで、`int型`の型は`type型`であることが分かりました。ここで1つ謎ポイントだと思います。`int型`にも型が存在し、その型は`type型`であるということです。では、`type型`の型は何でしょうか？

```shell-session
>>> tta = type(ta) #type(ta)の結果を代入
>>> type(tta)
<class 'type'>
```

ここで、`type型`の型は`type型`であることが分かりました。多分、ゲシュタルト崩壊してくると思うので、図示してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/f7f6de88-ab5d-e631-9232-b5701b3cb168.png)

`1`に対し、typeを実行することで、型は`int型`だとわかります。
`int型`に対し、typeを実行することで、型は`type型`だとわかります。
`type型`に対し、typeを実行することで、型は`type型`だとわかります。

(ここでなぜ`int`を`int型`と表記していたのかが分かると思いますが、`type型`を`type`と表記してしまうと、`type`関数と混ざって脳みそが崩壊するので、あえて型には型と表記しています。)


# objectとは

では、今度は、基底クラスについて考えてみます。ここでは簡単な例を考えてみます。

```shell-session
>>> class A:
...     def __init__(self):
...             print("A")
...
>>> class B(A):
...     def __init__(self):
...             super().__init__()
...             print("B")
...
>>> B()
A
B
```

　クラスAを定義し、それを継承したクラスとしてBを定義します。当たり前のことを言いますが、Aを継承したクラスBを定義しようとすると、Aを先に定義する必要があります。
 　クラスの基底クラス(継承元)を取得するには`__base__`を見ればわかります。

```shell-session
>>> B.__base__
<class '__main__.A'>
```

では、`1`の基底クラスは何でしょうか？

```shell-session
>>> a=1
>>> a.__base__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__base__'. Did you mean: '__hash__'?
```

1はクラスではないため、基底クラスは取得できません。では、`int型`の基底クラスは？

```shell-session
>>> ta=type(a)
>>> ta.__base__
<class 'object'>
```

`int型`の基底クラスは`object型`だそうです。では、`type型`の基底クラスは？

```shell-session
>>> tta=type(ta)
>>> tta.__base__
<class 'object'>
```

`type型`の基底クラスは`object型`だそうです。では、`object型`の基底クラスは？

```shell-sesion
>>> tab = ta.__base__
>>> tab
<class 'object'>
>>> tab.__base__
>>> tab.__base__ is None
True
```

こから、`object型`の基底クラスは存在しないことが分かりました。
ここまでの事実を図示するとこうなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/917cc227-436a-96f2-18b5-68a7de408424.png)

`int型`の基底クラスは`object型`
`type型`の基底クラスは`object型`
`object型`の基底クラスは存在しない。

# object型の型？

では、最後に`object型`の型について調べてみます。

```shell-session
>>> tab = ta.__base__
>>> type(tab)
<class 'type'>
```

`object型`の型は`type型`です。これらを図示すると以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/b1e34b88-9d40-8989-f65e-a30173d6c235.png)

# objectと型の相互循環

たぶん混乱がすごいと思うので、混乱の元凶部分だけ書き出します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/dd38556c-a3bc-9ce4-d1ac-f9ae29c5a238.png)

事実を整理します。

事実1. インスタンスを生成するためにはクラスの定義が必要である。
事実2. 継承したクラスを定義するためには、基底クラスを事前に定義する必要がある。

例えば、1というint型のインスタンスを生成するためには、int型の定義が必要です(事実1)
int型はobject型を継承したクラスなので、基底クラスであるobject型の定義が必要です(事実2)

ここから、2つのパターンを見てみます。

## パターン1. object型を先に定義する

object型を定義しようとしたとき、object型は基底クラスがないため、そのまま定義できそうです。(事実2はクリア)
しかし、object型自身はtype型のインスタンスとなっています。object型をtypeしたものがtype型であり、isinstanceでも、object型がtype型のインスタンスであることが確認できます。

```shell-session
>>> tta
<class 'type'>
>>> tab
<class 'object'>
>>> isinstance(tab,tta)
True
```

ということは、object型の生成には、type型が必要になります。(事実1)
では、type型を定義することを試みます。type型を定義しようとすると、基底クラスであるobjectが必要なります。しかし、今はobject型を定義しようとして、議論していたため、現在はobject型が未定義であるため、typeが定義できません。

## パターン2. type型を先に定義する

type型を定義しようとすると、基底クラスであるobject型を定義する必要があります。object型はtype型のインスタンスです。そうすると、object型の定義にtype型が必要になります。ここで、循環が発生してしまい、定義できません。

# ChatGPTさんに聞いてみる

どうも解せないので、ChatGPTさんに聞いてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/239ea040-eb62-b4d9-f28f-3f5ef368bc07.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/c7c654a5-8ade-61a6-7553-b108b821505f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/d8639d85-e38d-6f61-8014-0b856647b519.png)

# 型とobject？

実は、ChatGPTさんによくよく聞いてみるとpythonの型がキモイということが分かりました。では、typescriptの型取得はどうなっているでしょうか？

```
$ npx ts-node
> const a=1
undefined
> typeof a
'number'
> typeof (typeof a)
'string'
```

　typescriptでは型の取得は`typeof`を使いますが、この`typeof`で返ってくる値は"string"型になっています。したがって、上記のような型とオブジェクトの循環定義問題のようなものは起きないようです。
　では、Javaの場合は？途中の過程は端折りますが、循環定義問題は起きてます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139643/368bb2c8-18a9-b043-4c69-d4283c7602af.png)


型を表すjava.lang.Classは自分自身の型をjava.lang.Classとしており、その基底クラスをjava.lang.Objectとしています。java.lang.Objectの基底クラスはnullですが、型はjava.lang.Classとなっています。これはpythonのtype型とobject型の構図とほぼ同じです。


# すべてはオブジェクトである。

オブジェクト指向のプログラミング言語でいいがちな話ですが、「すべてがオブジェクトである。」ということです。Pythonにはこういう記述があります。

> Python における オブジェクト (object) とは、データを抽象的に表したものです。Python プログラムにおけるデータは全て、オブジェクトまたはオブジェクト間の関係として表されます。(ある意味では、プログラムコードもまたオブジェクトとして表されます。これはフォン・ノイマン: Von Neumann の "プログラム記憶方式コンピュータ: stored program computer" のモデルに適合します。)

https://docs.python.org/ja/3/reference/datamodel.html#objects-values-and-types

　こう聞くと割と納得ではありますが、型自身すらobjectとなっていると割と面食らうものがあります。ChatGPTのいうことだけ聞いていると、pythonの型ってのは特殊な実装なんだなーぐらいで済んだのですが、どうやら割と純なオブジェクト指向を標榜する言語はどれでも起こってそうです。検証してないですがC#も恐らくそう。
　もともとオブジェクト指向を調べており、型を生成する型とかを考えると、そもそもクラスとインスタンスの境界があいまいになってきて、自分自身が迷宮に入ってきたのでまとめました。実際、クラスとインスタンスの境界が私にはわからなくなりました。
　私自身わかっていないですが、このようなオブジェクト指向の型システムを作ることを考えたとき、objectを定義するとき、その型は未定義にする。type型を定義する。objectの型をtype型と上書き定義する。みたいな、ちょっとした手続きによる工夫とそれができる実装が必要なんだろうなぁ。という感触があります。そこの確信をするには多分言語の実装まで見ないといけないので辛いですが・・・その辺もまたどっかで調べたいです。