【Python】クラス(class)の基礎|__init__・self・メソッド・属性・継承

【Python】クラス(class)の基礎|__init__・self・メソッド・属性・継承 Python

クラス(class)は、データ(属性)と処理(メソッド)をひとまとめにする仕組みです。たとえば「犬」をクラスにすると、名前や年齢といったデータと、「吠える」といった動作を1つにまとめられます。同じ形のものをいくつも作りたいときの「設計図」だと考えると分かりやすいです。

つまずきやすいのは、メソッドの最初に必ず書くselfと、クラス属性に[](空リスト)を使うと、すべてのインスタンスで共有されてしまう罠です。これは関数のデフォルト引数の罠と同じ仕組みで起こります。この記事では、実機のPythonで確認しながら、クラスの基礎を整理します。

先に結論

  • クラスはclass 名前:で定義し、__init__(self, ...)で初期化します。
  • selfは「そのインスタンス自身」を指し、メソッドの第1引数に必ず書きます。
  • インスタンスごとのデータはself.属性 = 値で持たせます。
  • __str__を定義すると、print()での表示を整えられます。
  • クラス直下に書いた属性は全インスタンスで共有されます。とくに[]{}は要注意です。
  • 継承はclass 子(親):。親の初期化はsuper().__init__(...)で呼びます。

メソッドは関数と同じ書き方なので関数(def)の使い方、属性に持たせるデータはリスト(list)辞書(dict)がよく使われます。あわせて参考にしてください。

スポンサーリンク

クラスの基本(class・__init__・self)

クラスはclassで定義します。__init__は「インスタンスを作るときに自動で呼ばれる」特別なメソッドで、初期値を設定します。selfは、そのインスタンス自身を指します。

basic.py
class Dog:
    def __init__(self, name, age):
        # self.属性 でインスタンスごとのデータを持たせる
        self.name = name
        self.age = age

# インスタンス(実体)を作る → __init__ が呼ばれる
pochi = Dog("ポチ", 3)

# 属性にアクセスする
print(pochi.name)   # ポチ
print(pochi.age)    # 3

# 別のインスタンスは別のデータを持つ
hachi = Dog("ハチ", 5)
print(hachi.name)   # ハチ

実機でも、Dog("ポチ", 3)で作ったインスタンスのpochi.nameポチになりました。Dogという1つの設計図から、pochihachiという別々のデータを持つインスタンスを作れます。__init__selfは、これから作られるそのインスタンスを指し、self.name = nameでデータを記録します。

メソッドを定義する

クラスの中に書く関数をメソッドと呼びます。メソッドの第1引数には必ずselfを書き、その中からself.属性で自分のデータにアクセスします。

method.py
class Dog:
    def __init__(self, name):
        self.name = name

    # メソッド(第1引数は必ず self)
    def bark(self):
        return f"{self.name}: ワン!"

    def birthday(self, current_age):
        return f"{self.name}は{current_age + 1}歳になりました"

pochi = Dog("ポチ")
print(pochi.bark())           # ポチ: ワン!
print(pochi.birthday(3))      # ポチは4歳になりました
メソッドの第1引数 self を忘れない

メソッドを定義するときは、第1引数に必ずselfを書きます。selfを通じて、そのインスタンスの属性(self.nameなど)にアクセスできます。呼び出すときはpochi.bark()のように書き、selfには自動的にpochiが渡されるため、引数として明示的に渡す必要はありませんselfを書き忘れると「引数の数が合わない」というエラーになりやすいので注意してください。

__str__で表示を整える

クラスのインスタンスをそのままprint()すると、<__main__.Dog object at 0x...>のような分かりにくい表示になります。__str__メソッドを定義すると、print()したときの見た目を自分で決められます。

str.py
class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"Dog(name={self.name}, age={self.age})"

pochi = Dog("ポチ", 3)
print(pochi)        # Dog(name=ポチ, age=3)
print(str(pochi))   # 同じ(str() でも __str__ が使われる)

実機でも、__str__を定義するとprint(pochi)Dog(name=ポチ, age=3)と分かりやすく表示されました。デバッグやログで、インスタンスの中身をひと目で確認できるようになります。

クラス属性とインスタンス属性

属性には2種類あります。self.属性で作るインスタンス属性(インスタンスごとに別)と、クラスの直下に書くクラス属性(全インスタンスで共有)です。

class-vs-instance.py
class Counter:
    count = 0          # クラス属性(全インスタンスで共有)

    def __init__(self):
        Counter.count += 1
        self.id = Counter.count   # インスタンス属性(個別)

a = Counter()
b = Counter()
c = Counter()

print(Counter.count)   # 3(共有されているので増える)
print(a.id, b.id, c.id)   # 1 2 3(それぞれ別)

実機でも、Counter.countは全インスタンスで共有されて3になり、各インスタンスのid1, 2, 3と個別になりました。「全インスタンスで共通の値(設定や総数など)」はクラス属性、「インスタンスごとに違うデータ」はインスタンス属性(self.属性)に持たせます。

【最重要】クラス属性に[]を使う共有の罠

ここがクラスでもっとも危険な落とし穴です。クラス属性に[](空リスト)や{}(空辞書)を書くと、その1つのリストがすべてのインスタンスで共有されます。1つのインスタンスで追加したつもりが、別のインスタンスにも入ってしまいます。

mutable-class-attr.py
# NG: クラス属性に [] を書くと共有される
class Box:
    items = []          # 全インスタンスで共有されてしまう

    def add(self, x):
        self.items.append(x)

box1 = Box()
box2 = Box()
box1.add("A")
print(box2.items)   # ['A'] ← box1 に入れたのに box2 にも入っている!

# OK: __init__ の中で self.属性 として作る
class Box:
    def __init__(self):
        self.items = []   # インスタンスごとに別のリスト

    def add(self, x):
        self.items.append(x)

box1 = Box()
box2 = Box()
box1.add("A")
print(box2.items)   # [] ← 独立している
変更できる値はインスタンス属性にする

実機で確認したところ、クラス属性にitems = []と書いたクラスでは、box1.add("A")したあとにbox2.items['A']になってしまいました(共有されている)。一方、__init__の中でself.items = []と書くと、box2.items[]のまま独立していました。リストや辞書のような変更できる値(ミュータブル)は、クラス属性ではなく__init__の中でインスタンス属性として作るのが鉄則です。これは関数のデフォルト引数に[]を使う罠と同じ仕組みです。数値や文字列のような変更できない値なら、クラス属性でも問題ありません。

継承(super・オーバーライド)

あるクラスをもとに、機能を引き継いだ新しいクラスを作るのが継承です。class 子(親):と書き、親の__init__super().__init__(...)で呼びます。同じ名前のメソッドを定義すると、親のものを上書き(オーバーライド)できます。

inheritance.py
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

# Animal を継承する
class Cat(Animal):
    def __init__(self, name, color):
        super().__init__(name)   # 親の __init__ を呼ぶ
        self.color = color       # 子で追加する属性

    def speak(self):             # 親のメソッドを上書き
        return "ニャー"

cat = Cat("タマ", "茶")
print(cat.name)              # タマ(親から引き継ぎ)
print(cat.color)             # 茶(子で追加)
print(cat.speak())           # ニャー(上書きしたもの)
print(isinstance(cat, Animal))   # True(Animal でもある)

実機でも、CatAnimalからnameを引き継ぎ、colorを追加し、speak()ニャーに上書きできました。isinstance(cat, Animal)Trueになることから、CatのインスタンスはAnimalでもあると分かります。super().__init__(name)を呼ばないと、親が設定するはずのnameが設定されないため、忘れないようにします。

クラスメソッド・スタティックメソッド

通常のメソッド(インスタンスメソッド)のほかに、@classmethod@staticmethodがあります。インスタンスを作らずにクラスから直接呼べるメソッドです。

classmethod-staticmethod.py
class MathUtil:
    pi = 3.14

    # クラスメソッド(第1引数は cls。クラス属性にアクセスできる)
    @classmethod
    def circle_area(cls, r):
        return cls.pi * r * r

    # スタティックメソッド(self も cls も無い。ただの関数)
    @staticmethod
    def add(a, b):
        return a + b

# インスタンスを作らずに、クラスから直接呼べる
print(MathUtil.circle_area(2))   # 12.56
print(MathUtil.add(3, 4))        # 7

実機でも、MathUtil.circle_area(2)12.56MathUtil.add(3, 4)7になり、インスタンスを作らずに呼べました。@classmethodは第1引数がcls(クラス自身)でクラス属性を使えます。@staticmethodselfclsもなく、「そのクラスに関係する便利な関数」をまとめるのに使います。最初は通常のメソッドだけ覚えれば十分です。

よくある失敗

クラス属性に[]や{}を使って共有される

変更できる値をクラス直下に書くと全インスタンスで共有されます。__init__の中でself.属性 = []として作ります。

メソッドの第1引数 self を書き忘れる

メソッドの第1引数には必ずselfを書きます。書き忘れると、引数の数が合わずエラーになります。

self を付けずに属性へアクセスする

インスタンスの属性はself.nameのようにself.を付けます。付けないと、ただのローカル変数になってしまいます。

継承でsuper().__init__()を呼び忘れる

親の__init__を呼ばないと、親が設定する属性が初期化されません。子の__init__super().__init__(...)を呼びます。

インスタンスを作らずにインスタンスメソッドを呼ぶ

通常のメソッドはインスタンス.メソッド()で呼びます。クラスから直接呼びたいなら@classmethod@staticmethodを使います。

よくある質問

Qselfとは何ですか?
Aselfは「そのインスタンス自身」を指します。メソッドの第1引数に必ず書き、self.nameのようにしてインスタンスの属性にアクセスします。呼び出すときはobj.method()と書くと、selfに自動的にobjが渡されるため、明示的に渡す必要はありません。
Qクラス属性とインスタンス属性の違いは?
Aクラス直下に書いたクラス属性は全インスタンスで共有され、__init__の中でself.属性として作るインスタンス属性はインスタンスごとに別です。リストや辞書はインスタンス属性にしないと、全インスタンスで共有されてしまいます。
Qクラス属性に空リストを使ってはいけないのですか?
Aリストや辞書のような変更できる値をクラス属性にすると、すべてのインスタンスで共有され、1つで追加したものが全部に反映されてしまいます。__init__の中でself.items = []として、インスタンスごとに作ってください。数値や文字列なら問題ありません。
Q__init__と__str__は何ですか?
A__init__はインスタンスを作るときに自動で呼ばれる初期化メソッドで、self.属性 = 値で初期値を設定します。__str__print()したときの表示を決めるメソッドで、定義すると見やすい文字列で表示できます。
Q継承で親のメソッドを呼ぶには?
Asuper()を使います。子の__init__super().__init__(引数)と書くと親の初期化を実行できます。メソッドを上書き(オーバーライド)しつつ親の処理も使いたいときも、super().メソッド名()で呼べます。

まとめ

  • クラスはclass 名前:で定義し、__init__(self, ...)で初期化します。
  • selfはインスタンス自身を指し、メソッドの第1引数に必ず書きます。
  • インスタンスごとのデータはself.属性 = 値、表示は__str__で整えます。
  • クラス属性は共有。とくに[]{}__init__で作って共有を防ぎます。
  • 継承はclass 子(親):、親の初期化はsuper().__init__(...)で呼びます。
  • インスタンス不要のメソッドは@classmethod@staticmethodです。

Pythonのクラスは、__init__selfの役割さえつかめば難しくありません。とくに「変更できる値はクラス属性ではなく__init__で作る」という1点を押さえれば、初心者がハマる共有の罠を回避できます。データと処理をまとめて、見通しのよいコードを書きましょう。