Q1クラス変数 はどこに書きますか?
クラス変数とインスタンス変数 — どこに値が存在するか
Python のクラス変数とインスタンス変数の違いを図解で解説します。共有される値と各インスタンス固有の値、self.x = ... で何が起きるかまで押さえます。
前回はコンストラクタ __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("バナナ")
tax_rate は Product 1 か所に置かれ、apple も banana も同じ場所を見に行く。インスタンス変数 name は各インスタンスが自分のメモリの中に持つ。クラス変数 — 全員で 1 つを共有
クラス変数は class Product: のインデント直下 に tax_rate = 0.1 のように書きます。メソッドの外に書くのがポイントです。クラス自身が 1 つだけ持ち、そのクラスから作られた全インスタンスで共有されます。
アクセスは Product.tax_rate(クラス経由)でも apple.tax_rate(インスタンス経由)でもできます。後者の場合、Python は まずインスタンスを探し、見つからなければクラスを探す という順で値を引きに行きます。
- tax_rate = 0.1 ← クラス変数(共有)
- name = 'りんご' ← インスタンス変数
- name = 'バナナ' ← インスタンス変数
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
インスタンス変数 — 各自が持つ
インスタンス変数は メソッドの中で self.x = ... と書いた時点で、そのインスタンス専用の属性として作成 されます。多くは __init__ の中で self.name = name のように初期化しますが、__init__ 以外のメソッドの中で self.x = ... と書いても、その瞬間に 新しいインスタンス変数 が作られます。
インスタンス変数は 各インスタンスが個別のメモリに持つため、apple.name を変えても banana.name には一切影響しません。
apple.name と banana.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
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 もクラスの tax_rate=0.1 を共有参照。下段(apple.tax_rate = 0.05 実行後): apple の中に 新しいインスタンス変数 が誕生し、apple だけそちらを読むようになる。banana とクラスは変わらず。クラス変数を「インスタンス経由で書き換えられる」と誤解しない
apple.tax_rate = 0.05 を見ると一見「クラス変数を更新した」ように見えますが、実際には apple の中に新しいインスタンス変数を作っただけ です。共有しているクラス変数そのものを変えたいときは、Product.tax_rate = 0.05 のように クラス経由で代入 する必要があります。
クラス変数を更新したいとき
「全インスタンスの税率を一斉に変えたい」「これまで作った商品の累計数を数えたい」のように、クラスがまとめて持っておきたい状態 はクラス変数経由で更新します。
下の例の Product.created_count は 「これまでに作られた商品の数」 をクラス自身に持たせています。__init__ の中で Product.created_count += 1 と書くと、どのインスタンスから作っても同じ場所を更新するため、全体の合計が正しく数えられます。これを self.created_count += 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(インスタンス経由でも同じ値が読める)
今回は、クラス変数とインスタンス変数の 2 種類 を整理し、self.x = ... を書くと毎回新しいインスタンス変数が作られる こと、インスタンス変数があればクラス変数より優先される こと、そして全員で共有したい値はクラス経由で更新することを確認しました。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2次のコードを実行したとき、最後の print の出力はどれですか?class P: n = 10a = P()a.n = 99b = P()print(b.n)
Q3クラス変数 count を 全インスタンスから共通でカウントアップ したいとき、__init__ の中で書くべきはどれですか?