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

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

クラス変数とインスタンス変数(データ属性)

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

 Python においては、個々の円の半径が インスタンス変数(データ属性)で、すべての円が共通してもつ円周率の値が クラス変数 ということになります。実際に円オブジェクトを生み出す Circles というクラスを定義して、クラス変数とインスタンス変数の違いを見てみましょう。

# https://python.atelierkobato.com/instance/

# Circlesクラスの定義
class Circles:
    # 円周率の近似値をクラス変数として定義
    pi = 3.141592653589793
    
    # 半径をインスタンス変数(データ属性)として定義
    def __init__(self, r):
        self.radius = r
    
    # 円の面積を計算するメソッド
    def area(self):
        return self.pi * self.radius ** 2
    
    # 円の周長を計算するメソッド
    def perimeter(self):
        return 2 * self.pi * self.radius

# 半径 2, 5 のインスタンスを生成
circle_a = Circles(2)
circle_b = Circles(5)

# 円周率の参照
print("circle_aの円周率 :", circle_a.pi)
print("circle_bの円周率 :", circle_b.pi)

# 半径を参照
print("circle_aの半径 :", circle_a.radius)
print("circle_bの半径 :", circle_b.radius)

# area()メソッドで面積を計算
print("circle_aの面積 :", circle_a.area())
print("circle_bの面積 :", circle_b.area())

# perimeter()メソッドで周長を計算
print("circle_aの周長 :", circle_a.perimeter())
print("circle_bの周長 :", circle_b.perimeter())
circle_aの円周率 : 3.141592653589793
circle_bの円周率 : 3.141592653589793
circle_aの半径 : 2
circle_bの半径 : 5
circle_aの面積 : 12.566370614359172
circle_bの面積 : 78.53981633974483
circle_aの周長 : 12.566370614359172
circle_bの周長 : 31.41592653589793

 
 Circlesクラスでは円周率をクラス変数として定義しています:

    # 円周率の近似値をクラス変数として定義
    pi = 3.141592653589793

 クラス変数は次のような記法で定義します。

 クラス変数名 = 値

 クラス変数が定義されると、そのクラスから生み出されるすべてのインスタンスが同じ変数をもつことになります。Circlesクラスから生み出されるすべてのインスタンスは、円周率 pi = 3.141592653589793 という共通の情報をもちます。

 
 各々の円がもつ半径はインスタンス変数として与えます。
 インスタンス変数は、インスタンスが作られるときに個々に渡されます:

    # 半径をインスタンス変数として定義
    def __init__(self, r):
        self.radius = r

 
 Circlesクラスのインスタンスは、自身の面積を計算するメソッドをもちます:

    # 円の面積を計算するメソッド
    def area(self):
        return self.pi * self.radius ** 2

 このメソッドを使うときに引数は必要ありませんが、内部ではクラス変数 self.pi とインスタンス変数 self.radius の 2 乗を掛け算して面積を計算しています。

 
 Circlesクラスのインスタンスは、自身の周長を計算するメソッドも備えています:

    # 円の周長を計算するメソッド
    def perimeter(self):
        return 2 * self.pi * self.radius

 
 Circlesクラスのインスタンスを 2 種類作って、それぞれのインスタンスのもつクラス変数(円周率)を参照します:

# 半径 2, 5 のインスタンスを生成
circle_a = Circles(2)
circle_b = Circles(5)

print("circle_aの円周率 :", circle_a.pi)
print("circle_bの円周率 :", circle_b.pi)

 このように、あるオブジェクトのクラス変数を参照したいときは

 オブジェクト.クラス変数名

と記述します。次のコードではインスタンスの半径を取得します:

print("circle_aの半径 :", circle_a.radius)
print("circle_bの半径 :", circle_b.radius)

 このように、オブジェクトのインスタンス変数を取得するときは

 オブジェクト.インスタンス変数名

と記述します。上のサンプルコードには area()メソッドや、perimeter()メソッドの使用例も載せてあるので、確認しておいてください。
 

メタクラスとクラスオブジェクト

 Python はすべてがオブジェクトです。あらゆるオブジェクトは、何かのクラスから作られるインスタンスです。すると、
「すべてがオブジェクトであるというならば、クラスはどうなるの?」
という疑問が生じるかもしれません。実はクラスもやはり、typeクラスから生成されるオブジェクトなのです (type()関数の引数にクラス名を指定すると <class 'type'> と表示されます) 。クラスを生み出すクラスのことを メタクラス とよびますが、非常に高度な概念なので今の段階ではこれ以上踏み込みません(勉強してもあまり実用的ではありません)。

 しかし「クラス自身もオブジェクトである」ということだけでも知っておくと、Pythonの設計を理解しやすくなります。クラスがオブジェクトであることを明示したいときには、敢えてクラスオブジェクトとよぶことがあります。

 クラス変数はクラスオブジェクトの属性です。これはクラス変数が、クラスのインスタンスだけでなく、クラスオブジェクトから直接参照できることを意味しています。上のサンプルコードの一番下に次のコードを追加してみてください。

print(Circles.pi)

 コードを実行すると最後の行に次のように表示されます。

3.141592653589793

 これは、Circlesクラスというクラスオブジェクトから直接的にクラス変数を参照できることを意味しています。

 一方で、次のコードを追加すると実行に失敗します。

print(Circles.raduis)

 AttributeError が発生し、最後の行で

type object 'Circles' has no attribute 'raduis'

というメッセージが表示されます。Circles というクラスオブジェクトが radius という属性をもたないという意味です。インスタンス変数 radius をもつのは、あくまで Circles から作られたインスタンスであって、Circles 自身はインスタンス変数 radius をもっていないということです。
 

クラス変数を定義するメリット

 クラス変数を使用することの利点は、グローバル変数に頼らなくてすむということです。
 クラス変数はクラス自身から、あるいはクラスから生成されたあらゆるインスタンスから簡単に参照できますが、クラス自身と個々のインスタンスの内側に隔離された変数なので、グローバル変数のように名前の重複によって上書きされることがありません。
 たとえば、Circlesクラスの外側で pi という同じ名前の変数を定義しても、クラス変数は一切影響を受けません。試しに上のサンプルコードの一番上に

pi = 3.14

というコードを追加してみてください。実行結果はひとつも変わらないはずです。外側の pi とクラス内部で定義された pi は別物として扱われるからです。