【徹底解説】Pythonのクラス継承やクラス変数などをわかりやすく|クラスの使い方も解説【Python入門・応用21】

こんにちは。キノコードです。
この動画では、Pythonのクラスについて説明します。
Python超入門コースでは、インスタンスやコンストラクタなど、クラスの基本的な概念について説明しました。
この動画では、もう少し複雑なクラスの概念について説明します。

デストラクタ

まず、デストラクタについて説明します。
以前紹介したコンストラクタは、インスタンスを作成し、初期化するときに呼び出されるメソッドです。
一方、デストラクタはインスタンスが不要になり、削除されるときに呼び出されるメソッドです。
具体例で確認してみましょう。

class Student:

    def __init__(self, name):
        self.name = name

    def __del__(self):
        print("DELETE!!")

person = Student("Yamada")
print(person.name)

まず、クラスを定義します。
classと書いて、クラス名、そして:(コロン)を書きます。
今回、クラス名はStudentとします。
次にコンストラクタを定義します。
インデントを下げて、def、init、丸括弧、そして:(コロン)を書きます。
丸括弧内の仮引数はselfとnameとします。
このように、コンストラクタはinitという名前のメソッドで定義します。
そして、nameというアトリビュートに仮引数のnameを代入しましょう。
self、ドット、name、=(イコール)、nameと書きます。
次にデストラクタを定義します。
def、del、丸括弧、そして:(コロン)を書きます。
丸括弧内の仮引数はselfとします。
このようにデストラクタはdelという名前のメソッドで定義します。
そして、print関数でDELETE!!と表示させましょう。
これでクラスの定義は完成です。
次に、定義したクラスをインスタンス化し、変数personに代入します。
引数のnameには"Yamada"と渡します。
そして、アトリビュートのnameを表示してみましょう。
実行します。

Yamada、そしてDELETE!!と表示されました。
このように、クラス内にデストラクタを定義すると、プログラムが終了してインスタンスが不要になったときに呼び出されます。

class Student:

    def __init__(self, name):
        self.name = name

    def __del__(self):
        print("DELETE!!")

person = Student("Yamada")
print(person.name)
del person
print("--------------------")

また、del文を使ってインスタンスを削除し、デストラクタを呼び出すこともできます。
del文は宣言したオブジェクトを削除することができます。
先ほどはプログラムを終了することで、自動的にインスタンスが削除されました。
今度はdel文を使って明示的にインスタンスを削除してみましょう。
先ほど書いたコードの後に、del、personと書きます。
これで変数personに代入されたオブジェクトを削除できます。
また、デストラクタがどこで呼び出されたかわかりやすいように、最後にハイフンを表示させましょう。
実行します。

Yamada、DELETE!!、そして最後にハイフンが表示されました。
プログラム終了後ではなく、del文で宣言したときにデストラクタが呼び出されていることがわかります。
このように、del文を使って明示的にオブジェクトを削除し、デストラクタを呼び出すことができます。

class Student:

    def __init__(self, name):
        self.name = name

    def __del__(self):
        print("DELETE!!")

person = Student("Yamada")
print(person.name)
del person
print(person.name)

インスタンスを削除した後は、インスタンスのメソッドやアトリビュートを使うことはできません。
del文でインスタンスを削除した後に、アトリビュートのnameを表示してみましょう。
実行します。

このように、NameErrorが発生します。

class Student:

    def __init__(self, name):
        self.name = name

    def __del__(self):
        print("DELETE!!")

person = Student("Yamada")
person2 = person
del person
print("--------------------")

なお、del文を使うと必ずデストラクタが呼び出されるわけではありません。
インスタンスを参照しているところがなくなり、不要になった時点でデストラクタが呼び出されます。
確認してみましょう。
インスタンスが代入された変数personを変数person2に代入します。
次に、del文でpersonを削除します。
そして、わかりやすいように最後にハイフンを表示させましょう。
実行します。

ハイフンが表示された後にDELETE!!と表示されています。
すなわち、del文で宣言したときはデストラクタは呼び出されていません。
del文でpersonが削除された後も、インスタンスはperson2で参照されています。
このような場合、del文を使ったときにデストラクタは呼び出されないので注意しましょう。

クラスの継承

次に、クラスの継承について説明します。
クラスの継承とは、あるクラスを基本として、そこから更に別の機能を持つクラスを作ることです。
基本となる継承元のクラスのことを親クラス、またはスーパークラスと言います。
そこから派生した継承先のクラスのことを子クラス、またはサブクラスと言います。
クラスの継承を行う際、基本的な機能は親クラスで定義します。
子クラスには必要な部分だけを書き換えたり、追加したりします。
こうすることで、同じ処理をするコードを重複して書く必要がなくなり、コードがシンプルになります。
具体例で確認してみましょう。

class Student:
    def math(self, score):
        print(score)

class Grade1:
    def math(self, score):
        print(score)

    def english(self, score):
        print(score)

studentA = Student()
studentB = Grade1()
studentA.math(50)
studentB.math(60)
studentB.english(70)

例として、中学生を表すクラスを作成しましょう。
そして、受けたテストの点数を表示するメソッドを定義します。
学生は全員数学のテストを受けているとします。
そして、1年生は更に英語のテストも受けているとします。
まず、クラスの継承を行わずにコードを書いてみましょう。
学生全体を表すStudentクラスを作成します。
mathというメソッドを定義して、点数を表示する処理を書きます。
次に、1年生を表すクラスを作成します。
同様にmathというメソッドを定義して、点数を表示する処理を書きます。
更に英語のテストも受けているので、englishというメソッドも定義し、点数を表示する処理を書きます。
これでクラスの定義は完成です。
Studentのインスタンスを作成し、studentAに代入します。
また、Grade1のインスタンスを作成し、studentBに代入します。
そして、studentAのmathメソッドの引数に50を渡し、数学の点数を表示してみましょう。
また、studetBも同様に、数学と英語の点数を表示してみましょう。
実行します。

studentAの数学の点数、studentBの数学と英語の点数が表示されました。
しかし、mathメソッドはStudentクラスもGrade1クラスも同じことを書いています。
もし、同じようにGrade2クラスやGrade3クラスも作成する場合、毎回mathメソッドを書く必要があります。

class Student:
    def math(self, score):
        print(score)

class Grade1(Student):
    def english(self, score):
        print(score)

studentA = Student()
studentB = Grade1()
studentA.math(50)
studentB.math(60)
studentB.english(70)

そこでクラスの継承を使います。
クラスの継承を使うと、親クラスで定義したメソッドを子クラスでも使うことができます。
今回はStudentクラスを親クラス、Grade1クラスを子クラスとします。
先ほど定義したGrade1クラスに丸括弧を書き、引数にStudentクラスを渡します。
これでクラスの継承ができます。
従って、Grade1でmathメソッドを定義しなくても、mathメソッドを使うことができます。
Grade1のmathメソッドを削除しましょう。
そして、先ほどと同様にそれぞれのインスタンスを作成します。
mathメソッドとenglishメソッドを使って点数を表示してみましょう。
実行します。

先ほどと同様に、点数が表示されました。
StudentBではmathメソッドは定義されていませんが、mathメソッドを使うことができています。
更に、子クラスで定義したenglishメソッドも使うことができています。
このように、クラスの継承を使うことで同じコードを書く必要がなくなり、コードが読みやすくなります。

class Student:
    def math(self, score):
        print(score)

class Grade1(Student):
    def english(self, score):
        print(score)

studentA = Student()
studentB = Grade1()
studentA.math(50)
studentB.math(60)
studentB.english(70)
studentA.english(80)

なお、子クラスで定義されたメソッドを親クラスで使うことはできません。
先ほどのコードの最後に、studentAのenglishメソッドを書いてみましょう。
実行します。

このようにエラーが発生します。
studentAは親クラスのインスタンスなので、子クラスで定義されたメソッドは使えません。
注意しましょう。

メソッドのオーバーライド

次に、メソッドのオーバーライドについて説明します。
メソッドのオーバーライドとは、親クラスで定義したメソッドを子クラスで改めて定義することです。
これによって、親クラスで定義したメソッドは上書きされます。
具体例で確認してみましょう。

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def math(self, score):
        print(score)

class Grade1(Student):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print("Name :", self.name)
        print(" Age :", self.age)

    def english(self, score):
        print(score)

studentA = Student("Yamada", 15)
studentB = Grade1("Sato", 12)

先ほど定義したStudentクラスにコンストラクタを追加します。
コンストラクタは初期化メソッドとも呼ばれ、メソッドの一部です。
仮引数はself、name、ageとします。
そしてname、ageというアトリビュートに仮引数のname、ageを代入します。
次に、Grade1クラスにもコンストラクタを追加します。
子クラスに親クラスと同じ名前のメソッドを追加することで、メソッドのオーバーライドができます。
Studentクラスと同じように書き、更にprint関数でnameとageを表示する処理を書きましょう。
これでメソッドのオーバーライドができました。
Studentインスタンスを作成し、StudentAに代入します。
引数にはYamada、15を渡します。
そして、同様にGrade1インスタンスを作成し、StudentBに代入します。
今度は引数にSato、12を渡します。
結果を確認してみましょう。
実行します。

studentBのnameとageが表示されました。
親クラスのコンストラクタには、nameとageを表示する処理は書かれていません。
しかし、メソッドのオーバーライドによって、子クラスではコンストラクタが上書きされてnameとageが表示されます。
ただし、name、ageというアトリビュートに仮引数のname、ageを代入する部分は同じことを書いています。

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def math(self, score):
        print(score)

class Grade1(Student):
    def __init__(self, name, age):
        print("Name :", self.name)
        print(" Age :", self.age)

    def english(self, score):
        print(score)

studentA = Student("Yamada", 15)
studentB = Grade1("Sato", 12)

そこで、今度は子クラスのコンストラクタでアトリビュートを定義する処理を削除しました。
nameとageを表示する部分だけを追加しています。
こうするとどうなるか、結果を確認してみましょう。
実行します。

エラーが発生しました。
Grade1クラスはStudentクラスを継承しているので、Studentクラスのアトリビュートを使えるはずです。
しかし、コンストラクタをオーバーライドしたときに親クラスのコンストラクタが上書きされます。
子クラスのコンストラクタでは、アトリビュートを定義していないので、このようなエラーが発生します。

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def math(self, score):
        print(score)

class Grade1(Student):
    def __init__(self, name, age):
        super().__init__(name, age)
        print("Name :", self.name)
        print(" Age :", self.age)

    def english(self, score):
        print(score)

studentA = Student("Yamada", 15)
studentB = Grade1("Sato", 12)

親クラスを継承しつつ、子クラス独自の機能を追加するときはsuper関数を使います。
super関数を使うと親クラスのメソッドを継承できます。
先ほどと同様に、Grade1クラスでコンストラクタをオーバーライドします。
その後にsuper関数を使います。
super、そして丸括弧を書きます。
super関数の引数は不要です。
そして、ドット、その後に継承したい親クラスのメソッドを書きます。
今回は親クラスのコンストラクタを継承したいので、init、そして丸括弧を書きます。
ここの仮引数では、selfは不要で、nameとageだけを書きます。
これで親クラスのコンストラクタを継承できます。
その後に、nameとageを表示する処理を書きます。
これで完成です。
結果を確認してみましょう。
実行します。

エラーが発生せずに、studentBの名前と年齢が表示されました。
このように、メソッドのオーバーライドとsuper関数を併用することで、親クラスのメソッドを継承した上で別の処理を追加できます。

クラス変数

次に、クラス変数について説明します。
クラス変数は、そのクラスから作成された全てのインスタンスで共有される変数のことです。
クラスの中で通常の変数のように定義します。
一方、インスタンス変数はそのインスタンス特有の変数のことです。
コンストラクタで定義した変数nameなどはインスタンス変数です。
具体例で確認してみましょう。

class Student:

    job = "junior high school student"

    def __init__(self, name, age):
        self.name = name
        self.age = age

studentA = Student("Yamada", 15)
print(studentA.job)
print(studentA.name)
print(studentA.age)

studentB = Student("Sato", 12)
print(studentB.job)
print(studentB.name)
print(studentB.age)

先ほど定義したStudentクラスにクラス変数を追加しましょう。
クラス変数はクラスの中で通常の変数と同じように書きます。
文字列でjunior high school studentと書き、変数jobに代入します。
コンストラクタは先ほどと同じです。
mathメソッドはもう使わないので削除しました。
これでStudentクラスの定義が完成です。
今回はjobがクラス変数、nameとageがインスタンス変数です。
そして、Studentのインスタンスを作成し、studentAに代入します。
引数はYamada、15とします。
print関数でstudentAのjob、name、ageを表示してみます。
更にもう一つStudentのインスタンスを作成し、studentBに代入します。
引数はSato、12とします。
print関数でstudentBのjob、name、ageを表示してみましょう。
実行します。

結果が表示されました。
このように、クラス変数のjobは全てのインスタンスで共有されます。
一方、インスタンス変数のnameとageはインスタンスごとに異なります。

class Student:

    job = "junior high school student"
    age = 13

    def __init__(self, name, age):
        self.name = name
        self.age = age

studentA = Student("Yamada", 15)
print(studentA.job)
print(studentA.name)
print(studentA.age)

studentB = Student("Sato", 12)
print(studentB.job)
print(studentB.name)
print(studentB.age)

また、同じ名前のクラス変数とインスタンス変数があった場合、インスタンス変数が優先されます。
先ほど定義したStudentクラスで、新たにクラス変数ageを追加し、13を代入します。
クラス変数とインスタンス変数にageがある状態にし、結果を確認してみましょう。
実行します。

studentAのageは15、studentBのageは12と表示されました。
インスタンス変数が優先されていることが確認できます。

class Student:

    job = "junior high school student"
    age = 13

    def __init__(self, name, age):
        self.name = name
        self.age = age

print(Student.job)
print(Student.age)

また、クラス変数はインスタンスを作成しなくてもアクセスできます。
クラス名、ドット、クラス変数名でアクセスできます。
このとき、クラス名の後に丸括弧を付けないように注意しましょう。
インスタンスを作成せずに、クラス変数のjobとageを表示してみましょう。
実行します。

クラス変数のjobとageが表示されました。

クラスメソッド

次に、クラスメソッドについて説明します。
クラスメソッドは、インスタンスを作成しなくても呼び出せるメソッドです。
先ほど説明したクラス変数のメソッド版です。
一方、インスタンスメソッドは、インスタンスを作成してから呼び出すメソッドです。
今までクラス内で定義したメソッドは全てインスタンスメソッドです。
具体例で確認してみましょう。

class Student:

    job = "junior high school student"
    age = 13

    @classmethod
    def add_age(cls):
        cls.age += 1
        return cls.age

    def __init__(self, name, age):
        self.name = name
        self.age = age

print(Student.add_age())

先ほど定義したStudentクラスにクラスメソッドを追加しましょう。
クラスメソッドは、まず@(アットマーク)、classmethodと書きます。
レッスン16で説明したデコレーターの書き方と同じです。
その後に普通のメソッドと同様に定義します。
add_ageというメソッドを定義しましょう。
仮引数にはclsと書きます。
インスタンスメソッドの仮引数はselfと書きましたが、クラスメソッドの仮引数はclsと書きます。
selfはインスタンス自身を表し、clsはクラス自身を表していると考えましょう。
そして、クラス変数ageに1を足します。
クラス変数なので、cls、ドット、ageと書きます。
そして、return文でそのクラス変数ageを返します。
これでクラスメソッドの定義が完成です。
それではクラスメソッドのadd_ageを呼び出し、print関数で表示してみましょう。
実行します。

14と表示されました。
最初に13が代入されていたクラス変数ageに1が追加され、14と表示されています。
このように、クラスメソッドはクラス変数と同様にインスタンスを作成しなくても呼び出すことができます。
なお、今回記述したクラスメソッドはレッスン16で説明したデコレーターの一種です。
Pythonにはもともとclassmethodという組み込み関数があります。
classmethod関数は、メソッドをクラスメソッドに変換する関数です。
今回はそのclassmethod関数でadd_age関数を修飾している、という構文になっています。
デコレーターとして意識する必要はありませんが、そのような構文になっていることを知っておきましょう。

class Student:

    job = "junior high school student"
    age = 13

    @classmethod
    def add_age(cls):
        cls.age += 1
        return cls.age

    def __init__(self, name, age):
        self.name = name
        self.age = age

studentA = Student("Yamada", 15)
print(studentA.add_age())

また、クラスメソッドはインスタンスから呼び出すこともできます。
Studentのインスタンスを作成し、studentAに代入します。
引数はYamada、15とします。
そして、インスタンスからクラスメソッドを呼び出し、print関数で表示してみましょう。
実行します。

インスタンスからクラスメソッドを呼び出すことができました。
なお、今回のクラスメソッドはクラス変数のageに1を足す処理を定義しています。
インスタンス変数のageとは別なので注意しましょう。

スタティックメソッド

最後に、スタティックメソッドについて説明します。
スタティックメソッドはクラスメソッドと同様に、インスタンスを作成しなくても呼び出せるメソッドです。
ただし、クラスメソッドやインスタンスメソッドと異なり、clsやselfなどの引数を受け取りません。
具体例で確認してみましょう。

class Student:

    job = "junior high school student"
    age = 13

    @classmethod
    def add_age(cls):
        cls.age += 1
        return cls.age

    @staticmethod
    def greeting():
        print("hello")

    def __init__(self, name, age):
        self.name = name
        self.age = age

Student.greeting()

先ほど定義したStudentクラスにスタティックメソッドを追加しましょう。
スタティックメソッドは、クラスメソッドと同様に@(アットマーク)、staticmethodと書きます。
スタティックメソッドもデコレーターの書き方と同じです。
その後に、普通のメソッドと同様に定義します。
今回はgreetingというメソッドを定義しましょう。
引数が必要な場合は仮引数を書きますが、clsやselfを書く必要はありません。
今回は引数は必要ないので、何も書きません。
そしてprint関数でhelloと表示させます。
これでスタティックメソッドの定義が完成です。
それでは、スタティックメソッドのgreetingを呼び出し、結果を表示してみましょう。
実行します。

helloと表示されました。
このように、スタティックメソッドはクラスメソッドと同様に、インスタンスを作成しなくても呼び出せます。

class Student:

    job = "junior high school student"
    age = 13

    @classmethod
    def add_age(cls):
        cls.age += 1
        return cls.age

    @staticmethod
    def greeting():
        print("hello")
        print(f"I am {Student.age}")

    def __init__(self, name, age):
        self.name = name
        self.age = age

Student.greeting()

また、スタティックメソッドはクラスメソッドと異なり、クラスが渡されません。
そのため、クラス変数を使うときはそのクラス変数のクラスを宣言する必要があります。
先ほど定義したスタティックメソッドでクラス変数のageを使いましょう。
print関数でhelloと表示した後に、print関数でクラス変数のageを表示します。
レッスン13で説明したフォーマット済み文字列を使いましょう。
ダブルクォーテーションの前にfを書きます。
そして、ダブルクォーテーションの中に、I am、そして波括弧を書きます。
波括弧の中にStudent、ドット、ageと書き、クラス変数のageを表示します。
これで完成です。
先ほどと同様に、スタティックメソッドのgreetingを呼び出し、結果を表示してみましょう。
実行します。

hello、そしてI am 13と表示されました。

class Student:

    job = "junior high school student"
    age = 13

    @classmethod
    def add_age(cls):
        cls.age += 1
        return cls.age

    def __init__(self, name, age):
        self.name = name
        self.age = age

def greeting():
    print("hello")
    print(f"I am {Student.age}")

greeting()

スタティックメソッドはクラス内の変数やメソッドに直接アクセスしません。
従って、クラスの外で定義した普通の関数と同じ機能を持つと考えられます。
そこで、先ほど定義したスタティックメソッドのデコレーター部分を外し、クラスの外でgreetingを定義します。
そして、呼び出すときもStudentクラスを外し、greeting関数をそのまま呼び出しましょう。
実行します。

先ほどと同じ結果が表示されました。
このように、スタティックメソッドはクラスの外で定義した関数と同じ機能を持ちます。
クラスの中で関数を使いたい場合など、クラスの中で定義した方がわかりやすい場合にスタティックメソッドが使われます。