「同じクラスから作ったオブジェクトなのに、ある値は全員同じ、別の値は人によって違う。これってどうやって区別するの?」そんな疑問、ありませんか?
Pythonのオブジェクト指向では「クラス変数」と「インスタンス変数」を使い分けることで、共有したい情報と個別に持たせたい情報をうまく表現します。さらに継承を使うと、親クラスの設定や状態を子クラスに自然に引き継げます。
このセクションでは、クラス変数とインスタンス変数の違い、そして継承時にどう振る舞うのかを、初心者の方にもわかりやすく丁寧に解説します。クラス設計のコツや、つまずきやすいポイントも一緒に確認していきましょう。
簡単にイメージすると、クラス変数はクラス全体で共有する掲示板のようなもの。クラスから作られたすべてのインスタンスが同じ値を見ます。
一方でインスタンス変数は各インスタンスの名札。同じクラスから作っても、名札の内容はインスタンスごとに自由に変えられます。
「どれをクラス変数にして、どれをインスタンス変数にすべき?」と迷ったら、「全員共通の属性か?それとも個別の属性か?」で判断するとスッキリします。
以下の例では、Animalを親クラス、Dog を子クラスにして、クラス変数とインスタンス変数が継承でどう使われるかを見ていきます。
class Animal:
# クラス変数
species = "Animal"
def __init__(self, name):
# インスタンス変数
self.name = name
def display_info(self):
print(f"{self.species}: {self.name}")
class Dog(Animal):
# クラス変数をオーバーライド
species = "Dog"
def __init__(self, name, breed):
# 親クラスのコンストラクタを呼び出す
super().__init__(name)
# 子クラスのインスタンス変数
self.breed = breed
def display_info(self):
# 親クラスのメソッドを呼び出し
super().display_info()
print(f"Breed: {self.breed}")
# インスタンスを作成
animal = Animal("Generic Animal")
dog = Dog("Rex", "Golden Retriever")
# 情報を表示
animal.display_info()
dog.display_info()
# クラス変数の確認
print(f"Animal species: {Animal.species}")
print(f"Dog species: {Dog.species}")
コードを詳しく解説します。
まず Animal クラスには、クラス全体で共有する species
というクラス変数があります。Animal.species
は「Animal」という文字列で、Animalから作られたすべてのインスタンスは、特別なことをしない限りこの値を参照します。
同時に、コンストラクタ __init__
の中では、個別の名前を表す self.name
というインスタンス変数を用意しています。これはインスタンスごとに違う値を持てます。
次に Dog クラスは Animal を継承しています。注目ポイントは、同じ名前 species
のクラス変数を子クラスで定義し直しているところです。これを「オーバーライド」と呼びます。
Dog では species
が「Dog」になるので、Dog のインスタンス dog は、self.species
と書いたときに「Dog」を参照します。また、Dog 独自の情報として breed(犬種)をインスタンス変数に追加しています。
display_info
メソッドでは、super().display_info()
を使って親クラスの表示ロジックをそのまま活かしつつ、子クラスならではの犬種の表示を一行足しています。こうすることで「親の良いところを再利用しながら、必要な差分だけ追加する」という継承のメリットをそのまま享受できます。
最後にクラス変数を直接クラスから参照して確認すると、Animal.species
は「Animal」、Dog.species
は「Dog」と表示され、親子で独立していることがわかります。これがクラス変数のオーバーライドです。
「self.species はどこを見に行ってるの?」と気になる方もいるはずです。Pythonは属性を探すとき、次の順番で探します。
1) まずインスタンス自身(dog.__dict__
)
2) 次にそのクラス(Dog.__dict__
)
3) さらに親クラス(Animal.__dict__
)
…という順で、必要に応じて親の親へと辿ります。これをメソッド解決順序(MRO)と呼びます。
だから Dog
のインスタンスで self.species
を見ると、まずインスタンスに同名の属性が無ければ、Dog
クラスの species
を見つけ、「Dog」が返ってくるのです。もし Dog
に無ければ、親の Animal
の species
を使います。
この仕組みを理解すると、クラス変数とインスタンス変数の「見つかり方」がスッと腑に落ちます。
クラス変数はクラスに属しています。だからクラス側で書き換えると、インスタンスから見える値も変わります(ただし、子クラスでオーバーライドしている場合は子クラス側が優先です)。
# ↑のコードの続き
# Animal 側のクラス変数を変更
Animal.species = "Creature"
animal = Animal("Foo")
print(animal.species) # Creature(Animalの新しい値)
print(Animal.species) # Creature
dog = Dog("Bar", "Beagle")
print(dog.species) # Dog(Dogでオーバーライドしているため影響なし)
print(Dog.species) # Dog
「親クラスの掲示板を書き換えても、子クラスが自分の掲示板を持っていれば、そちらが優先される」――そんなイメージで捉えると理解しやすいです。
インスタンスに同名の属性を作ると、インスタンス変数がクラス変数を“上書きして見える”ようになります。これを「シャドーイング」と呼ぶことがあります。
# ↑のコードの続き
dog = Dog("Baz", "Pug")
print(dog.species) # Dog(クラス変数)
# インスタンスに同名の属性を追加
dog.species = "Pet"
print(dog.species) # Pet(インスタンス変数が優先される)
# しかしクラス自体は変わらない
print(Dog.species) # Dog
「特定のインスタンスだけ、ちょっと違う表示にしたい」というときに便利ですが、意図せず上書きして混乱することもあるので気をつけましょう。
リストや辞書のような“中身を書き換えられる(ミュータブル)”オブジェクトをクラス変数にすると、全インスタンスで共有されます。意図しない“連動”が起きやすいので要注意です。
class Kennel:
tags = [] # これは全インスタンスで共有される
def add_tag(self, tag):
self.tags.append(tag)
a = Kennel()
b = Kennel()
a.add_tag("clean")
print(b.tags) # ['clean'] 思わず共有されている!
個別に持たせたいなら、ミュータブルな値は __init__
の中でインスタンス変数として作るのが安全です。
class SafeKennel:
def __init__(self):
self.tags = [] # 各インスタンスごとに別のリスト
def add_tag(self, tag):
self.tags.append(tag)
「共有したいのか、個別にしたいのか?」を最初に決めて、ミュータブルな値は基本的にインスタンス変数にする――このルールを覚えておくとトラブルを避けやすくなります。
ここまでの内容をまとめます。
「この値は全員同じでいいのか?それとも1つ1つ違うのか?」――この問いかけを常に意識すると、クラス変数とインスタンス変数、そして継承の使い分けが自然と身につきます。最初は小さなサンプルから、少しずつ手を動かして確かめてみましょう。