ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

Python - クラス

初めに

 Pythonでのクラスの定義の仕方、アクセス制限やプロパティ、コンストラクタ、継承などについてまとめています。

クラス

 クラスは変数とメソッドを定義することができるオブジェクトの設計図のようなものです。

 クラスを定義するときは、オブジェクト指向の概念を理解することが重要になります。下の記事にオブジェクト指向についての考え方を書いております。オブジェクト指向がよくわからない場合は、まずそちらをご覧ください。

fineworks-fine.hatenablog.com

(例:従業員を表すクラス)

class Employee:
    # クラス変数
    company_name = "株式会社ニシキヘビ"

    def __init__(self, name, department):
        # インスタンス変数
        self.name = name
        self.department = department

    def introduce(self):
        # クラス変数とインスタンス変数を使用するメソッド
        print(f"私の名前は{self.name}です。{Employee.company_name}の{self.department}で働いています。")

 上の例からクラスでは次の3つを定義することができることが分かります。

クラス変数・インスタンス変数

クラス変数・インスタンス変数の違い

 クラス変数とインスタンス変数の違いは次の通りです。

項目 クラス変数 インスタンス変数
共通性 すべてのオブジェクトで共有 オブジェクトごとに独立
定義場所 クラス定義内 オブジェクト生成時
アクセス方法 クラス名.変数名 オブジェクト名.変数名

 クラス全体で共通のものはクラス変数で定義し、インスタンスごとに変更したい共通の属性はインスタンス変数で定義します。

(再掲)

class Employee:
    # クラス変数
    company_name = "株式会社ニシキヘビ"

    def __init__(self, name, department):
        # インスタンス変数
        self.name = name
        self.department = department

 先ほどの例では、従業員にとって会社名は共通のため、クラス変数でcompany_nameを定義します。従業員の名前や所属部署は全従業員に共通の属性ではありますが、その中身は従業員ごとに異なります。そのため、namedepartmentインスタンス変数で定義します。

コンストラクタ(__init__)

 __init__()メソッドはコンストラクタと呼ばれ、このクラスのインスタンスが生成されたときに呼び出されます。__init__()の第1引数selfインスタンス自身を表します。

 コンストラクタの基本的な役割はインスタンス変数の代入です。

class Employee:
   # 省略
   # コンストラクタ
  def __init__(self, name, department):
    # インスタンス変数
    self.name = name
    self.department = department

def main():
    # オブジェクトの作成
    employee1 = Employee("田中", "営業")
    employee2 = Employee("佐藤", "開発")

if __name__ == '__main__':
    main()

 上の例では、main()内でインスタンスを作成しています。引数はインスタンス自身であるselfを除いたnameとdepartmentが必要になります。

メソッド

 クラスが持つ関数をメソッドと呼びます。メソッドの第1引数もコンストラクタ同様selfインスタンス自身を指定し、第2引数以降でメソッドの引数を受け取ります。

class Employee:
    # 省略

    # メソッドの定義
    def introduce(self):
        # クラス変数とインスタンス変数を使用するメソッド
        print(f"私の名前は{self.name}です。{Employee.company_name}の{self.department}で働いています。")

def main():
    # オブジェクトの作成
    employee1 = Employee("田中", "営業")
    employee2 = Employee("佐藤", "開発")

    # メソッドの呼び出し
    employee1.introduce()
    employee2.introduce()

if __name__ == '__main__':
    main()

アクセス制限

アクセス制限の仕方

 Pythonにはpublicprivateなどのアクセス修飾子はサポートされていません。アンダーバーを用い、アクセス制限を表現します。

先頭に付けるアンダーバーの個数 意味
なし 参照できる
1個 参照できるが慣習的に外部から参照しない
2個 参照できない(AttributeError)

(例)

class MyClass:
    def __init__(self):
        self._name = "田中"

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        self._name = new_name


def main():
    human1 = MyClass()
    human1.name = "山下"
    print(f"名前:{human1.name}")

if __name__ == '__main__':
    main()

 上記の例のようにアクセス制限をするときはゲッター、セッターを使い、アクセスを制御します。

 _nameのようにアンダーバーをつけることで外部から直接参照しないことを示しています。しかし、_nameの値を取得したい場合や値を変更したい場合があります。そのときはゲッター、セッターを介して値の取得、代入を行います。

 ゲッターやセッターはプロパティと呼ばれるクラスのメンバで変数のように参照できる関数のことです。プロパティはデコレータと呼ばれる@ではじまるキーワードを使い定義されます。デコレータは関数やクラスに特殊な振る舞いを注入することができる機能です。

(再掲:定義部分)

# ゲッター
    @property
    def name(self):
        return self._name
# セッター
    @name.setter
    def name(self, new_name):
        self._name = new_name

プロパティ 説明 デコレータ
ゲッター 外部から値を取得するためのプロパティ @property
セッター 外部から値を代入するためのプロパティ @変数名.setter

 前述の通り、プロパティは関数でありながら変数のように参照することができます。

def main():
    human1 = MyClass()
    # セッターの呼び出し
    human1.name = "山下"
    # print内でゲッターを呼び出している
    print(f"名前:{human1.name}")

継承

継承とは

 Pythonでも他のオブジェクト指向言語と同様、クラスを継承することができます。クラスを継承することでサブクラスは親クラスがもつ属性やメソッドを利用することができます。

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

    def speak(self):
        raise NotImplementedError

    def eat(self):
        print(f"{self.name} は食事中")

class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)
        self.breed = breed

    def speak(self):
        print(f"{self.name} はワンワン吠えます")

    def walk(self):
        print(f"{self.name} は散歩中です")

def main():
    # オブジェクト作成
    pochi = Dog("ポチ", 3, "柴犬")

    # 継承した属性とメソッドの利用
    print(pochi.name)  # 出力:ポチ
    print(pochi.age)  # 出力:3
    pochi.speak()  # 出力:ポチはワンワン吠えます
    pochi.eat()  # 出力:ポチは食事中
    pochi.walk()  # 出力:ポチは散歩中です

if __name__ == '__main__':
    main()
継承の仕方

 Pythonでは次のように継承します。

class Animal:
    # 省略

class Dog(Animal):  # Animalクラスを継承
    # 省略
メソッドのオーバーライド

 サブクラスでは親クラスのメソッドをオーバーライド(上書き)できます。

class Animal:
    # 省略

    def speak(self):
        raise NotImplementedError

    # 省略

class Dog(Animal):
    # 省略

    def speak(self):
        print(f"{self.name} はワンワン吠えます")

    # 省略

 Animalクラスでspeck()メソッドを定義し、サブクラスであるDogクラスでオーバーライドしています。今回は、詳細をサブクラスで定義したいため、親クラスではraise NotImplementedErrorとしています。

 raiseは例外を発生させるためのキーワードです。raise NotImplementedErrorは未実装のメソッドを呼び出した際に発生する例外となっています。

super()

 super()は親クラスを参照します。

class Dog(Animal):
    def __init__(self, name, age, breed):  # サブクラスのコンストラクタ
        super().__init__(name, age)  # 親クラスを参照し初期化
        self.breed = breed
多重継承

 Pythonは多重継承がサポートされています。

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

class Voice:
    def __init__(self, sound):
        self.sound = sound

class Dog(Animal, Voice):
    def __init__(self, name):
        super().__init__(name)
        self.sound = "bow wow"

dog = Dog("Pochi")
print(dog.name, dog.sound)  # 出力: Pochi bow wow

 複数のクラスに共通する性質をもつサブクラスを作りたい場合に多重継承をします。上の例では Animal クラスと Voice クラスを継承した Dog クラスを定義しています。