Python のクラスオブジェクトとインスタンスオブジェクトって何?

疑問: インスタンスオブジェクトって具体的に何のこと?



答え: クラスオブジェクトからインスタンス化されたものを指しています。

# インスタンスオブジェクト
girl_friend = GirlFriend('サーバルちゃん')



あるいは
答え:「変数に代入」できるものを指しています。

例えば、整数 1, 文字列 'Hello, world!', リスト [1,2,3] は変数に代入できます。

つまり、これらはインスタンスオブジェクトとして Python は取り扱っていることを表しています。

# 変数に代入できるものはインスタンスオブジェクト
a = 1
b = 'Hello, world!'
c = [1, 2, 3]




もしかしたら「整数 1インスタンスオブジェクトです。」と言われても、実際にインスタン化している訳ではないので、いまいちピンと来ないかもしれません。

変数に代入する以外にも何かインスタンスオブジェクトであるかどうか確認する方法はないでしょうか?実は、インスタンスオブジェクトを使えば、「属性の参照」ができます。

ところで、インスタンスオブジェクトを使うと何ができるのでしょうか?インスタンスオブジェクトが理解できる唯一の操作は、属性の参照です。
9.3.3. インスタンスオブジェクト



そこで実際に属性の参照ができるかどうかをみて、整数 1インスタンスオブジェクトであるかどうかを確認して見ましょう。

>>> # 整数 1 はインスタンスオブジェクト
a = 1

# だから、属性の参照ができる。
# a の実部
a.real == 1

# a の虚部
a.imag == 0



正確には属性とは違いますが、文字列 str も同様に参照することができます。

b = 'Hello, world!'

b[0] == 'H'
b[1] == 'e'
b[2] == 'l'










f:id:domodomodomo:20140903131706j:plain f:id:domodomodomo:20140914093027j:plain

中国 甘粛省





疑問: クラスオブジェクトって具体的に何のこと?



答え: クラス定義を抜けると生成されるオブジェクトを指しています。

クラス定義から普通に (定義の終端に到達して) 抜けると、
クラスオブジェクト (class object) が生成されます。
9.3.1. クラス定義の構文


# クラスオブジェクトの定義
class GirlFriend(object):
  max_intimacy = 100
  min_intimacy = 0

  def __init__(self, new_name=''):
    self.name = new_name                                               
    self.intimacy = 0 


# クラスオブジェクト
GirlFriend  # <class '__main__.GirlFriend'>



クラスオブジェクトは、基本的にはクラス定義で作成された名前空間の内容をくるむラッパ (wrapper) だそうです。

クラスオブジェクトは、基本的にはクラス定義で作成された名前空間の内容をくるむラッパ (wrapper) です。
9.3.1. クラス定義の構文



いまいちピンときませんが、「クラスは、名前空間の内容をくるむラッパ (wrapper) 」というのは、「クラスはモジュールを小分けにしたみたいなもの」っことですかね。そんなに複雑なものでは無さそうです。次のような sample.py というスクリプトを作って動作を確認してみたいと思います。

# sample.py


# -- A -- ここから分割
class A(object):
    a = 10
    def b(self):
        return -10
# ------- ここまで分割


# -- B -- ここから分割
class B(object):
    a = 20
    def b(self):
        return -20
# ------- ここまで分割


# -- C -- ここから分割
class C(object):
    a = 30
    def b(self):
        return -30
# ------- ここまで分割



小分けされた sample.py の名前空間を見てみましょう。

# sample という名前空間を import
import sample


# class で、さらに小分けにしている。
# sample.A の名前空間の変数 a
sample.A.a == 10

# sample.B の名前空間の変数 a
sample.B.a == 20

# sample.C の名前空間の変数 a
sample.C.a == 30



変数 b には関数が代入されています。sample.A.b を参照すると function, 関数 という文字が見えます。

# sample という名前空間を import
import sample

# sample.A の名前空間の変数 b
sample.A.b
hex(id(A.b))

# sample.B の名前空間の変数 b
B.b
hex(id(B.b))

# sample.C の名前空間の変数 b
sample.C.b
hex(id(C.b))
>>> # sample という名前空間を import
>>> import sample
>>>
>>> # sample.A の名前空間の変数 b
>>> sample.A.b
<function A.b at 0x10b8ec510>
>>> hex(id(A.b))
'0x10b8ec510'
>>>
>>> # sample.B の名前空間の変数 b
>>> B.b
<function B.b at 0x10b9b91e0>
>>> hex(id(B.b))
'0x10b9b91e0'
>>>
>>> # sample.C の名前空間の変数 b
>>> sample.C.b
<function C.f at 0x10e55f1e0>
>>> hex(id(C.b))
'0x0x10e55f1e0'
>>>



メソッドではなく関数なので仮引数 self に実引数を与えないといけない。今回は実引数として None を与えます。

# sample という名前空間を import
import sample

# メソッドではなく関数なので仮引数 self に
# 実引数を与えないといけない。
sample.A.b()

# 実引数として None を与えます。
sample.A.b(None)
sample.B.b(None)
sample.C.b(None)
>>> # sample という名前空間を import
>>> import sample
>>>
>>>
>>> # メソッドではなく関数なので仮引数 self に
>>> # 実引数を与えないといけない。
>>> sample.A.b()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: b() missing 1 required positional argument: 'self'
>>>
>>>
>>>
>>> # 実引数として None を与えます。
>>> sample.A.b(None)
-10
>>>
>>> sample.B.b(None)
-20
>>>
>>> sample.C.b(None)
-30
>>>

仮引数(parameter) は関数定義に表れる名前で定義されるのに対し、
実引数 (argument) は関数を呼び出すときに実際に渡す値のことです。

実引数と仮引数の違いは何ですか?



実際、クラスはモジュールを小分割したラッパです。なので、モジュールと同じように(モジュールを import するとモジュールが実行されるように)、クラスも定義が完了すると、ひっそりとその中身が実行されます。

>>> # 実行されて Hello, world! が出力されました。
>>> class D(object):
...   print('Hello, world!')
... 
Hello, world!
>>>



こうして見るとモジュールとクラスって何だか似ていますね。もし、クラスとモジュールの違いは何?と聞かれたら、クラスは "継承" と "インスタンス化" できるけど、モジュールは "継承" も "インスタンス化" できないというところでしょうか。

上記のようにクラス定義文の中で定義された変数や関数をクラス変数と呼びます。

実はクラスオブジェクトも、インスタンスオブジェクトです。

繰り返しになりますが、インスタンスオブジェクトとは何でしょうか?「変数に代入」できるものを指しています。

例えば、整数1,文字列'Hello, world!',リスト [1, 2, 3] は変数に代入できます。そしてこれらはインスタンスオブジェクトになります。

>>> # 変数に代入できるものはインスタンスオブジェクト
>>> a = 1
>>> b = 'Welcome to ようこそジャパリパーク! 今日もドッタンバッタン大騒ぎ うー!がぉー!高らかに笑い笑えば フレンズ喧嘩して すっちゃかめっちゃかしても仲良し けものは居ても のけものは居ない本当の愛はここにあるほら 君も手をつないで大冒険'
>>> c = [1, 2, 3]
>>>



実はクラスの定義がなされた段階で、既にクラスオブジェクトを変数に代入すると言う操作が行われています。

>>> # 変数 C にクラスオブジェクトを代入
>>> class C: pass
... 
>>>
>>> # 変数 C を参照
>>> C
<class '__main__.C'>
>>> 



Python ではイコール '=' を使って代入するときだけではなく、次の5つの場合に "変数への代入"、正確に言えば "変数への束縛" が行われています。

以下の構造で、名前が束縛されます:
 関数の仮引数 (formal parameter) 指定、
 import 文、クラスや関数の定義
 代入が行われるときの代入対象の識別子
 for ループのヘッダ、
 with 文や except 節の as の後ろ。
4. 実行モデル — Python 3.6.5 ドキュメント



また、次の記事で確認しますがクラスオブジェクトも、属性を参照することができますし、更に言えば属性に代入することもできます。

以上のことから、クラスオブジェクトがインスタンスオブジェクトだと、どうやら言えそうです。実際に本当にそうなのか確認していきます。
 

疑問: クラスオブジェクトは、どのクラスオブジェクトのインスタンスなの?

答え: type クラスオブジェクト

一体どのクラスオブジェクトをインスタンス化したものが GirlFriend クラスオブジェクト(GirlFriend インスタンスオブジェクト)になたったのでしょうか?

>>> # GirlFriend クラスは type クラスの
>>> # インスタンスオブジェクトであることがわかります。
>>> type(GirlFriend)
<class 'type'>
>>>

class type(object)
引数が1つだけの場合、object の型を返します。返り値は型オブジェクトで、一般に object.__class__ によって返されるのと同じオブジェクトです。
オブジェクトの型の判定には、 isinstance() 組み込み関数を使うことが推奨されます。これはサブクラスを考慮するからです。



GirlFriend クラスオブジェクト は type クラスオブジェクトからインスタンス化されたインスタンスオブジェクトだということがわかりました。

このことからクラスオブジェクトを生成するために
「1. クラス定義文を書くこと」と
「2. type クラスオブジェクトからインスタンス化すること」が、
同じであることがわかりました。

# 1. クラス定義文を書くこと
class GirlFriend(object):
  max_intimacy = 100                                                               
  min_intimacy = 0 

# 2. type クラスオブジェクトからインスタンス化すること
# GirlFriend インスタンスオブジェクト(クラスオブジェクト)
GirlFriend = type('GirlFriend', (object,),
    dict(max_intimacy=100, min_intimacy=0))

class type(name, bases, dict)
引数が 3 つの場合、新しい型オブジェクトを返します。本質的には class 文の動的な形式です。



このようにして type 関数は引数の個数によって動作が異なるので留意しておいてください。
Python の type 関数について思ったこと

疑問: じゃあ type クラスオブジェクトは、どのクラスオブジェクトのインスタンスなの?

答え: type 自身のインスタンスです。

>>> type(type)
<class 'type'>
>>> 



全てのクラスオブジェクトは、type クラスからインスタンス化されています。例えば int, bool, double, function も type からインスタンス化されたインスタンスであり、クラスでもあります。

>>> # object
>>> obj = object()
>>>
>>> type(obj)
<class 'object'>
>>> type(object)
<class 'type'>
>>> 
>>>  # int
>>> type(0)
<class 'int'>
>>> type(int)
<class 'type'>
>>> 
>>> # bool
>>> type(True)
<class 'bool'>
>>> type(bool)
<class 'type'>
>>> 
>>> # None
>>> type(None)
<class 'NoneType'>
>>> type(type(None))  # NondeType とは直接参照できないので
<class 'type'>
>>> 
>>> # function
>>> def func():
...   pass
... 
>>> type(func)
<class 'function'>
>>> type(type(func))  # function とは直接参照できないので
<class 'type'>
>>> 



そして繰り返しになりますが type クラスオブジェクトは、type クラスオブジェクト自身からインスタンス化されたクラスオブジェクトです。

>>> # type
>>> type(type)
<class 'type'>
>>> 



Python では、取り扱うすべてのインスタンスオブジェクトは、クラスオブジェクト、型を持っていることが、わかりました。このことはユーザが定義したクラスオブジェクトが int, bool, str などの最初から定義されているクラスオブジェクト(組み込み型, builtin type)と同じクラスオブジェクトという枠組みに含まれていることを意味しています。

クラスオブジェクト自体も type というクラスオブジェクトをインスタンス化したものであり、さらに type も自身 type をインスタンス化したものであります。

すべてのオブジェクトは、同一性 (identity)、型、値をもっています。
3. データモデル — Python 3.6.3 ドキュメント



これらを図にまとめると以下のようになります。
f:id:domodomodomo:20180414164656j:plain


疑問: なるほど、全てのクラスオブジェクトは type クラスを継承しているのかな?

答え: 違います。

全てのクラスオブジェクトは object クラスを継承しています。
ただし、object は、 object クラスを継承していません。
f:id:domodomodomo:20180414164707j:plain


ユーザ定義クラスも object クラスを継承しなければなりません。

>>> class A(object):
...   pass
... 
>>> 
>>> A.__bases__
(<class 'object'>,)
>>> 

__bases__ は基底クラスからなるタプルで、
基底クラスのリストに表れる順序で並んでいます
3. データモデル — Python 3.6.5 ドキュメント



object クラスは省略することもできます。
object クラスのみを継承する場合であれば。

>>> class B:
...   pass
... 
>>> 
>>> B.__bases__
(<class 'object'>,)
>>> 



object クラスは何物も継承していていません。

>>> object.__bases__
()
>>> 

◯ 次の2つは区別しておいてください。

全てのクラスオブジェクトは、

type クラスオブジェクト から インスタンス化されます。
object クラスオブジェクト 継承しています。

おわりに

実はこの記事は、Python のクラスとは何かについて書いている記事の一部になります。3連載の1回目になります。







インスタンス化する」とは何でしょうか?「type は自分自身をインスタンス化しています。」と書きましたが、これはどういうことでしょうか?次は、クラス変数とインスタンス変数の違いを通して、クラスオブジェクトとインスタンスオブジェクトの関係を追っていきたいと思います。