Last Updated: 2/6/2024, 5:44:57 AM

クラスオブジェクトと
インスタンスオブジェクトって
なに?







 クラスオブジェクト  は、

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



 インスタンスオブジェクト  は、

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





# 対話モード >>> に
# コピペで実行できます。
class GirlFriend:
    def __init__(self, name):
        self.name = name                                               

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

GirlFriend   # <- これはクラスオブジェクトが代入されている。
girl_friend  # <- これはインスタンスオブジェクトが代入されている。
>>> GirlFriend   # <- これはクラスオブジェクトが代入されている。
<class '__main__.GirlFriend'>
>>> girl_friend  # <- これはインスタンスオブジェクトが代入されている。
<__main__.GirlFriend object at 0x108ec4748>
>>> 

いまいちピンと来ないので、もう少し掘り下げて見たいと思います。 Python でオブジェクトと言えば、関数と値をひとつにまとめたものです。 この文章では Python のオブジェクトの仕組み、そのものについて取り扱っています。




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

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

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

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

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

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

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

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

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

ところで、インスタンスオブジェクトを使うと何ができるのでしょうか? インスタンスオブジェクトが理解できる唯一の操作は、属性の参照です。
9.3.3. インスタンスオブジェクト - Python チュートリアル (opens new window)

そこで実際に属性の参照ができるかどうかをみて、 整数 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'
























この先はクラスオブジェクトについて、ご説明いたします。

中国 甘粛省

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

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

# クラスオブジェクトの定義
class E:
    pass

# クラスオブジェクト
E

>>> E
<class '__main__.E'>
>>> 

クラス定義から普通に (定義の終端に到達して) 抜けると、 クラスオブジェクト (class object) が生成されます。
9.3.1. クラス定義の構文 - Python チュートリアル (opens new window)

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

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

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

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

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

# 変数 E にクラスオブジェクトを代入
class E:
    pass

# 変数 E を参照
E

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

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

  1. 代入文
  2. クラス定義文
  3. 関数定義文
  4. 関数定義文の仮引数
  5. import 文
  6. for 文の in
  7. try 文の except
  8. wtih 文の as

束縛については、こちらの記事でご説明させていただきました。 代入から初めて束縛へと理解を持ち上げていきます。



# ◯ 名前空間

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

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

 名前空間  とは、平たく言えば複数のオブジェクトを1つにまとめたものです。 名前空間については、この先の以下の記事でご説明させていただければと思います。

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

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

# 対話モード >>> に
# コピペして実行すると
# Hello, world! が出力されます。
class D:
    print('Hello, world!')

...
Hello, world!
>>>

# ◯ クラスとモジュールの違いってなに?

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

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

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

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

# ◯ 動作確認

次のような sample.py というスクリプトを作って動作を確認してみたいと思います。 ここは特に適当に読み流していただければと思います。

#
# 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
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) (opens new window) は関数定義に表れる名前で定義されるのに対し、 実引数 (argument) (opens new window) は関数を呼び出すときに実際に渡す値のことです。
実引数と仮引数の違いは何ですか? - Python よくある質問 (opens new window)





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

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

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

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

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

class GirlFriend:
    pass

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

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

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

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

# 1. クラス定義文を書くこと
class GirlFriend:
    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) (opens new window)
引数が 3 つの場合、新しい型オブジェクトを返します。本質的には class 文の動的な形式です。

このようにして type は引数の個数によって、 動作が異なります。 すこしややこしいですね。

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

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

type(type)

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

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

# ◯ object クラス

obj = object()
type(obj)
type(type(obj))

>>> type(obj)
<class 'object'>
>>> type(type(obj))
<class 'type'>  <--- ここに注目
>>> 

# ◯ bool クラス

type(True)
type(type(bool))

>>> type(True)
<class 'bool'>
>>> type(type(bool))
<class 'type'>  <--- ここに注目
>>> 

# ◯ int クラス

type(0)
type(type(0))

>>> type(0)
<class 'int'>
>>> type(type(0))
<class 'type'>  <--- ここに注目
>>> 

# ◯ None クラス

type(None)
type(type(None))

>>> type(None)
<class 'NoneType'>
>>> type(type(None))
<class 'type'>  <--- ここに注目
>>> 

# ◯ function クラス

def function():
    pass

type(function)
type(type(function))
>>> type(function)
<class 'function'>
>>> type(type(function))
<class 'type'>  <--- ここに注目
>>> 

# ◯ type クラス

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

type(type)

>>> type(type)
<class 'type'>  <--- ここに注目
>>> 

# ◯ まとめ

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

このように最初から定義されているクラスを  組み込み型, builtin type  と呼びます。

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

すべてのオブジェクトは、同一性 (identity)、型、値をもっています。
3.1. オブジェクト、値、および型 - Python 言語リファレンス (opens new window)

これらを図にまとめると以下のようになります。

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

答え: ...

全てのクラスオブジェクトは object クラスを継承しています。 ただし、object は object クラスを継承していません。

ユーザ定義クラスも object クラスを継承しています。

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

__bases__ は基底クラスからなるタプルで、 基底クラスのリストに表れる順序で並んでいます
3.1. オブジェクト、値、および型 - Python 言語リファレンス (opens new window)

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

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

# ◯ object クラス

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

>>> object.__bases__
()
>>> 

# ◯ bool クラス

bool は int を継承しています。 ほかのクラスは継承していないのに、ちょっと、仲間ハズレですね。もう少し注目してみましょう。

PEP 285 -- bool 型の追加 (opens new window)
PEP 285 -- Adding a bool type

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().

# 動作

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

# 結果は全部 True
0 == False
1 == True
2 != False and 2 != True
3 != False and 3 != True
4 != False and 4 != True
5 != False and 5 != True

>>> 0 == False
True
>>> 1 == True
True
>>> 2 != False and 2 != True
True
>>> 3 != False and 3 != True
True
>>> 4 != False and 4 != True
True
>>> 5 != False and 5 != True
True
>>> 

# なんで int を継承させたの?

答え: 実装が簡単だから

PEP 285 -- bool 型の追加 (opens new window)
PEP 285 -- Adding a bool type

6. bool は int を継承するべきか?
6. Should bool inherit from int?

理想から言えば bool は mixed-mode arithmetic を実行する方法を知っている (訳注: True + 0 = 1 が計算できるような)、別の整数型として実装する方がよいでしょう。
In an ideal world, bool might be better implemented as a separate integer type that knows how to perform mixed-mode arithmetic.

しかしながら int から継承して bool を定義してしまえば、mixed-mode arithmetic の実装を非常に簡単にできます (PyInt_Check() の実引数に int ではなく bool を与えて呼び出しても、PyInt_Check() を呼び出す全ての C 言語コードは、ある程度は問題なく動作するでしょう。 -- PyInt_Check() は int のサブクラスに対しても true を返して動作してくれます) (訳注: 型を判定するだけなら問題なく動くし)
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).

また、私は代替可能性という観点からも、正しいと信じています。 int を実引数に受けるコードが True, False を受け取っても 0, 1 を受けたとったと同じように振る舞います。 (訳注: もちろん実際に演算するときも問題ない.. ということかな.. ちなみに Python 2 では PyInt_Check だったものが Python 3 では PyLong_Check に統合されています。)
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.

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

If operands in an expression contains both INTEGER and REAL constants or variables, this is a mixed mode arithmetic expression.
Mixed Mode Arithmetic Expressions (opens new window)

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

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

bool 型の世界で計算しているのに、いきなり int 型がでてくるからです。 例えば、日本語で話していたのに、いきなりタイ語が出てくるとビビります。




まず第1に bool 同士の計算をしたら bool を返してほしいです。 int が返ってくるのは、いくらか違和感があります。

>>> # bool + book すると int に変身しちゃうのか...
... True + True
2
>>>

bool は int を継承しているので四則演算子を使えたりします。 いっそのこと NotImplementedError (opens new window) を返してくれた方が、一貫性があるように感じます。

# 対話モード >>> に
# コピペで実行できます。
class C:
    # __add__ メソッドを定義すると
    # + 演算を定義できます。
    # こういうのを難しい言葉で
    # 演算子オーバーロード, operator overload と言います。
    def __add__(self, other):
        raise NotImplementedError

C() + C()

>>> C() + C()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __add__
NotImplementedError
>>> 

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

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

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

True + True = 2 になるような機能は、条件を満たすものをカウントするときには使えるかもしれません。 もし使う機会が頻繁にあるなら機能としてあった方がいいと思いますが、 こんなことをする機会はそこまで頻繁には無いので必要性は薄いかな.. と。

# あればいいけど
def count_diff1(s1, s2):
    return sum(c1 != c2 for c1, c2 in zip(s1, s2))

# なくても構わない
def count_diff2(s1, s2):
    return sum(1 for c1, c2 in zip(s1, s2) if c1 != c2)

assert count_diff1('hello', 'nihao') == 4
assert count_diff2('hello', 'hello') == 0

bool クラスは、オブジェクトが存在すれば True を返すように実装されています。

class C:
    pass

bool(C())
# True

bool(None)
# False

bool([0])
# True

bool([])
# False

そのことについては、 このあと if 文とは何かという記事の中で説明させていただきます。

とは言え、何事もバランスによりけりかなと...

# ◯ まとめ

次の2つのことを区別しておいてください。

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

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

# おわりに

「インスタンスオブジェクト」と「クラスオブジェクト」の存在をご紹介させていただきました。










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

「インスタンス化する」とは何でしょうか?「type は自分自身をインスタンス化しています。」と書きましたが、これはどういうことでしょうか?

次は「属性」に着目して「クラス変数」と「インスタンス変数」の違いを通して、 クラスオブジェクトとインスタンスオブジェクトの関係を追っていきたいと思います。