<一覧に戻る

クラス変数とインスタンス変数の継承

「同じクラスから作ったオブジェクトなのに、ある値は全員同じ、別の値は人によって違う。これってどうやって区別するの?」そんな疑問、ありませんか?

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」と表示され、親子で独立していることがわかります。これがクラス変数のオーバーライドです。

ここが大事:属性の参照順序(MRO)と継承

「self.species はどこを見に行ってるの?」と気になる方もいるはずです。Pythonは属性を探すとき、次の順番で探します。

1) まずインスタンス自身(dog.__dict__) 2) 次にそのクラス(Dog.__dict__) 3) さらに親クラス(Animal.__dict__) …という順で、必要に応じて親の親へと辿ります。これをメソッド解決順序(MRO)と呼びます。

だから Dog のインスタンスで self.species を見ると、まずインスタンスに同名の属性が無ければ、Dog クラスの species を見つけ、「Dog」が返ってくるのです。もし Dog に無ければ、親の Animalspecies を使います。

この仕組みを理解すると、クラス変数とインスタンス変数の「見つかり方」がスッと腑に落ちます。

変更して確かめよう:クラス変数を後から書き換えると?

クラス変数はクラスに属しています。だからクラス側で書き換えると、インスタンスから見える値も変わります(ただし、子クラスでオーバーライドしている場合は子クラス側が優先です)。

# ↑のコードの続き
# 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)

「共有したいのか、個別にしたいのか?」を最初に決めて、ミュータブルな値は基本的にインスタンス変数にする――このルールを覚えておくとトラブルを避けやすくなります。

まとめ

ここまでの内容をまとめます。

  • クラス変数は“クラス全体で共有”、インスタンス変数は“各インスタンスごと”。
  • 継承すると、子クラスは親のクラス変数・メソッドを引き継ぎますが、同名のクラス変数を定義すれば子クラス側が優先されます(オーバーライド)。
  • Pythonは属性を「インスタンス → クラス → 親クラス…」の順で探します(MRO)。だから、インスタンスに同名の属性を作るとクラス変数は隠れます。
  • ミュータブルな値をクラス変数にすると全インスタンスで共有されるため、意図しない連動に注意しましょう。

「この値は全員同じでいいのか?それとも1つ1つ違うのか?」――この問いかけを常に意識すると、クラス変数とインスタンス変数、そして継承の使い分けが自然と身につきます。最初は小さなサンプルから、少しずつ手を動かして確かめてみましょう。

出力結果: