Python のクラス変数とインスタンス変数って何?

疑問: インスタンス変数とクラス変数って何?

答え:

インスタンス変数はインスタンスオブジェクトの属性です。
クラス変数はクラスオブジェクトの属性です。



f:id:domodomodomo:20170406193028j:plain

図. クラスオブジェクトとインスタンスオブジェクトの関係の考え方




f:id:domodomodomo:20180113154538j:plain 何を言ってるか、さっぱりだと思うので、ゆっくり見ていきましょう。




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

実は、この記事は以下の記事からの続きになります。
インスタンスオブジェクトとクラスオブジェクトって
何かをざっくりと押さえておいてください。


疑問: インスタンス変数とクラス変数の違いは何?

答え: 箇条書きで列挙すると、だいたい以下の通りです。
まず、表面的な動作の違いを復習しておきたいと思います。

違い クラス変数 インスタンス変数
1. 定義場所 クラス定義文の直下で
代入された変数
関数定義文の直下で
代入された self の属性
2. 使い分け 全てのインスタンス
共有する値
そのインスタンス
だけで使う値
3. クラス
オブジェクトから参照
できる できない
4. インスタンス
オブジェクトから参照
できる できる
5. 変更すると 全ての
インスタンスの属性
が変更される。
その
インスタンスの属性
だけが変更される。


違い 1. 定義場所
クラス変数とインスタンス変数は、どこで定義するの?


◯ クラス変数
クラス定義文の直下で変数に代入

class GirlFriend(object):
  # クラス変数
  max_intimacy = 100  


インスタンス変数
関数定義文の直下で代入された self の属性に代入

class GirlFriend(object):
  def __init__(self, new_name=''):
    # インスタンス変数
    self.name = new_name         


違い 2. 使い分け
クラス変数とインスタンス変数は、どう使い分ければいいの?

答え:
クラスに属するインスタンス全てで共有する変数は、
クラス変数に代入します。


例えば、恋愛ゲームについて考えましょう。彼女 GirlFriend には名前 name, 彼氏への親密度 intimacy があり最小値 0, 最大値 100 とします。これでクラス設計をするなら

インスタンスオブジェクトで共有しない変数、名前 name と親密性 intimacy は、__init__ 関数の中で定義してインスタンス変数に代入します。

class GirlFriend():
  # インスタンスオブジェクト間で共有される変数
  max_intimacy = 100                                                               
  min_intimacy = 0                                                                 

  # インスタンスオブジェクト間で共有しない変数
  def __init__(self, new_name=''):                                              
    self.name =  new_name
    self.intimacy = 0


◯ ダメな設計例
これは間違っています。何故なら、名前 name と親密性 intimacy がすべての GirlFriend インスタンスオブジェクトで共有されるクラス変数として定義されているからです。

class GirlFriend():                                                                
  max_intimacy = 100
  min_intimacy = 0
  intimacy = 0
  name = ''


違い 3. 参照
クラスブジェクトから参照できるか、できないか

引き続き、このような GirlFriend クラスについて考えます。

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


◯ クラスオブジェクトからクラス変数を参照してみる。
クラスオブジェクトは、クラス定義の内部で属性に代入されたインスタンスオブジェクトを、外部から参照できる仕様になっています。

>>> # 参照できる
>>> GirlFriend.max_intimacy
100
>>>
>>> GirlFriend._min_intimacy
0
>>>


◯ クラスオブジェクトからインスタンス変数を参照してみる。

>>> # 参照できない
>>> GirlFriend.name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'GirlFriend' has no attribute 'name'
>>>
>>> GirlFriend.intimacy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'GirlFriend' has no attribute 'intimacy'
>>>


違い 4. 参照
インスタンスブジェクトから参照できるか、できないか

>>> # 変数 girl_friend に代入されたものが
>>> # インスタンスオブジェクト
>>> girl_friend = GirlFriend('サーバルちゃん')
>>>


インスタンスオブジェクトからクラス変数を参照してみる。
>>> # クラスオブジェクトの属性も参照できる
>>> girl_friend.max_intimacy
100
>>> girl_friend.min_intimacy
0


インスタンスオブジェクトからインスタンス変数を参照してみる。
>>> # インスタンスオブジェクトの属性
>>> girl_friend.name
サーバルちゃん
>>>
>>> girl_friend.intimacy
0
>>>

違い 4. 変更
クラス変数とインスタンス変数を変更してみる。

◯ クラス変数を変更する。

クラス変数を変更すると、そこから生成された全てのインスタンス変数も変更される。

>>> girl_friend1 = GirlFriend()
>>> girl_friend2 = GirlFriend()
>>> 
>>> girl_friend1.max_intimacy
100
>>> girl_friend2.max_intimacy
100
>>> 
>>> # クラスオブジェクトの属性を変更すると
>>> GirlFriend.max_intimacy = 50
>>>
>>> # そこから生成された全ての
>>> # インスタンスオブジェクトの属性も変更される。
>>> girl_friend1.max_intimacy
50
>>> girl_friend2.max_intimacy
50

 

山口県萩市




ここまでは、表面的な動作の違いを復習しました。
ここからは、もう少し突っ込んだ動作原理を見ていきたいと思います。

疑問: なぜクラスオブジェクトの属性を変更すると、
そこから生成された全てのインスタンスオブジェクトの属性も変更されるの?

答え:
インスタンスオブジェクトの属性
→ クラスオブジェクトの属性
→ クラスオブジェクトの親クラスオブジェクトの属性
→ クラスオブジェクトの親クラスオブジェクトの親クラスオブジェクトの...
といった順番に属性にアクセスしてるから。



ちょっと復習...
>>> girl_friend1 = GirlFriend()
>>> girl_friend2 = GirlFriend()
>>> 
>>> girl_friend1.max_intimacy
100
>>> girl_friend2.max_intimacy
100
>>> 
>>> # クラスオブジェクトの属性を変更すると
>>> GirlFriend.max_intimacy = 50
>>>
>>> # そこから生成された全ての
>>> # インスタンスオブジェクトの属性も変更される。
>>> girl_friend1.max_intimacy
50
>>> girl_friend2.max_intimacy
50

 

ここから本題...
>>> # step1. girl_friend1 の属性 max_intimacy に
>>> #          インスタンスオブジェクトは代入されてるかな?
>>> # -> されてない...
>>>
>>> # step2. GirlFriend の属性 max_intimacy に
>>> #          インスタンスオブジェクトは代入されてるかな?
>>> # -> 50 があった!
>>> girl_friend1.max_intimacy
50
>>>
>>>
>>> #
>>> # インスタンスオブジェクトの属性を変えても...
>>> #
>>> girl_friend1.max_intimacy = 100
>>> girl_friend1.max_intimacy
100
>>>
>>> # クラスオブジェクトの属性に影響はない。
>>> GirlFriend.max_intimacy
50
>>> girl_friend2.max_intimacy
50
>>>
>>>
>>> # 
>>> # インスタンスオブジェクトの属性を消せば...
>>> #
>>> girl_friend1.max_intimacy
100
>>> del girl_friend1.max_intimacy
>>> 
>>> # またクラスオブジェクトの属性が参照される。
>>> girl_friend1.max_intimacy
50

◯ 属性アクセスのだいたいの動作

だいたいこんな順番で属性にアクセスしてます。
a.x
→ a.__class__.x
→ a.__class__.__bases__[0].x
→ a.__class__.__bases__[0].__bases__[0].x
...

これを図にするとこんな感じになります。 f:id:domodomodomo:20170406193028j:plain

Step1: マニュアルの文章

まず、マニュアルの文章を読んで見ましょう。

属性アクセスのデフォルトの振る舞いは、オブジェクトの辞書の属性の取得、設定、削除です。例えば a.x は、まず a.__dict__['x']、それから type(a).__dict__['x']、さらに type(a) のメタクラスを除く基底クラスへと続くというように探索が連鎖します。
デスクリプタ HowTo ガイド — Python 3.6.3 ドキュメント

これを文章にすると、このような感じ。これを関数呼び出し日本語の表記を除いて obj.attr のような属性参照だけに書き換えて、上の図を導出します。
a.x
→ a.__dict__['x']
→ type(a).__dict__['x']
→ type(a) のメタクラスを除く基底クラスの __dict__['x']
→ type(a) のメタクラスを除く基底クラスの メタクラスを除く基底クラス..
...

Step2: class.__bases__

クラスオブジェクトの基底クラスのタプルです。
class.__bases__


「type(a) のメタクラスを除く基底クラス」とは
オブジェクト a を生成したクラスの親クラスのことで

だいたいの場合 type(a).__bases__[0]) と同じ

__bases__ がタプルになっているのは
多重継承している場合があるから。


a.x
→ a.__dict__['x']
→ type(a).__dict__['x']
type(a).__bases__[0].__dict__['x']
type(a).__bases__[0].__bases__[0].__dict__['x']
...

Step3: instance.__dict__

オブジェクトの (書き込み可能な) 属性を保存するために使われる辞書またはその他のマッピングオブジェクトです。
__dict__ 属性


a.__dict__['x'] は
だいたいの場合 a.x と同じ

a.x
→ a.x
→ type(a).x
→ type(a).__bases__[0].x
→ type(a).__bases__[0].__bases__[0].x
...


もう1回書き換えて
a.x
→ a.x
→ type(a).x
→ type(a).__bases__[0].x
→ type(a).__bases__[0].__bases__[0].x
...  


4: type 関数, instance.__class__

返り値は型オブジェクトで、一般に object.__class__ によって返されるのと同じオブジェクトです。
type 関数

クラスインスタンスが属しているクラスです。
__class__ 属性

tyep(a) は
だいたいの場合 __class__ と同じ


a.x
a.__class__.x
a.__class__.__bases__[0].x
a.__class__.__bases__[0].__bases__[0].x ...  

◯ __class__ 属性への代入

__class__ 属性に別のクラスオブジェクトを代入することでクラスキャストのような動作をします。
Python でクラスキャスト -  

ポイント

インスタンスオブジェクトは
名前空間をつなぎ合わせただけで
表現されています。


最初の図は、インスタンスオブジェクトが、名前空間をつなぎ合わせただけで表現されていることを説明しようとしていました。どのようにしてつなぎ合わせているかというと、単純に属性に代入しているだけです。

具体的に言えば、インスタンスオブジェクトの __class__ 属性には、クラスオブジェクトが代入されています。クラスオブジェクトの __bases__ 属性には、親クラスオブジェクトが代入されています。

このことは実は Python では、「インスタンス化」と「継承」が「コンポジッション」で表現されていることを示してるのかなと思っています。

コンポジション(Composition)は、日本語で「混合物」を意味する単語である。あるクラスの機能を持つクラスのことを指す。 特定のクラスの機能を、自分が作るクラスにも持たせたい場合に、継承を使わずフィールドとしてそのクラスを持ち、 そのクラスのメソッドを呼び出すメソッドを持たせること。そうすることで、クラスに他のクラスの機能を組み込むことができる。
継承とコンポジションをどう使い分けるか


ただ Python では「継承」が「コンポジッションによって表現」されていたとしても、動作は「継承」が「実装によって表現」されるのと同じなので、結局、可能なら避けた方が望ましいことに変わりはないのかなと思ったりもします。

extends は悪だ; チャールズ・マンソンほどでないかもしれないが、可能ならいつでも避けなければならないほど悪いものだ。GoFデザインパターンの本は、ページ数を割いて、実装による継承 (extends) をインターフェイス (implements) による実装に置き換える方法について議論している。
The extends keyword is evil; maybe not at the Charles Manson level, but bad enough that it should be shunned whenever possible. The Gang of Four Design Patterns book discusses at length replacing implementation inheritance (extends) with interface inheritance (implements).

良い設計者は、ほとんどのコードをインターフェイスについて書いていて、具象ベースのクラスについては書いていない。この記事は、なぜ設計者がそのような奇妙な習慣を持つのかを説明し、2、3のインターフェイスベースのプログラミングの基礎について導入する。
Good designers write most of their code in terms of interfaces, not concrete base classes. This article describes why designers have such odd habits, and also introduces a few interface-based programming basics.
Why extends is evil; Improve your code by replacing concrete base classes with interfaces

変数の命名規則

◯ システムで定義された名前 (system-defined name)

__bases__, __class__, __dict__ とかアンダースコア _ をたくさん使った変数が出てきました。これは何でしょうか?システムで定義された (system-defined) 名前です。Python が使っているか、あるいは Python が使い方を指定した変数です。__init__ なんかもこれの仲間に入ります。

システムで定義された (system-defined) 名前です。これらの名前はインタプリタと (標準ライブラリを含む) 実装上で定義されています; ... (中略) ... このドキュメントで明記されている用法に従わない、 あらゆる __*__ の名前は、いかなる文脈における利用でも、警告無く損害を引き起こすことがあります。
2. 字句解析 — Python 3.6.3 ドキュメント

◯ プライベートな識別子

その他にもアンダースコア _ を使う命名規則として、プライベートな識別子であるかどうかを明示するために使います。プライベートな識別子であるとは、obj.attr のようにオブジェクトの属性として外部から参照されないということです。

項番 レベル 命名規則 参照の仕方
1 モジュール _val1 参照できない
2 クラス _val2 obj._val2
3 クラス __val3 obj._クラス名__val3
4 インスタンス _val4 obj._val4
5 インスタンス __val5 obj._クラス名__val5
6 関数 val6 参照できない
# モジュール定義文

# 1. モジュールのローカル変数
_val1 = 1
"""
# 参照
できない
# 説明
モジュールを import しても
この変数 _val は import されません。
"""


class Person(object):
    # クラス定義文

    # 2. クラスのローカル変数
    _val2 = 10
    """
    # 参照
    obj._val2
    # 機能
    無。命名規則です。
    """
    __val3 = 100
    """
    # 参照
    Person._Person__val3
    # 機能
    Person.__val3 では参照できない。
    Person._Person__val3 として参照します。
    # いつ使うの?
    他のクラスから継承されたときに
    名前が上書きされてしまうのを避けるため。
    """

    def func(self):
        # 関数定義文

        # 3. インスタンスのローカル変数
        self._val4 = 1000
        self.__val5 = 10000
        """
        クラスのローカル変数と同じ
        """

        # 4. 関数のローカル変数
        val6 = 100000
        """
        # 参照
        参照できない
        # 機能
        参照できない
        # 説明
        _val6 = 100000
        とはしないことの方が多い気がします。

        1つ目の理由は、
        関数のローカル変数なら、
        スコープが小さいので大抵自明だから。

        2つ目の理由は、
        関数には属性という概念は無いので
        func.val という様に外部から参照できないから。
        """

2.3.2. 予約済みの識別子種 (reserved classes of identifiers)
実践されている命名方法

終わりに

次は、関数とメソッドの違いを明確にしてオブジェクトの理解を深めます。