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





クラスオブジェクトは、
クラス定義を抜けると生成されるオブジェクトです。

インスタンスオブジェクトは、
クラスオブジェクトからインスタンス化されるオブジェクトです。

class GirlFriend(object):
    def __init__(self, name):
    self.name = new_name                                               

GirlFriend  # <- これはクラスオブジェクトが代入されている。


girl_friend = GirlFriend('サーバルちゃん')

girl_friend  # <- これはインスタンスオブジェクトが代入されている。




f:id:domodomodomo:20180624212528p:plain



いまいちピンと来ないので、もう少し掘り下げて見たいと思います。この文章は 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!
>>>



クラスには、モジュールに2つの機能が付け加えられています。1つ目は、クラスは、インスタンス化してオブジェクトを生成できること。2つ目は、クラスは、他のクラスを継承できること。

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

クラスが、モジュールを分割しただけでシンプルに表現できているなんて、すごい設計ですよね。ワイだったらもっと複雑怪奇なものしか思いつかない.. これを見た時は、感動しました。

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

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

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

例えば、整数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 クラスを継承しているのかな?

答え: ...




f:id:domodomodomo:20180625161822j:plain





全てのクラスオブジェクトは 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 クラスは継承していていません。

>>> object.__bases__
()
>>> 

◯ bool クラス

bool は int を継承しています。

bool 型は単純に int 型の派生型となります。repr() や str() で扱われる場合を除いて、ほとんどの場合 False と True の値は 0, 1 と同じように動作します(例えば、False==0 と True==1 は、それぞれ真になります)。
The bool type would be a straightforward subtype (in C) of the int type, and the values False and True would behave like 0 and 1 in most respects (for example, False==0 and True==1 would be true) except repr() and str().

PEP 285 -- Adding a bool type | Python.org
動作

おそらくこれは 論理演算 が偽を表す場合は 0 を、真を表す場合は 1 に沿ってこのような実装をしていると思われます。

>>> 0 == False
True
>>> 1 == True
True
>>> 2 in (False, True)
False
>>> 3 in (False, True)
False
>>> 4 in (False, True)
False
>>>
何で int を継承させたの?

答え: 実装が簡単だから

6. bool は int を継承するべきか?
理想から言えば bool は mixed-mode arithmetic を実行する方法を知っている(True + 0 = 1 が計算できるような)、別の整数型として実装する方がよいでしょう。

しかしながら int から継承して bool を定義してしまえば、mixed-mode arithmetic の実装を非常に簡単にできます(PyInt_Check() の実引数に int ではなく bool を与えて呼び出しても、PyInt_Check() を呼び出す全ての C コードは、ある程度は問題なく動作するでしょう。-- PyInt_Check() は int のサブクラスに対しても true を返して動作してくれます)(型を判定するだけなら問題なく動くし)。また、私は代替可能性という観点からも、正しいと信じています。int を実引数に受けるコードが True, False を受け取っても 0, 1 を受けたとったと同じように振る舞います。(もちろん実際に演算するときも問題ない.. ということかな.. ちなみに Python 2 では PyInt_Check だったものが Python 3 では PyLong_Check に統合されています。)

bool を引数に受け取るコードが int を与えられると動作しないかもしれません。例えば 3 と 4 は真理値であると考えると、それぞれ True として見なされますが、3 & 4 は 0 になります。

6. Should bool inherit from int?
In an ideal world, bool might be better implemented as a separate integer type that knows how to perform mixed-mode arithmetic.

However, inheriting bool from int eases the implementation enormously (in part since all C code that calls PyInt_Check() will continue to work -- this returns true for subclasses of int). Also, I believe this is right in terms of substitutability: code that requires an int can be fed a bool and it will behave the same as 0 or 1.

Code that requires a bool may not work when it is given an int; for example, 3 & 4 is 0, but both 3 and 4 are true when considered as truth values.

PEP 285 -- Adding a bool type | Python.org

If operands in an expression contains both INTEGER and REAL constants or variables, this is a mixed mode arithmetic expression.

Mixed Mode Arithmetic Expressions

なんで "理想から言えば ... 別の整数型として実装する方がよい" のか?

答え: ブール代数から外れてしまうから

まず第1に bool 同士の計算をしたら bool を返してほしいです。int が返ってくるのは、いくらか違和感があります。bool は int を継承しているので四則演算子を使えたりします。いっそのこと二項演算が定義されていないことを示す NotImplemented を返してくれた方が、一貫性があるように感じます。

>>> # 2 が返されます。
>>> True + True
2
>>> 



また第2に bool 同士の計算では 0, 1 (Ture, False) の2種類だけ取り扱って欲しいです。2 とか 3 とかいう数字が返ってくるのは、いくらか違和感があります。

ブール代数とは
イギリスの数学者 ブール (George Boole) が1854年の著書「思考の法則に関する研究」で 提唱した記号論理学を ブール代数 (Boolean algebra) と呼び, 1 または 0 の2値のみをもつ変数を用いる論理である. 2値代数,2値論理数学,ディジタル代数,スイッチング代数などとも呼ばれる

ブール代数と論理演算 (1) Boolean algebra & logical operation



このようにして int を継承してしまうとブール代数という系から外れてしまうので "理想から言えば", "In an ideal world" 確かに実装は分けておいた方が良さそうな気がします。

__bool__ メソッド

if 文などで条件分岐をする際、論理演算を実行しています。内部で __bool__ メソッドが呼ばれています。

class Cls(object):
    def __bool__(self):
        print('Hello, world!')
        return True

obj = Cls()
if obj:
    pass
>>> # if 文が実行された際に
>>> # Hello, world! が出力されています。
>>> obj = Cls()
>>> if obj:
...     pass
... 
Hello, world!
>>> 
def main():
    # assert 文は False の時に例外を投げます。
    assert A()
    assert not B()
    assert C()


class A(object):
    pass

class B(A):
    def __len__(self):
        return 0

class C(B):
    def __bool__(self):
        return True


if __name__ == '__main__':
    main()

object.__bool__
真理値テストや組み込み演算 bool() を実装するために呼び出されます; False または True を返さなければなりません。このメソッドが定義されていないとき、 __len__() が定義されていれば呼び出され、その結果が非 0 であれば真とみなされます。クラスが __len__() も __bool__() も定義していないければ、そのクラスのインスタンスはすべて真とみなされます。



例えば int も __bool__ メソッドを持っており int.__bool__ は 真偽判定は 0 であれば False, それ以外は True となります。

>>> bool(0)
False
>>> bool(1)
True
>>> bool(2)
True
>>> bool(3)
True
>>> 



bool.__bool__ は、int から継承した int.__bool__ を参照するようになっています。

>>> int.__bool__ is bool.__bool__
True
>>>
>>> help(int.__bool__)
Help on wrapper_descriptor:

__bool__(self, /)
    self != 0
(END)
>>>



bool は int から継承して __bool__ を借用しているので bool.__bool__ の説明は、int.__bool__ と同じですね。

>>> help(bool.__bool__)
Help on wrapper_descriptor:

__bool__(self, /)
    self != 0
(END)
>>>



ちなみに __bool__(self, /) の '/' は、なんでしょうか?位置引数であることを明示するために書かれてる気配があります。ただ PEP 457 の status は draft で正式採用はされていないので実際に使おうとすると SyntaxError になります。

>>> def __bool__(sef, /):
  File "<stdin>", line 1
    def __bool__(sef, /):
                      ^
SyntaxError: invalid syntax
>>> 

pep 457 - Syntax For Positional-Only Parameters



◯ まとめ

次の2つは区別しておいてください。全てのクラスオブジェクトは、

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

おわりに

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







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