クラス変数とインスタンス変数

クラス変数とインスタンス変数

クラス変数とインスタンス変数(アトリビュート)

クラス変数

 クラスの内部 (スイート) に定義される変数を クラス変数 とよびます。
 クラス変数はクラス自身がもつ属性値であり、すべてのインスタンスに共有されます。
 たとえば、次のような単純なクラスを設計してみます。

# In[1]

class My_class:
    x = 100

 クラス変数 x はクラス自身がもつ情報なので、クラスの属性値として参照できます。

# In[2]

# My_classのクラス変数xを参照
print(My_class.x)
100

 クラス変数を上書きすることもできます。

# In[3]

# クラス変数を上書きする
My_class.x = 200

# My_classのクラス変数xを参照
print(My_class.x)
200

 ただし、クラス変数の上書きはクラスの設計変更にあたり、その後生成されるすべてのインスタンスが影響を受けます。原則として設計変更はクラスの内部 (スイート) を修正して対応すべきです。特にクラスの定義文とは全く関係ない箇所でそのようなコードを書き加えてしまうとコードの可読性は著しく損なわれます。他人があなたのコードを読むとき、クラスの設計内容はクラスの定義文にまとまっていると思っています。クラス変数を外から書き換えるような方法はなるべく避けてください。

 クラス変数は個々のインスタンスから参照することもできます。

# In[4]

# My_classのインスタンスを作成
My_object = My_class()

# クラス変数を参照
print(My_object.x)
100

 ただし、インスタンスにクラス変数が保存されているわけではありません(そのようなコピーはメモリの無駄遣いです)。上の参照方法はオブジェクトを介して間接的にクラス変数にアクセスしているだけです。

 クラス変数を使用することの利点は、グローバル変数に頼らなくてすむということです。
 クラス変数はクラス自身から、あるいはクラスから生成されたインスタンスから簡単に参照できますが、クラスの内側に隔離された変数なので、グローバル変数のように名前の重複によって上書きされることがありません。
 

インスタンス変数 (アトリビュート)

 __init__() に定義される、すなわち個々のインスタンス生成時に与えられる変数を インスタンス変数 (アトリビュート) とよびます。たとえば、次のようなクラスを設計したとします。

# In[1]

class Class_A:
    def __init__(self, value):
        self.x = value

 Class_A のインスタンスは生成時に引数 value に受け取った値をインスタンス変数として内部に保存します。たとえば、value に 100 を渡すとインスタンス変数 100 を保存します。この値はインスタンスの属性値として参照できます。

# In[2]

object_1 = Class_A(100)

# インスタンス変数を参照
print(object_1.x)
100

 value に "apple" を渡せばインスタンス変数 "apple" を保存します。

# In[3]

object_2 = Class_A("apple")

# インスタンス変数を参照
print(object_2.x)
apple

 インスタンス変数は外部から書き換えることができます。

# In[4]

# インスタンス変数xを上書きする
object_2.x = "orange"

# インスタンス変数を参照
print(object_2.x)
orange

 インスタンス変数の追加も可能です。

# In[5]

# インスタンス変数yを追加する
object_2.y = "banana"

# インスタンス変数yを参照
print(object_2.y)
banana

 

Circlesクラス

 円のデータを生成するクラスを設計してみましょう。数学で「円」に分類される図形はすべて相似なので、半径を与えるだけでその特徴が定まります。つまり、ある円を他の円と区別する情報は半径だけです。しかし、半径を与えるだけでは円としての情報は不十分です。良く知られているのように、個々の円の面積や周長などを計算するには、すべての円が共通してもつ「円周率の値」という情報が必要不可欠です。そこで、すべての円が共通してもつ円周率の値をクラス変数、個々の円の半径をインスタンス変数としてクラスを設計してみます。

# In[1]

# 円クラスを定義
class Circles:
    # 円周率の近似値をクラス変数として定義
    pi = 3.141592653589793
    
    # 半径,面積,周長をインスタンス変数として定義
    def __init__(self, r):
        self.radius = r
        self.area =self.pi * self.radius ** 2
        self.perimeter = 2 * self.pi * self.radius

 円周率 pi をクラス変数として定義して、__init__() の内部で半径 (radius)、面積 (area)、周長 (perimeter) を計算するのに用いています。半径 5 の円を生成して、クラス変数とインスタンス変数を表示してみます。

# In[2]

# Circlesクラスのインスタンスを生成
c1 = Circles(5)

# クラス変数を参照
print("円周率: {:.5f}".format(c1.pi))

# インスタンス変数を参照
print("半径: {:.3f}".format(c1.radius))
print("面積: {:.3f}".format(c1.area))
print("周長: {:.3f}".format(c1.perimeter))
円周率: 3.14159
半径: 5.000
面積: 78.540
周長: 31.416

 

データの上書きに関する注意点

 すでに述べたように、個々のインスタンスを介してクラス変数を参照できますが、データの書き換えはできません。しかし、一見するとクラス変数を書き換えたように錯覚するので注意が必要です。次のコードを実行してみてください。

# In[1]

class My_class:
    x = 100

my_object = My_class()
my_object.x = 200
print(my_object.x)
200

 実行結果を見ると、my_object を介してクラス変数 x を 200 に書き換えることに成功したように思えます。しかし、実際はそうではありません。クラス変数を直接参照してみましょう。

# In[2]

# クラス変数を参照
print(My_class.x)
100

 クラス変数は元の値 100 のままです。
 どうしてこのような結果になたのでしょうか?

 実は、my_object.x = 200 は「my_object に新しいインスタンス変数 x を追加して 200 を代入する」ことを意味するコードなのです。つまり、my_object はクラス変数と同名のインスタンス変数を備えるようになったということです。このようなケースでは、もはやインスタンスを介してクラス変数を参照することができません。My_class.x という参照がインスタンス変数 x を素通しできないからです。

 インスタンスからクラス変数を参照できるのは、クラス変数と同名インスタンス変数が存在しない場合です。とはいえ、先にも述べたように「外側からクラス変数を変更しない」というマナーを守るべきです。そうすれば、わざわざ複雑なルールを覚える必要はなくなります。