クラスを学び始めると、同じ「変数」でもクラス変数とインスタンス変数の2種類があることに戸惑いやすいです。
両者は見た目が似ていますが、「どこに保存されているか」「だれが共有するか」「いつまで生きるか」が決定的に異なります。
本記事ではPython初心者の方に向けて、定義方法から挙動、落とし穴、継承時の使い分けまで丁寧に解説します。
クラス変数とインスタンス変数とは
クラス変数(class variable)の定義と特徴
クラス変数は、クラス定義の直下で宣言する変数です。
クラスオブジェクトに紐づくため、原則として全インスタンスで共有されます。
クラス名経由でアクセスするのが基本です。
- 役割は「全体で共有したい情報」を持つことです。たとえば定数(税率や単位)、生成数のカウンタ、共通の設定などが該当します。
- スコープはクラスに属し、クラスが存在する限り有効です。
- インスタンスからも参照できますが、後述の通り代入は新しいインスタンス変数の作成(シャドーイング)になりやすい点に注意します。
インスタンス変数(instance variable)の定義と特徴
インスタンス変数は、__init__
などのメソッド内でself.attr
の形で設定する属性です。
各インスタンスごとに独立して保持され、片方を変更しても他方に影響しません。
- 役割は「各オブジェクトの固有の状態」を表すことです。ユーザー名、座標、残高などはインスタンスごとに違います。
- スコープはインスタンスに属し、そのインスタンスが生きている間だけ有効です。
違いの要点(共有範囲と寿命)
両者の本質的な違いを、共有範囲と寿命の観点で整理します。
観点 | クラス変数 | インスタンス変数 |
---|---|---|
保存場所 | クラス(型)に保存 | 各インスタンスに保存 |
共有範囲 | 全インスタンスで共有 | 共有しない(独立) |
典型用途 | 定数、共通設定、生成数カウンタ | オブジェクト固有の状態 |
寿命 | クラスが生きている間 | インスタンスが生きている間 |
変更の影響 | 全インスタンスに及ぶ | そのインスタンスのみ |
アクセス基本形 | ClassName.attr | self.attr |
「共有したいならクラス変数、個別に持たせたいならインスタンス変数」が基本指針です。
定義とアクセスの基本(Pythonの書き方)
クラス変数の定義位置とアクセス(ClassName.attr)
クラス変数はクラス定義直下で宣言します。
アクセスと更新はClassName.attr
経由が安全です。
# クラス変数の定義とアクセス例
class AppConfig:
# クラス定義直下に置いた属性はクラス変数
APP_NAME = "SampleApp" # 定数的な使い方
DEFAULT_TIMEOUT = 5 # 共有設定
# クラス名から参照
print(AppConfig.APP_NAME)
print(AppConfig.DEFAULT_TIMEOUT)
# インスタンスからも参照は可能(後述の通り代入は注意)
cfg1 = AppConfig()
cfg2 = AppConfig()
print(cfg1.APP_NAME, cfg2.APP_NAME)
SampleApp
5
SampleApp SampleApp
補足
クラス変数に代入するときはクラス名で行うと明確で安全です。
インスタンスから代入するとシャドーイング(影)が発生しやすく混乱を招きます。
インスタンス変数の定義(__init__とself.attr)
インスタンス変数は__init__
でself
に結びつけるのが基本です。
これは「生成時に初期設定をしたい」という要件に合致します。
# インスタンス変数の定義と基本アクセス
class User:
def __init__(self, name, point=0):
self.name = name # インスタンス変数
self.point = point # インスタンス変数
u1 = User("Alice", 10)
u2 = User("Bob", 20)
print(u1.name, u1.point)
print(u2.name, u2.point)
Alice 10
Bob 20
補足
インスタンス変数はself
経由で定義・参照・更新するのが基本です。
モジュール定数のような共有設定はクラス変数、オブジェクト固有の状態はインスタンス変数に分けます。
代入時の挙動(シャドーイングと上書き)
同じ名前がクラス変数とインスタンス変数の両方に存在しうる点が混乱の元です。
インスタンスから代入すると、そのインスタンスに新しい属性が作られ、クラス変数を「隠す」ことがあります。
# シャドーイング(影)と上書きの違い
class Item:
tax_rate = 0.1 # クラス変数(共有)
def __init__(self, price):
self.price = price # インスタンス変数(個別)
i1 = Item(100)
i2 = Item(200)
# クラス変数の参照は両者とも0.1
print(i1.tax_rate, i2.tax_rate)
# i1から同名に代入 → i1にだけインスタンス属性tax_rateが新規作成される(シャドーイング)
i1.tax_rate = 0.2
print(i1.tax_rate, i2.tax_rate) # i1だけ0.2、i2は依然としてクラス変数0.1
# クラス名経由で上書き → 全インスタンスに影響
Item.tax_rate = 0.08
print(i1.tax_rate, i2.tax_rate) # i1はインスタンス属性0.2が優先、i2は0.08を参照
# シャドーイングを解除したい場合はインスタンス属性を削除
del i1.tax_rate
print(i1.tax_rate, i2.tax_rate) # どちらもクラス変数0.08を参照
0.1 0.1
0.2 0.1
0.2 0.08
0.08 0.08
インスタンスから代入すると同名のインスタンス属性を作ってしまい、クラス変数を上書きしたつもりが「隠しただけ」になる点に注意しましょう。
変更の影響と挙動の違い(初心者向け注意点)
クラス変数の変更は全インスタンスに影響
クラス変数は共有されるため、クラス名経由での変更は原則としてすべてのインスタンスに波及します。
class FeatureFlag:
enabled = False # 全体で共有するフラグ
a = FeatureFlag()
b = FeatureFlag()
print(a.enabled, b.enabled)
FeatureFlag.enabled = True
print(a.enabled, b.enabled)
False False
True True
インスタンス変数の変更はそのインスタンスのみ
インスタンス変数は独立しているため、片方の変更は他方に影響しません。
class Account:
def __init__(self, balance=0):
self.balance = balance
x = Account(100)
y = Account(100)
x.balance += 50
print(x.balance, y.balance) # xだけ増える
150 100
ミュータブル型(list, dict)共有の落とし穴
ミュータブル型をクラス変数に置くと、すべてのインスタンスで「同じオブジェクト」を参照してしまうため、思わぬ共有が発生します。
# 悪い例: クラス変数にミュータブル(list)を置くと全インスタンスで共有される
class BasketBad:
items = [] # 全インスタンスで共有されるリスト
def add(self, item):
self.items.append(item)
b1 = BasketBad()
b2 = BasketBad()
b1.add("apple")
b2.add("banana")
print("b1:", b1.items)
print("b2:", b2.items)
b1: ['apple', 'banana']
b2: ['apple', 'banana']
回避策はインスタンスごとに新しいリストを__init__
で作ることです。
# 良い例: インスタンス変数として初期化する
class BasketGood:
def __init__(self):
self.items = [] # インスタンスごとに別のリスト
def add(self, item):
self.items.append(item)
g1 = BasketGood()
g2 = BasketGood()
g1.add("apple")
g2.add("banana")
print("g1:", g1.items)
print("g2:", g2.items)
g1: ['apple']
g2: ['banana']
型ヒントを使う場合はtyping.ClassVar
でクラス変数を明示すると、誤用を検知しやすくなります(後述のベストプラクティス参照)。
属性探索順序(インスタンス→クラス)
Pythonの属性参照obj.attr
は、次の順に探索します。
- インスタンス自身の
__dict__
(インスタンス変数) - クラス
type(obj)
の属性(クラス変数、メソッド) - 親クラス(MRO)の順
class Base:
x = "class Base" # クラス変数
class Sub(Base):
pass
s = Sub()
print("1) 最初はクラス変数:", s.x)
# 同名のインスタンス属性を作ると優先される(シャドーイング)
s.x = "instance Sub"
print("2) インスタンス属性が優先:", s.x)
# インスタンス属性を消すと、再びクラス変数が見える
del s.x
print("3) クラス変数にフォールバック:", s.x)
# 実際の探索の様子を確認
print("instance __dict__:", s.__dict__)
print("class __dict__ has x:", "x" in Sub.__dict__)
print("base __dict__ has x:", "x" in Base.__dict__)
1) 最初はクラス変数: class Base
2) インスタンス属性が優先: instance Sub
3) クラス変数にフォールバック: class Base
instance __dict__: {}
class __dict__ has x: False
base __dict__ has x: True
意図しないシャドーイングを避けるため、共有値の更新はClassName.attr
で行うのが安全です。
継承時の違いと使い分けの指針
継承でのクラス変数の上書き(オーバーライド)
サブクラスはクラス変数を再定義して上書き(オーバーライド)できます。
サブクラスごとの設定を変えたいときに便利です。
class Product:
tax_rate = 0.10 # 親の既定税率
def price_with_tax(self, price):
return int(price * (1 + self.tax_rate))
class Food(Product):
tax_rate = 0.08 # 食品は軽減税率
class Luxury(Product):
tax_rate = 0.15 # 贅沢品は高税率
print(Food().price_with_tax(1000)) # 1080
print(Luxury().price_with_tax(1000)) # 1150
print(Product().price_with_tax(1000)) # 1100
1080
1150
1100
メソッド内でself.tax_rate
と書いても、最終的にはクラス変数へ解決されるため、サブクラスの値が適用されます。
インスタンス変数の拡張と初期化(__init__)
サブクラスで固有の状態を増やす場合は、__init__
でインスタンス変数を追加します。
親の初期化が必要ならsuper()
で呼び出します。
class UserBase:
def __init__(self, name):
self.name = name # 基本情報
class PremiumUser(UserBase):
def __init__(self, name, level):
super().__init__(name) # 親の初期化を忘れない
self.level = level # サブクラス固有の状態
u = PremiumUser("Alice", level=2)
print(u.name, u.level)
Alice 2
使い分けの実践例(カウンタ・設定定数)
代表的な使い分けとして、生成数カウンタはクラス変数、個別IDはインスタンス変数で実装します。
設定定数もクラス変数に置けます。
class Session:
# クラス変数: 生成数のカウンタと設定定数
counter = 0
TIMEOUT_SECONDS = 15 # 設定定数(原則として変更しない想定)
def __init__(self, user):
# 新しいセッションが作られるたびにカウンタを増やす
Session.counter += 1
self.id = Session.counter # インスタンス変数: 個別ID
self.user = user # インスタンス変数: 所有者
def __str__(self):
return f"Session(id={self.id}, user={self.user}, timeout={Session.TIMEOUT_SECONDS})"
s1 = Session("alice")
s2 = Session("bob")
print(s1)
print(s2)
print("total:", Session.counter)
Session(id=1, user=alice, timeout=15)
Session(id=2, user=bob, timeout=15)
total: 2
ベストプラクティス(状態はインスタンス、設定はクラス)
クラス変数とインスタンス変数は意図を明確に分けると理解しやすくなります。
- 変化する状態やデータはインスタンス変数に置く(例: 残高、名前、座標)。
- 変わらない設定値や全体で共有したい情報はクラス変数に置く(例: 定数、既定値、カウンタ)。
- ミュータブルな共有が必要なら、明示的に排他制御やコピーを伴う設計にする。多くの場合はインスタンス側に持たせる。
- 型ヒントでは
typing.ClassVar
を使ってクラス変数を明示することで、静的解析で誤用を検知しやすくなります。
from typing import ClassVar
class Config:
# ClassVarでクラス変数を明示(型チェッカーに「インスタンス変数にしない」意図を伝える)
VERSION: ClassVar[str] = "1.0.0"
DEFAULT_RETRY: ClassVar[int] = 3
def __init__(self, name: str):
self.name = name # インスタンス変数(状態)
まとめ
本記事では、Pythonのクラス変数とインスタンス変数の違いを、定義場所、アクセス、寿命、共有範囲、継承時の挙動まで段階的に解説しました。
共有したい不変の設定はクラス変数、オブジェクト固有で変化する状態はインスタンス変数という指針を守ることで、思わぬバグ(シャドーイングやミュータブル共有)を避けられます。
特にインスタンスから同名属性に代入するとシャドーイングが起きる点、リストや辞書をクラス変数に置くと全インスタンスで共有される点は必ず押さえましょう。
最後に、定数はClassName.attr
で参照・更新し、状態はself.attr
で扱うという一貫したコーディングスタイルを徹底することが、読みやすく保守しやすいコードへの近道です。