Q1クラス変数はどこに書きますか?
クラス変数とインスタンス変数 — どこに値が存在するか
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("バナナ")
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__の中で書くべきはどれですか?