順番に読み進めながら学べます

クラス変数とインスタンス変数 — どこに値が存在するか

Pythonのクラス変数とインスタンス変数の違いを、tax_rateとcreated_countを例に解説。self.x = ... が新しい属性を作る挙動と、共有カウンタをProduct.x += 1で更新する理由を確認できます。

前回はコンストラクタ__init__を必須化やデフォルト引数で実用的に使う書き方、対になる__del__までを整理しました。今回はその上で、クラスに登場する2 種類の変数 — クラス変数とインスタンス変数 —の違いと、これまで何度も書いてきたself.x = ...メモリ上で何をしているのかを整理します。

2 種類の変数がある

Python のクラスに書く変数には、大きく分けて次の2 種類があります。

- クラス変数 — クラス直下(class Product:のすぐ下)に書く。全インスタンスで共有される値

- インスタンス変数 — メソッドの中でself.x = ...と書く。各インスタンスが個別に持つ

どちらもapple.nameのような同じドット記法でアクセスでき、見た目では違いが分かりません。だからこそ「どこに値が存在するか」をきちんと意識する必要があります。

class Product:
    tax_rate = 0.1                 # クラス変数(全員で共有)

    def __init__(self, name):
        self.name = name           # インスタンス変数(各自が持つ)

apple  = Product("りんご")
banana = Product("バナナ")
値が存在する場所が違う
Productクラス本体tax_rate=0.1(クラス変数)applename='りんご'banananame='バナナ'持つ共有参照共有参照
クラス変数tax_rateProduct 1 か所に置かれ、applebanana同じ場所を見に行く。インスタンス変数nameは各インスタンスが自分のメモリの中に持つ

クラス変数 — 全員で 1 つを共有

クラス変数はclass Product: のインデント直下tax_rate = 0.1のように書きます。メソッドの外に書くのがポイントです。クラス自身が 1 つだけ持ち、そのクラスから作られた全インスタンスで共有されます。

アクセスはProduct.tax_rate(クラス経由)でもapple.tax_rate(インスタンス経由)でもできます。後者の場合、Python はまずインスタンスを探し、見つからなければクラスを探すという順で値を引きに行きます。

変数の探索範囲 — インスタンス → クラスの順
Product(クラスの変数領域)
  • tax_rate = 0.1 ← クラス変数(共有)
apple(インスタンスの変数領域)
  • name = 'りんご' ← インスタンス変数
banana(インスタンスの変数領域)
  • name = 'バナナ' ← インスタンス変数
Python は属性を探すときまずインスタンスの中を見て、無ければ外側のクラスに問い合わせる。インスタンスから見れば、外側のクラス変数は共通の傘のように被さっている。
class Product:
    tax_rate = 0.1                 # クラス変数(全員で共有)

    def __init__(self, name, price):
        self.name = name           # インスタンス変数(各自が持つ)
        self.price = price

apple = Product("りんご", 150)
banana = Product("バナナ", 80)

print(Product.tax_rate)   # 0.1(クラス経由)
print(apple.tax_rate)     # 0.1(インスタンス経由でも読める)
print(banana.tax_rate)    # 0.1

# 同じメモリを指している
print(id(apple.tax_rate) == id(Product.tax_rate))  # True

クラス変数を 1 つ追加して、クラスからもインスタンスからも読めることを確認します。

class Product:の中にクラス変数 currency = "JPY"を 1 行で書いてください(__init__の上)。

__init__(self, name, price)self.name = name / self.price = priceを書いてください。

apple = Product("りんご", 150)を作り、print(Product.currency)print(apple.currency)の両方を実行して、同じ値が表示されることを確認してください。

(正しく実行できれば解説が表示されます)

Python エディタ

コードを実行してください

インスタンス変数 — 各自が持つ

インスタンス変数はメソッドの中で self.x = ...と書いた時点で、そのインスタンス専用の属性として作成されます。多くは__init__の中でself.name = nameのように初期化しますが、__init__以外のメソッドの中でself.x = ...と書いても、その瞬間に新しいインスタンス変数が作られます。

インスタンス変数は各インスタンスが個別のメモリに持つため、apple.nameを変えてもbanana.nameには一切影響しません。

インスタンス変数は各インスタンスが個別に持つ
apple(インスタンス)name = 'りんご'(独立メモリ)banana(インスタンス)name = 'バナナ'(独立メモリ)持つ持つ
apple.namebanana.name別のメモリ位置にある。apple.nameを書き換えてもbanana.nameには一切影響しない。
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

apple = Product("りんご", 150)
banana = Product("バナナ", 80)

print(apple.name, apple.price)     # りんご 150
print(banana.name, banana.price)   # バナナ 80

# id() を見ると別の場所を指している
print(id(apple.name) == id(banana.name))   # False

インスタンス変数が個別であることをid()で体感します。

class Product:を定義し、__init__(self, name, price)self.nameself.priceを初期化してください。

apple = Product("りんご", 150)banana = Product("バナナ", 80)を作ってください。

print(apple is banana)print(id(apple.name) == id(banana.name))の 2 行を実行し、両方とも Falseが返ることを確認してください。

Python エディタ

コードを実行してください

self.x = ... でインスタンス変数が作られる

クラス変数tax_rate = 0.1を持つProductで、あるインスタンスappleに対してapple.tax_rate = 0.05と書くとどうなるでしょうか?

結論から言うと、クラス変数は書き換えられずappleの中に新しいインスタンス変数 tax_rateが作られます。以後apple.tax_rateを読むと、Python はまずインスタンスを探し、見つかったのでクラスは見に行かないため、新しい 0.05 が返ります。bananaには何の影響もありません。

class Product:
    tax_rate = 0.1

    def __init__(self, name, price):
        self.name = name
        self.price = price

apple = Product("りんご", 150)
banana = Product("バナナ", 80)

# apple だけに新しい税率を持たせる
apple.tax_rate = 0.05

print(apple.tax_rate)      # 0.05(apple のインスタンス変数)
print(banana.tax_rate)     # 0.1 (クラス変数を見に行く)
print(Product.tax_rate)    # 0.1 (クラス変数は変わっていない)
代入前と代入後で何が変わるか
apple(インスタンス変数なし)banana(インスタンス変数なし)Producttax_rate=0.1apple.tax_rate=0.05★ 新規tax_rate=0.05appleのインスタンス変数banana(インスタンス変数なし)Producttax_rate=0.1(変わらず)共有参照参照参照
上段(代入前): applebananaもクラスのtax_rate=0.1を共有参照。下段(apple.tax_rate = 0.05 実行後): appleの中に新しいインスタンス変数が誕生し、appleだけそちらを読むようになる。bananaとクラスは変わらず。

クラス変数を「インスタンス経由で書き換えられる」と誤解しない

apple.tax_rate = 0.05を見ると一見「クラス変数を更新した」ように見えますが、実際にはapple の中に新しいインスタンス変数を作っただけです。共有しているクラス変数そのものを変えたいときは、Product.tax_rate = 0.05のようにクラス経由で代入する必要があります。

self.x = ...でインスタンス変数が新しく作られることを実際に確認します。

class Product:を定義し、クラス変数tax_rate = 0.1__init__(self, name)self.name = nameを書いてください。

apple = Product("りんご")banana = Product("バナナ")を作ってください。

apple.tax_rate = 0.05を実行したあと、print(apple.tax_rate) / print(banana.tax_rate) / print(Product.tax_rate)の 3 行を実行し、apple だけ値が変わって他は0.1 のままであることを確認してください。

Python エディタ

コードを実行してください

クラス変数を更新したいとき

「全インスタンスの税率を一斉に変えたい」「これまで作った商品の累計数を数えたい」のように、クラスがまとめて持っておきたい状態はクラス変数経由で更新します。

下の例のProduct.created_count「これまでに作られた商品の数」をクラス自身に持たせています。__init__の中でProduct.created_count += 1と書くと、どのインスタンスから作っても同じ場所を更新するため、全体の合計が正しく数えられます。これをself.created_count += 1と書いてしまうと、インスタンスごとに別の変数ができてしまい、合計を数える本来の目的が果たせなくなります。

Product.x += 1 と self.x += 1 の違い
Product.x+= 1クラスの x3 まで増える正しく累計が取れるself.x+= 1各自に新しい x が誕生クラスの x は0 のまま更新結果代入結果
クラス経由なら同じ 1 つのカウンタを全員で増やせる。self.x += 1だと各自が自分の中にコピーを作ってしまい、共有のカウンタは 0 のまま。
class Product:
    created_count = 0          # クラス変数:累計商品数

    def __init__(self, name):
        self.name = name
        Product.created_count += 1   # クラス経由で更新

apple = Product("りんご")
banana = Product("バナナ")
orange = Product("みかん")

print(Product.created_count)  # 3
print(apple.created_count)    # 3(インスタンス経由でも同じ値が読める)

クラス変数をインスタンス生成のたびに 1 ずつ増やすカウンターを作ります。

class Product:の中にクラス変数created_count = 0を書いてください。

__init__(self, name)self.name = nameを代入したあと、Product.created_count += 1を 1 行で書いてください。

Product("りんご") / Product("バナナ") / Product("みかん")を順に作り、最後にprint(Product.created_count)3が表示されることを確認してください。

Python エディタ

コードを実行してください

今回は、クラス変数とインスタンス変数の2 種類を整理し、self.x = ... を書くと毎回新しいインスタンス変数が作られること、インスタンス変数があればクラス変数より優先されること、そして全員で共有したい値はクラス経由で更新することを確認しました。

QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1クラス変数はどこに書きますか?

Q2次のコードを実行したとき、最後のprintの出力はどれですか?
class P:
n = 10
a = P()
a.n = 99
b = P()
print(b.n)

Q3クラス変数count全インスタンスから共通でカウントアップしたいとき、__init__の中で書くべきはどれですか?