コンテンツにスキップ

クラスとオブジェクト

この章で学ぶこと

  • クラスとオブジェクト(インスタンス)の概念
  • クラスの定義方法(class__init__self
  • インスタンス変数とメソッド
  • クラス変数とインスタンス変数の違い
  • 継承の基本
  • 特殊メソッド(__str__
  • クラスに関するよくある間違い

クラスとは

クラス(class)は、データ(属性)とそのデータに対する操作(メソッド)をひとまとめにした「設計図」です。クラスから生成された実体をオブジェクト(またはインスタンス)と呼びます。

たとえば、「学生」というクラスを考えると:

  • 属性(データ): 名前、年齢、学籍番号
  • メソッド(操作): 自己紹介する、成績を登録する

クラスは設計図であり、そこから「太郎」「花子」といった具体的なオブジェクトを作成します。


クラスの定義

最もシンプルなクラス

class Student:
    pass  # 何もしないクラス(プレースホルダ)

# クラスからオブジェクト(インスタンス)を生成
s = Student()
print(type(s))  # <class '__main__.Student'>

__init__ メソッド(コンストラクタ)

__init__ は、オブジェクトが生成されるときに自動的に呼ばれる特殊なメソッドです。オブジェクトの初期化を行います。

class Student:
    def __init__(self, name, age):
        self.name = name  # インスタンス変数に値を設定
        self.age = age

# オブジェクトの生成(__init__ が自動的に呼ばれる)
s1 = Student("太郎", 20)
s2 = Student("花子", 21)

print(s1.name)  # 太郎
print(s2.age)   # 21

よくある間違い

__init__ のスペルを間違える:

# 間違い: アンダースコアが 1 つしかない、またはスペルミス
class Student:
    def _init_(self, name):   # アンダースコアが 1 つずつ → 普通のメソッド
        self.name = name

    def __int__(self, name):  # init ではなく int → 全く別のメソッド
        self.name = name

# 正しい: アンダースコアは 2 つずつ
class Student:
    def __init__(self, name):  # __init__(前後に 2 つずつ)
        self.name = name

上記の間違いをすると、エラーは出ないがオブジェクト生成時に初期化が行われず、属性にアクセスしようとしたときに AttributeError になります。

self とは

self は、メソッドの第 1 引数に必ず置かれるもので、そのメソッドを呼び出したオブジェクト自身を指します。

class Student:
    def __init__(self, name):
        # self.name は「このオブジェクトの name 属性」を意味する
        self.name = name

    def greet(self):
        # self を通じてオブジェクトの属性にアクセスする
        print(f"こんにちは、私は{self.name}です")

s = Student("太郎")
s.greet()  # こんにちは、私は太郎です

self の仕組み

s.greet() と呼び出すと、Python は内部的に Student.greet(s) として実行します。つまり、self にはメソッドを呼び出したオブジェクト s が自動的に渡されます。呼び出し時に self を明示的に渡す必要はありません。

よくある間違い

メソッド定義で self を忘れる:

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

    # 間違い: self がない
    # def greet():
    #     print(f"こんにちは、{self.name}")
    # s.greet() → TypeError: greet() takes 0 positional arguments but 1 was given

    # 正しい: self を第 1 引数にする
    def greet(self):
        print(f"こんにちは、{self.name}")

self. をつけずにインスタンス変数に代入する:

class Student:
    def __init__(self, name, age):
        self.name = name
        age = age  # 間違い! self.age ではなくローカル変数 age に代入している

s = Student("太郎", 20)
print(s.name)  # 太郎
# print(s.age) # AttributeError: 'Student' object has no attribute 'age'

インスタンス変数とメソッド

インスタンス変数

インスタンス変数は、各オブジェクトが個別に持つデータです。self.変数名 でアクセスします。

class Student:
    def __init__(self, name, student_id):
        self.name = name              # インスタンス変数
        self.student_id = student_id  # インスタンス変数
        self.scores = []              # 空のリストで初期化

# 各オブジェクトは独立したデータを持つ
s1 = Student("太郎", "S001")
s2 = Student("花子", "S002")

s1.scores.append(85)
s2.scores.append(92)

print(s1.scores)  # [85](s1 のデータ)
print(s2.scores)  # [92](s2 のデータ、s1 とは独立)

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

クラス変数はクラス全体で共有され、インスタンス変数は各オブジェクトが個別に持ちます。

class Student:
    # クラス変数: すべてのインスタンスで共有
    school_name = "Python大学"
    count = 0

    def __init__(self, name):
        # インスタンス変数: 各オブジェクトが個別に持つ
        self.name = name
        Student.count += 1  # クラス変数を更新

s1 = Student("太郎")
s2 = Student("花子")

# クラス変数はすべてのインスタンスから参照できる
print(s1.school_name)  # Python大学
print(s2.school_name)  # Python大学
print(Student.count)   # 2

よくある間違い

クラス変数とインスタンス変数の混同:

class Student:
    scores = []  # クラス変数(すべてのインスタンスで共有される!)

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

    def add_score(self, score):
        self.scores.append(score)  # クラス変数を変更してしまう

s1 = Student("太郎")
s2 = Student("花子")

s1.add_score(85)
s2.add_score(92)

print(s1.scores)  # [85, 92](s2 のスコアも含まれてしまう!)
print(s2.scores)  # [85, 92](同じリストを共有している)

# 正しい: リストはインスタンス変数として __init__ で初期化する
class Student:
    def __init__(self, name):
        self.name = name
        self.scores = []  # インスタンス変数(各オブジェクトで独立)

    def add_score(self, score):
        self.scores.append(score)

s1 = Student("太郎")
s2 = Student("花子")
s1.add_score(85)
s2.add_score(92)
print(s1.scores)  # [85]
print(s2.scores)  # [92]

メソッド

メソッドは、クラスの中に定義された関数です。オブジェクトのデータを操作するために使います。

class Student:
    def __init__(self, name, student_id):
        self.name = name
        self.student_id = student_id
        self.scores = []

    def add_score(self, score):
        """成績を追加する"""
        self.scores.append(score)

    def get_average(self):
        """成績の平均を計算して返す"""
        if len(self.scores) == 0:
            return 0
        return sum(self.scores) / len(self.scores)

    def show_info(self):
        """学生の情報を表示する"""
        avg = self.get_average()  # 自分自身の別のメソッドを呼び出す
        print(f"[{self.student_id}] {self.name} - 平均: {avg:.1f}点")

# 使用例
s = Student("太郎", "S001")
s.add_score(85)
s.add_score(72)
s.add_score(90)
s.show_info()  # [S001] 太郎 - 平均: 82.3点

よくある間違い

メソッドを呼び出すときに () を忘れる:

s = Student("太郎", "S001")

# 間違い: () がないとメソッドオブジェクトへの参照になる
s.show_info     # 何も起きない
print(s.show_info)  # <bound method Student.show_info of ...>

# 正しい: () をつけて呼び出す
s.show_info()   # [S001] 太郎 - 平均: 0.0点

実行例

>>> class Dog:
...     def __init__(self, name, breed):
...         self.name = name
...         self.breed = breed
...     def bark(self):
...         print(f"{self.name}: ワンワン!")
...
>>> d = Dog("ポチ", "柴犬")
>>> d.name
'ポチ'
>>> d.breed
'柴犬'
>>> d.bark()
ポチ: ワンワン
>>> type(d)
<class '__main__.Dog'>

実践例: 銀行口座クラス

class BankAccount:
    """銀行口座を表すクラス"""

    def __init__(self, owner, balance=0):
        self.owner = owner      # 口座名義
        self.balance = balance  # 残高(デフォルト 0)

    def deposit(self, amount):
        """入金する"""
        if amount <= 0:
            print("入金額は正の数を指定してください")
            return
        self.balance += amount
        print(f"{amount}円を入金しました(残高: {self.balance}円)")

    def withdraw(self, amount):
        """出金する"""
        if amount <= 0:
            print("出金額は正の数を指定してください")
            return
        if amount > self.balance:
            print("残高が不足しています")
            return
        self.balance -= amount
        print(f"{amount}円を出金しました(残高: {self.balance}円)")

    def get_balance(self):
        """残高を取得する"""
        return self.balance

# 使用例
account = BankAccount("太郎", 10000)
account.deposit(5000)    # 5000円を入金しました(残高: 15000円)
account.withdraw(3000)   # 3000円を出金しました(残高: 12000円)
account.withdraw(20000)  # 残高が不足しています
print(f"現在の残高: {account.get_balance()}円")  # 現在の残高: 12000円

特殊メソッド: __str__

__str__ メソッドを定義すると、print() でオブジェクトを表示したときの文字列表現をカスタマイズできます。

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

    def __str__(self):
        return f"Student({self.name}, {self.student_id})"

s = Student("太郎", "S001")

# __str__ が定義されていない場合
# print(s)  # <__main__.Student object at 0x...>(読みにくい)

# __str__ が定義されている場合
print(s)    # Student(太郎, S001)

# str() 関数でも呼ばれる
text = str(s)
print(text)  # Student(太郎, S001)

主要な特殊メソッド

Python にはこの他にも多くの特殊メソッド(ダンダーメソッド)があります。

メソッド 呼ばれる場面
__init__ オブジェクト生成時 Student("太郎")
__str__ print()str() print(s)
__repr__ 開発者向け文字列表現 REPLでの表示
__len__ len() len(obj)
__eq__ == obj1 == obj2
__lt__ < obj1 < obj2

これらは発展的な内容なので、必要になったときに学べば十分です。


継承

継承(inheritance)とは、既存のクラスの機能を引き継いで新しいクラスを作る仕組みです。元のクラスを親クラス(基底クラス)、新しいクラスを子クラス(派生クラス)と呼びます。

基本的な継承

# 親クラス
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name}が鳴きました")

# 子クラス(Animal を継承)
class Dog(Animal):
    def speak(self):
        print(f"{self.name}: ワンワン!")

class Cat(Animal):
    def speak(self):
        print(f"{self.name}: ニャー!")

# 使用例
dog = Dog("ポチ")
cat = Cat("タマ")

dog.speak()  # ポチ: ワンワン!
cat.speak()  # タマ: ニャー!

メソッドのオーバーライドと super()

子クラスで親クラスと同じ名前のメソッドを定義すると、オーバーライド(上書き)になります。親クラスのメソッドを呼び出したい場合は super() を使います。

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

    def info(self):
        return f"{self.name}{self.age}歳)"

class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)  # 親クラスの __init__ を呼び出す
        self.breed = breed           # 子クラス独自の属性を追加

    def info(self):
        # 親クラスの info() を呼び出し、さらに情報を追加
        base_info = super().info()
        return f"{base_info} - 犬種: {self.breed}"

dog = Dog("ポチ", 3, "柴犬")
print(dog.info())  # ポチ(3歳) - 犬種: 柴犬

よくある間違い

子クラスの __init__super().__init__() を呼び忘れる:

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

# 間違い: super().__init__() を呼んでいない
class Dog(Animal):
    def __init__(self, name, breed):
        # super().__init__(name) を忘れている!
        self.breed = breed

d = Dog("ポチ", "柴犬")
# print(d.name)  # AttributeError: 'Dog' object has no attribute 'name'

# 正しい:
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # 親クラスの初期化を呼ぶ
        self.breed = breed

d = Dog("ポチ", "柴犬")
print(d.name)   # ポチ
print(d.breed)  # 柴犬

継承の使いどころ

継承は「A は B の一種である」(is-a 関係)が成り立つ場合に使います。

  • Dog is an Animal(犬は動物の一種): 適切な継承
  • Student is a Person(学生は人の一種): 適切な継承

無理に継承を使う必要はありません。まずはクラスの基本を理解し、必要に応じて継承を活用してください。


実践例: クラスを活用したプログラム

class Subject:
    """科目を表すクラス"""

    def __init__(self, name, credit):
        self.name = name      # 科目名
        self.credit = credit  # 単位数
        self.score = None     # 成績(未登録は None)

    def set_score(self, score):
        """成績を登録する"""
        if 0 <= score <= 100:
            self.score = score
        else:
            print("成績は 0 から 100 の範囲で入力してください")

    def __str__(self):
        score_str = f"{self.score}点" if self.score is not None else "未登録"
        return f"{self.name}{self.credit}単位): {score_str}"


class Student:
    """学生を表すクラス"""

    def __init__(self, name, student_id):
        self.name = name
        self.student_id = student_id
        self.subjects = []  # 履修科目のリスト

    def enroll(self, subject):
        """科目を履修登録する"""
        self.subjects.append(subject)

    def get_gpa(self):
        """GPA を計算する(簡易版: 成績 / 100 * 4.0)"""
        scored = [s for s in self.subjects if s.score is not None]
        if not scored:
            return 0.0
        total_points = sum(s.score / 100 * 4.0 * s.credit for s in scored)
        total_credits = sum(s.credit for s in scored)
        return total_points / total_credits

    def show_transcript(self):
        """成績表を表示する"""
        print(f"=== 成績表: {self.name}{self.student_id}) ===")
        for subject in self.subjects:
            print(f"  {subject}")
        print(f"  GPA: {self.get_gpa():.2f}")


# 使用例
math = Subject("数学", 2)
english = Subject("英語", 2)
physics = Subject("物理", 2)

math.set_score(85)
english.set_score(72)
physics.set_score(90)

student = Student("太郎", "S001")
student.enroll(math)
student.enroll(english)
student.enroll(physics)
student.show_transcript()

出力:

=== 成績表: 太郎(S001) ===
  数学(2単位): 85点
  英語(2単位): 72点
  物理(2単位): 90点
  GPA: 3.29

よくある間違いのまとめ

よくある間違い

1. self を忘れる:

class MyClass:
    # 間違い: self がない
    # def greet():
    #     print("Hello")
    # obj.greet() → TypeError

    # 正しい:
    def greet(self):
        print("Hello")

2. __init__ のスペルミス:

class MyClass:
    # 間違い: アンダースコアが足りない
    # def _init_(self):
    # def __int__(self):

    # 正しい:
    def __init__(self):
        pass

3. self. をつけ忘れて属性を保存できない:

class MyClass:
    def __init__(self, value):
        value = value  # ローカル変数への代入(インスタンスに保存されない)
        # 正しくは: self.value = value

4. クラス変数にミュータブルな値を使って全インスタンスで共有される:

class MyClass:
    data = []  # クラス変数 → 全インスタンスで共有される
    # 正しくは __init__ 内で self.data = [] とする

5. メソッド呼び出しで () を忘れる:

obj = MyClass()
obj.method    # メソッドオブジェクトへの参照(実行されない)
obj.method()  # 正しい: メソッドの実行

6. 子クラスで super().init() を呼び忘れる:

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

class Child(Parent):
    def __init__(self, name, age):
        # super().__init__(name) を忘れると self.name が設定されない
        super().__init__(name)  # 必ず呼ぶ
        self.age = age

まとめ

  • クラスはデータ(属性)と操作(メソッド)をまとめた設計図であり、クラスから生成した実体をオブジェクト(インスタンス)と呼ぶ
  • __init__ はオブジェクト生成時に自動で呼ばれる初期化メソッド。スペルに注意(前後にアンダースコア 2 つずつ)
  • self はメソッド内で自分自身のオブジェクトを参照するための引数。すべてのメソッドの第 1 引数に必要
  • インスタンス変数self.変数名)は各オブジェクトが個別に持つデータ。クラス変数は全インスタンスで共有されるため、ミュータブルな値には使わないこと
  • __str__ を定義すると、print() でオブジェクトをわかりやすく表示できる
  • 継承を使うと、既存のクラスの機能を引き継いだ新しいクラスを作れる
  • 親クラスのメソッドを呼び出すには super() を使う。子クラスの __init__ では super().__init__() を忘れないこと