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

この記事は
オブジェクトの構成、仕組みについて取り扱います。

この記事は
次の3つの記事から構成されています。



この記事は
オブジェクト指向でプログラミングすることについては取り扱いません。

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



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

>>> # インスタンスオブジェクト
>>> 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


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


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


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



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

>>> # sample という名前空間を
>>> 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
>>> sample.A.b
<function C.f at 0x10e55f1e0>
>>>
>>> # sample.B の名前空間の変数 b
>>> sample.B.b
<function C.f at 0x10e55f1e0>
>>>
>>> # sample.C の名前空間の変数 b
>>> sample.C.b
<function C.f at 0x10e55f1e0>
>>>



メソッドではなく関数なので仮引数 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
>>>



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

>>> class D:
...   print('Hello, world!')
... 
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.3 ドキュメント



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

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

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

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

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

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

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'>
>>> 
>>> isinstance(type, type)
True



全てのクラスオブジェクトは、type クラスからインスタンス化されています。例えば int, bool, double, function も 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(NoneType)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'NoneType' is not defined
>>> # NondeType は直接参照できないので
>>> type(None.__class__)
<class 'type'>
>>> # function
>>> def func():
...   pass
... 
>>> type(func)
<class 'function'>
>>> type(function)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'function' is not defined
>>> # function は直接参照できないので
>>> type(func.__class__)
<class 'type'>



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

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

◯ まとめ

Python では、取り扱うすべてのインスタンスオブジェクトは、クラスオブジェクト、型に属していることがわかりました。このことはユーザが定義したクラスが int, boolean, str などの組込型と同じ種類のクラスオブジェクトという枠組みに含まれていることを意味しています。

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

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

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

全てのクラスオブジェクトが object クラスを継承しています。

ただし、object は、 object クラスを継承していません。

object
 +- int
 |   +- bool
 +- long
 +- float
 +- basestring
 |   +- str
 |   +- unicode
 +- list
 +- tuple
 +- set
 +- dict
 +- BaseException
     +- Exception

とほほのWWW入門 | Python入門 - クラス



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

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

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



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

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




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

>>> object.__bases__
()
>>> 

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

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

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

おわりに

次は、クラス変数とインスタンス変数の違いを追っていきたいと思います。