クラスの属性とメソッド:データと操作をまとめる#

はじめに#

前回の記事では、クラスの基本的な概念とオブジェクト指向プログラミングの考え方について学びました。クラスを使うと、データ(属性)と機能(メソッド)をひとまとめにできることがわかりました。今回は、クラスの属性とメソッドについてさらに詳しく掘り下げていきます。

クラスにはインスタンス属性とクラス属性、インスタンスメソッドとクラスメソッドという種類があります。これらの違いと使い方を理解することで、より柔軟で効率的なプログラムを書けるようになります。

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

属性(データ)には、大きく分けて「インスタンス属性」と「クラス属性」の2種類があります。

インスタンス属性#

インスタンス属性は、各オブジェクト(インスタンス)がそれぞれ持つデータです。前回見た例では、商品の名前や価格、在庫数などがインスタンス属性でした。インスタンス属性は通常、__init__メソッドの中でself.属性名 = 値の形で定義します。

>
class Student:
    def __init__(self, name, grade):
        # インスタンス属性を定義
        self.name = name    # 名前
        self.grade = grade  # 学年
        self.scores = []    # 成績リスト(空のリストで初期化)

# 学生オブジェクトを2つ作成
student1 = Student("田中", 2)
student2 = Student("佐藤", 3)

# それぞれの属性にアクセス
print(f"{student1.name}さんは{student1.grade}年生です。")
print(f"{student2.name}さんは{student2.grade}年生です。")
田中さんは2年生です。
佐藤さんは3年生です。

この例では、namegradescoresがインスタンス属性です。各学生オブジェクトは、自分自身の名前、学年、成績リストを持っています。一つのオブジェクトの属性を変更しても、他のオブジェクトには影響しません。

クラス属性#

クラス属性は、クラス全体で共有されるデータです。クラスの中で、メソッドの外側に定義します。すべてのオブジェクトで共通の値を使いたい場合に便利です。

>
class Student:
    # クラス属性を定義
    school_name = "山田学園"

    def __init__(self, name, grade):
        # インスタンス属性
        self.name = name
        self.grade = grade

    def introduce(self):
        print(f"私は{Student.school_name}{self.name}です。{self.grade}年生です。")

# 学生オブジェクトを作成
student1 = Student("田中", 2)
student1.introduce()

# もう一人作成
student2 = Student("佐藤", 3)
student2.introduce()

# クラス属性にアクセス
print(f"学校名: {Student.school_name}")
私は山田学園の田中です。2年生です。
私は山田学園の佐藤です。3年生です。
学校名: 山田学園

この例では、school_nameがクラス属性です。学校名はすべての生徒で共通であるため、クラス属性として定義するのが適切です。クラス属性は、クラス名.属性名の形でアクセスします。

インスタンス属性とクラス属性の違いを表にまとめると以下のようになります。

種類定義場所アクセス方法特徴
インスタンス属性__init__内などself.属性名オブジェクトごとに独立
クラス属性クラス内(メソッド外)クラス名.属性名すべてのオブジェクトで共有

クラス属性はクラス内でself.属性名でも参照できますが、いつでもクラス名.属性名を使うことをおすすめします。selfを使うと、インスタンス属性とクラス属性が混同される可能性があります。クラス属性はクラス全体で共有されるため、クラス名.属性名を使うことで、より明確に意図を示すことができます。

インスタンスメソッド・クラスメソッド・スタティックメソッド#

メソッドにも、いくつかの種類があります。主なものは「インスタンスメソッド」と「クラスメソッド」「スタティックメソッド」です。

インスタンスメソッド#

これまで見てきた通常のメソッドは「インスタンスメソッド」です。最初の引数にselfを取り、特定のオブジェクトに対して操作を行います。

>
class Circle:
    def __init__(self, radius):
        self.radius = radius

    # インスタンスメソッド
    def calculate_area(self):
        return 3.14 * self.radius ** 2

    # インスタンスメソッド
    def calculate_circumference(self):
        return 2 * 3.14 * self.radius

# 円のオブジェクトを作成
circle1 = Circle(5)
circle2 = Circle(7)

# メソッドを呼び出す
area1 = circle1.calculate_area()
area2 = circle2.calculate_area()

print(f"半径5cmの円の面積: {area1:.2f}平方cm")
print(f"半径7cmの円の面積: {area2:.2f}平方cm")
半径5cmの円の面積: 78.50平方cm
半径7cmの円の面積: 153.86平方cm

クラスメソッド#

クラスメソッドは、クラス全体に関わる操作を行うためのメソッドです。@classmethodというデコレータを使って定義し、最初の引数にcls(クラス自身)を取ります。クラスメソッドは、クラス名.メソッド名の形で呼び出せます。

デコレータはメソッドなどに追加の処理を付与するための構文です。ここでは、メソッドの前に書く@から始まる特別なコードということだけ覚えておきましょう。

>
class Student:
    # クラス属性
    school_name = "山田学園"

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

    # インスタンスメソッド
    def introduce(self):
        print(f"私は{self.school_name}{self.name}です。{self.grade}年生です。")

    # クラスメソッド
    @classmethod
    def change_school_name(cls, new_name):
        # クラスメソッドではクラス属性は cls.school_name でアクセスできる
        cls.school_name = new_name  # Student.school_name = new_name と同じ
        print(f"学校名を{cls.school_name}に変更しました。")

# まずはインスタンスを作る
student1 = Student("田中", 2)
student2 = Student("佐藤", 3)

# インスタンスメソッドを呼び出す
student1.introduce()
student2.introduce()

# クラスメソッドを呼び出す
Student.change_school_name("鈴木学園")

# 変更後のクラス属性はすべてのオブジェクトに影響する
student1.introduce()
student2.introduce()
私は山田学園の田中です。2年生です。
私は山田学園の佐藤です。3年生です。
学校名を鈴木学園に変更しました。
私は鈴木学園の田中です。2年生です。
私は鈴木学園の佐藤です。3年生です。

この例では、change_school_nameがクラスメソッドです。クラス属性であるschool_nameを変更・取得するために使われています。クラスメソッドはクラスに関連する操作に使用し、clsを通じてクラス自体にアクセスします。

スタティックメソッド#

スタティックメソッドは、クラスやオブジェクトに関連しますが、クラス属性やインスタンス属性にアクセスする必要がない場合に使います。@staticmethodデコレータを使って定義し、特別な最初の引数(selfcls)は必要ありません。

>
class MathHelper:
    # クラス属性
    pi = 3.14159

    # スタティックメソッド
    @staticmethod
    def is_even(number):
        return number % 2 == 0

    # スタティックメソッド
    @staticmethod
    def absolute(number):
        if number < 0:
            return -number
        return number

    # インスタンスメソッド
    def calculate_circle_area(self, radius):
        return MathHelper.pi * radius ** 2

    # クラスメソッド
    @classmethod
    def get_pi(cls):
        return cls.pi

# スタティックメソッドはクラス名から直接呼び出せる
print(f"10は偶数ですか?: {MathHelper.is_even(10)}")
print(f"7は偶数ですか?: {MathHelper.is_even(7)}")
print(f"-5の絶対値: {MathHelper.absolute(-5)}")

# インスタンスを作成
math_helper = MathHelper()

# インスタンスメソッドを呼び出す
area = math_helper.calculate_circle_area(5)
print(f"半径5の円の面積: {area:.2f}")

# クラスメソッドを呼び出す
print(f"円周率: {MathHelper.get_pi()}")
10は偶数ですか?: True
7は偶数ですか?: False
-5の絶対値: 5
半径5の円の面積: 78.54
円周率: 3.14159

この例では、is_evenabsoluteがスタティックメソッドです。これらのメソッドは、クラスやオブジェクトの状態に依存せず、単に引数に対して計算を行うだけです。

3種類のメソッドの違いを表にまとめると以下のようになります。

種類定義方法第一引数特徴
インスタンスメソッド通常の定義self(インスタンス)インスタンス属性にアクセス可能
クラスメソッド@classmethodcls(クラス)クラス属性にアクセス可能
スタティックメソッド@staticmethodなし(通常の引数)クラス/インスタンス属性にはアクセスしない

まとめ#

この記事では、クラスの属性とメソッドについて詳しく学びました。

  • 属性の種類
    • インスタンス属性:各オブジェクトが独自に持つデータ
    • クラス属性:クラス全体で共有されるデータ
  • メソッドの種類
    • インスタンスメソッドselfを使って特定のオブジェクトに対して操作を行う
    • クラスメソッド@classmethodデコレータとcls引数を使い、クラス全体に関わる操作を行う
    • スタティックメソッド@staticmethodデコレータを使い、クラスやオブジェクトの状態に依存しない操作を行う

これらの概念を理解し使いこなすことで、より整理されたクラス設計ができるようになります。