Python & AI Tutorials Logo
Python プログラミング

30. クラスとオブジェクトの導入

30.1) オブジェクト指向プログラミングの考え方(独自の型を作る)

本書を通して、これまで Python の組み込み型を扱ってきました。整数、文字列、リスト、辞書などです。各型は、データ(たとえば文字列内の文字)と、そのデータに対して実行できる操作(たとえば .upper().split())をひとまとめにしています。このデータと振る舞いの組み合わせは強力です。文字列を単なる生の文字シーケンスとしてではなく、固有の能力を持つ完全な実体として考えられるようになります。

オブジェクト指向プログラミング(object-oriented programming: OOP) はこの考え方を拡張します。問題領域に固有のデータと振る舞いをひとまとめにした、クラス(class) と呼ばれる独自のカスタム型を作れるようになります。Python がテキストを扱うための str 型や、シーケンスを扱うための list 型を提供しているのと同じように、金融取引を管理する BankAccount 型、学業成績を追跡する Student 型、在庫システムのための Product 型などを作れます。

なぜ独自の型を作るのか?

学校システムで学生の情報を管理することを考えてみましょう。クラスがなければ、別々の変数や辞書を使うかもしれません。

python
# 個別の変数を使う方法 - すぐにごちゃごちゃします
student1_name = "Alice Johnson"
student1_id = "S12345"
student1_gpa = 3.8
 
student2_name = "Bob Smith"
student2_id = "S12346"
student2_gpa = 3.5
 
# あるいは辞書を使う方法 - より良いですが、それでも制限があります
student1 = {"name": "Alice Johnson", "id": "S12345", "gpa": 3.8}
student2 = {"name": "Bob Smith", "id": "S12346", "gpa": 3.5}

このアプローチは単純なケースでは機能しますが、限界があります。

  1. バリデーションがない: gpa-5.0"excellent" のような不正な値を設定してしまうのを防げません
  2. 関連する振る舞いがない: 優等生かどうかの判定や学生情報の整形のような操作は、コード全体に散らばった別々の関数になります
  3. 型チェックができない: 学生を表す辞書は、他のどんな辞書とも同じに見えます。学生辞書が期待されているところに誤って商品辞書を使ってしまうようなミスを、Python が見つける助けになりません

クラスは、学生とは何で、学生に対してどんな操作が意味を持つのかを正確に表す新しい型を定義できるため、これらの問題を解決します。

python
# これを目指して作っていきます - データと振る舞いをひとまとめにする Student クラス
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def is_honors(self):
        return self.gpa >= 3.5
    
    def display_info(self):
        status = "Honors" if self.is_honors() else "Regular"
        return f"{self.name} ({self.student_id}) - GPA: {self.gpa} [{status}]"
 
# これで student オブジェクトを作れます
alice = Student("Alice Johnson", "S12345", 3.8)
bob = Student("Bob Smith", "S12346", 3.5)
 
print(alice.display_info())  # Output: Alice Johnson (S12345) - GPA: 3.8 [Honors]
print(bob.is_honors())       # Output: True

この章では、こうしたクラスをゼロから作る方法を学びます。まずは最もシンプルなクラスから始め、徐々に機能を追加して、リッチで便利な独自型を作れるようにしていきます。

クラスとインスタンス: 設計図のたとえ

クラス(class)インスタンス(instance) の違いを理解することは、オブジェクト指向プログラミングの基本です。

  • クラス は設計図やテンプレートのようなものです。ある種類のオブジェクトがどんなデータを持ち、どんな操作を実行できるかを定義します。クラス自体は特定の学生ではなく、「学生である」とはどういうことかの定義です。

  • インスタンスオブジェクト(object) とも呼ばれます)は、その設計図から作られた具体例です。alice = Student("Alice Johnson", "S12345", 3.8) を作るとき、Alice 固有のデータを持つ、特定の学生インスタンスを 1 つ作っています。

Student クラス
設計図

alice インスタンス
名前: Alice Johnson
ID: S12345
GPA: 3.8

bob インスタンス
名前: Bob Smith
ID: S12346
GPA: 3.5

carol インスタンス
名前: Carol Davis
ID: S12347
GPA: 3.9

1 つのクラスから必要なだけインスタンスを作れます。建築家が 1 枚の設計図を使って多くの家を建てられるのと同じです。各インスタンスはそれぞれ独自のデータ(Alice の GPA は Bob と異なる)を持ちますが、クラスが定義した同じ構造と能力を共有します。

この章で学ぶこと

この章では、Python におけるオブジェクト指向プログラミングの中核概念を紹介します。

  1. class キーワードによる クラス定義
  2. インスタンス作成 と属性へのアクセス
  3. インスタンスデータを操作する メソッド(method)の追加
  4. self の理解 と、メソッドがインスタンスデータへアクセスする方法
  5. __init__ メソッドによる インスタンス初期化
  6. __str____repr__ による 文字列表現の制御
  7. 1 つのクラスから 複数の独立したインスタンスを作る

この章の終わりまでに、プログラムをより整理しやすく、保守しやすく、表現力の高いものにする独自のカスタム型を設計・実装できるようになります。第31章では、より高度なクラス機能でこの基礎を発展させ、第32章では継承とポリモーフィズムを扱います。

30.2) class でシンプルなクラスを定義する

まずは、可能な限り最もシンプルなクラスを作りましょう。まだデータも振る舞いも持たず、新しい型を定義するだけのものです。

class キーワード

クラスは class キーワードに続けてクラス名を書き、最後にコロンを付けて定義します。

python
class Student:
    pass  # 今は空のクラスです
 
# インスタンスを作成
alice = Student()
print(alice)  # Output: <__main__.Student object at 0x...>
print(type(alice))  # Output: <class '__main__.Student'>

この最小限のクラスでも有用です。Student という新しい型を作成します。alice = Student() でインスタンスを作ると、Python は Student 型の新しいオブジェクトを作成します。出力から、alice が確かに Student オブジェクトであることが分かりますが、まだ面白いことは何もしません。

クラス名の命名規則

Python のクラス名は、CapWords または PascalCase と呼ばれる特定の規約に従います。各単語を大文字で始め、単語の間にアンダースコアは入れません。

python
class BankAccount:      # 良い: CapWords
    pass
 
class ProductInventory:  # 良い: CapWords
    pass
 
class HTTPRequest:      # 良い: 略語はすべて大文字
    pass
 
# クラスではこれらのスタイルを避けましょう:
# class bank_account:   # 悪い: snake_case は関数/変数向け
# class bankaccount:    # 悪い: 読みにくい
# class BANKACCOUNT:    # 悪い: ALL_CAPS は定数向け

この規約は、コードを読むときにクラスと関数・変数(snake_case を使う)を区別しやすくします。

インスタンスを作成する

クラスからインスタンスを作る見た目は、関数呼び出しと同じです。クラス名に続けて丸括弧を付けます。

python
class Product:
    pass
 
# 3 つの異なる product インスタンスを作成
item1 = Product()
item2 = Product()
item3 = Product()
 
# 各インスタンスは別々のオブジェクトです
print(item1)  # Output: <__main__.Product object at 0x...>
print(item2)  # Output: <__main__.Product object at 0x...>
print(item3)  # Output: <__main__.Product object at 0x...>
 
# 同じ型でも、別のオブジェクトです
print(item1 is item2)  # Output: False
print(type(item1) is type(item2))  # Output: True

Product() を呼ぶたびに、新しい独立したインスタンスが作成されます。メモリアドレス(0x... の部分)が異なり、メモリ上で別々のオブジェクトであることが確認できます。

なぜ空のクラスから始めるのか?

何もしないクラスから始める理由が気になるかもしれません。理由は 2 つあります。

  1. 概念の明確さ: クラスがデータや振る舞いとは別の「新しい型」そのものであることを理解すると、複雑さを足す前に基本概念をつかみやすくなります。

  2. 実用性: 空のクラスでも、マーカーやプレースホルダーとして役立つことがあります。たとえば、独自の例外型を定義できます。

python
class InvalidGradeError:
    pass
 
class StudentNotFoundError:
    pass
 
# これらの空クラスは、区別できるエラー型として機能します

ただし、実際のコードでは空のクラスはまれです。次はデータを追加して、クラスを有用なものにしていきましょう。

30.3) インスタンスを作成して属性にアクセスする

クラスはデータを保持できるようになると有用になります。Python では、インスタンスに紐づくデータである 属性(attribute) を、代入するだけでいつでも追加できます。

インスタンスに属性を追加する

ドット記法を使ってインスタンスに属性を追加できます。

python
class Student:
    pass
 
# インスタンスを作成
alice = Student()
 
# 属性を追加
alice.name = "Alice Johnson"
alice.student_id = "S12345"
alice.gpa = 3.8
 
# 属性にアクセス
print(alice.name)        # Output: Alice Johnson
print(alice.student_id)  # Output: S12345
print(alice.gpa)         # Output: 3.8

ドット(.)演算子で属性にアクセスします。alice.name は「alice オブジェクトの name 属性を取得する」という意味です。これは、文字列(text.upper() のような)やリスト(numbers.append(5) のような)でこれまで使ってきた構文と同じです。これらはオブジェクトのメソッドや属性にアクセスしています。

各インスタンスは固有の属性を持つ

同じクラスの異なるインスタンスは、それぞれ独立した属性を持ちます。

python
class Student:
    pass
 
# 2 人の学生を作成
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
bob = Student()
bob.name = "Bob Smith"
bob.gpa = 3.5
 
# 各インスタンスは独自のデータを持ちます
print(alice.name)  # Output: Alice Johnson
print(bob.name)    # Output: Bob Smith
 
# 片方を変更してももう片方には影響しません
alice.gpa = 3.9
print(alice.gpa)  # Output: 3.9
print(bob.gpa)    # Output: 3.5 (unchanged)

この独立性は重要です。alicebob は別々のオブジェクトで、別々のデータを持っています。alice.gpa を変更しても bob.gpa には影響しません。

属性はどんな型でもよい

属性は単純な型に限りません。どんな Python の値でも保持できます。

python
class Student:
    pass
 
student = Student()
student.name = "Carol Davis"
student.grades = [95, 88, 92, 90]  # リスト属性
student.contact = {                 # 辞書属性
    "email": "carol@example.com",
    "phone": "555-0123"
}
student.is_active = True            # 真偽値属性
 
# ネストしたデータにアクセス
print(student.grades[0])           # Output: 95
print(student.contact["email"])    # Output: carol@example.com

この柔軟性により、リッチなデータ構造を使って複雑な現実世界の実体をモデル化できます。

存在しない属性にアクセスする

存在しない属性にアクセスしようとすると AttributeError が発生します。

python
class Student:
    pass
 
student = Student()
student.name = "David Lee"
 
print(student.name)  # Output: David Lee
# print(student.age)  # AttributeError: 'Student' object has no attribute 'age'

このエラーは役に立ちます。属性が存在すると期待しているのに存在しない、というタイプミスやロジックエラーを捕捉してくれます。

手動で属性を割り当てる問題点

インスタンスを作成した後で属性を手動で追加することは できます が、この方法には深刻な欠点があります。

python
class Student:
    pass
 
# 属性の設定忘れやスペルミスが起きやすい
alice = Student()
alice.name = "Alice Johnson"
alice.student_id = "S12345"
# gpa の設定を忘れた!
 
bob = Student()
bob.name = "Bob Smith"
bob.stuent_id = "S12346"  # Typo: stuent instead of student
bob.gpa = 3.5
 
# これで alice には gpa がなく、bob にはタイポがあります
# print(alice.gpa)  # AttributeError
# print(bob.student_id)  # AttributeError

これはミスが起きやすく、面倒です。すべてのインスタンスが正しい属性を持った状態で始まるようにする方法が必要です。そこで登場するのが __init__ メソッドで、30.5 節で扱います。ただしその前に、メソッド(クラスに属する関数)について学びましょう。

30.4) インスタンスメソッドを追加する: self を理解する

メソッド(method)はクラス内で定義され、インスタンスデータを操作する関数です。クラスに、データだけでなく振る舞いも与えます。

シンプルなメソッドを定義する

Student クラスにメソッドを追加してみましょう。

python
class Student:
    def display_info(self):
        print(f"{self.name} - GPA: {self.gpa}")
 
# インスタンスを作成して属性を追加
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
# メソッドを呼び出す
alice.display_info()  # Output: Alice Johnson - GPA: 3.8

メソッド display_info は、通常の関数と同様に def でクラス内に定義されます。重要な違いは最初の引数 self です。

self を理解する

self 引数は、メソッドが操作対象の特定のインスタンスにアクセスするためのものです。alice.display_info() を呼ぶと、Python は自動的に alice をメソッドの第 1 引数として渡します。メソッド内では selfalice を参照するため、self.namealice.name に、self.gpaalice.gpa にアクセスします。

裏側では次のようなことが起きています。

python
class Student:
    def display_info(self):
        print(f"{self.name} - GPA: {self.gpa}")
 
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
# 次の 2 つの呼び出しは等価です:
alice.display_info()           # 通常の書き方
Student.display_info(alice)    # Python が実際に行うこと
 
# どちらも出力: Alice Johnson - GPA: 3.8

alice.display_info() と書くと、Python はそれを Student.display_info(alice) に変換します。インスタンス(alice)がメソッド内の self 引数になります。

なぜ "self" なのか?

self はキーワードではなく慣習上の名前です。技術的には別の名前でも動作します。

python
class Student:
    def display_info(this):  # 動きますが、こうしないでください
        print(f"{this.name} - GPA: {this.gpa}")

しかし、必ず self を使ってください。これは Python の普遍的な慣習であり、他の Python プログラマにとって読みやすいコードになります。別の名前を使うと、読み手を混乱させ、コミュニティの標準にも反します。

複数インスタンスでのメソッド

self の力は、複数のインスタンスがあると明確になります。

python
class Student:
    def display_info(self):
        print(f"{self.name} - GPA: {self.gpa}")
 
# 2 人の学生を作成
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
bob = Student()
bob.name = "Bob Smith"
bob.gpa = 3.5
 
# 同じメソッド、異なるデータ
alice.display_info()  # Output: Alice Johnson - GPA: 3.8
bob.display_info()    # Output: Bob Smith - GPA: 3.5

alice.display_info() を呼ぶと selfalice です。bob.display_info() を呼ぶと selfbob です。同じメソッドコードがどのインスタンスにも使えるのは、self が呼び出し元のインスタンスに応じて切り替わるからです。

alice.display_info

self = alice

bob.display_info

self = bob

alice.name へアクセス
alice.gpa

bob.name へアクセス
bob.gpa

メソッドは追加の引数を取れる

メソッドは self 以外の引数も受け取れます。

python
class Student:
    def update_gpa(self, new_gpa):
        self.gpa = new_gpa
        print(f"Updated {self.name}'s GPA to {self.gpa}")
 
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
alice.update_gpa(3.9)  # Output: Updated Alice Johnson's GPA to 3.9
print(alice.gpa)       # Output: 3.9

alice.update_gpa(3.9) を呼ぶと、Python は aliceself として、3.9new_gpa として渡します。メソッド定義は def update_gpa(self, new_gpa) ですが、呼び出し時に渡す引数は 1 つだけです。self は Python が自動的に処理します。

メソッドは値を返せる

メソッドは通常の関数と同様に値を返せます。

python
class Student:
    def is_honors(self):
        return self.gpa >= 3.5
    
    def get_status(self):
        if self.is_honors():
            return "Honors Student"
        else:
            return "Regular Student"
 
alice = Student()
alice.name = "Alice Johnson"
alice.gpa = 3.8
 
bob = Student()
bob.name = "Bob Smith"
bob.gpa = 3.2
 
print(alice.get_status())  # Output: Honors Student
print(bob.get_status())    # Output: Regular Student

get_statusself.is_honors() を使って別のメソッド(is_honors)を呼び出している点に注目してください。メソッドは同じインスタンス上の別メソッドを呼び出せます。

メソッドと関数: どちらを使うべきか

メソッドと、独立した(スタンドアロンの)関数のどちらを使うべきか迷うかもしれません。次が指針です。

次の場合はメソッドを使います:

  • インスタンスデータ(self.nameself.gpa など)へのアクセスが必要
  • その型に論理的に属する(Student が するである こと)
  • インスタンスの状態を変更する

次の場合はスタンドアロン関数を使います:

  • インスタンスデータが不要
  • 複数の型で動作する
  • 一般的なユーティリティである
python
class Student:
    # メソッド: インスタンスデータが必要
    def is_honors(self):
        return self.gpa >= 3.5
 
# 関数: 汎用ユーティリティで、どんな GPA 値でも動作
def calculate_letter_grade(gpa):
    if gpa >= 3.7:
        return "A"
    elif gpa >= 3.0:
        return "B"
    elif gpa >= 2.0:
        return "C"
    else:
        return "D"
 
alice = Student()
alice.gpa = 3.8
 
# インスタンス固有の判定にはメソッドを使う
print(alice.is_honors())  # Output: True
 
# 一般的な計算には関数を使う
print(calculate_letter_grade(alice.gpa))  # Output: A
print(calculate_letter_grade(2.5))        # Output: C

よくあるメソッドのパターン

以下は頻繁に使う一般的なパターンです。

ゲッター(getter)メソッド(計算した情報を取り出す):

python
class Student:
    def get_full_info(self):
        return f"{self.name} ({self.student_id}) - GPA: {self.gpa}"

セッター(setter)メソッド(バリデーション付きで属性を変更する):

python
class Student:
    def set_gpa(self, new_gpa):
        if 0.0 <= new_gpa <= 4.0:
            self.gpa = new_gpa
        else:
            print("Invalid GPA: must be between 0.0 and 4.0")

問い合わせ(query)メソッド(はい/いいえの質問に答える):

python
class Student:
    def is_honors(self):
        return self.gpa >= 3.5
    
    def is_failing(self):
        return self.gpa < 2.0

アクション(action)メソッド(操作を実行する):

python
class Student:
    def add_grade(self, grade):
        self.grades.append(grade)
        # 全 grade に基づいて GPA を再計算する
        self.gpa = sum(self.grades) / len(self.grades)

30.5) __init__ でインスタンスを初期化する

インスタンス作成後に属性を手動で設定するのは面倒で、ミスも起きやすいです。__init__ メソッドを使うと、インスタンス生成時にデータを渡して初期化できます。

__init__ メソッド

__init__ メソッド(「ダンダー・イニット」または「イニット」と発音します)は、Python が新しいインスタンスを作成するときに自動的に呼び出す特殊メソッドです。

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
# 初期データ付きでインスタンスを作成
alice = Student("Alice Johnson", "S12345", 3.8)
bob = Student("Bob Smith", "S12346", 3.5)
 
print(alice.name)  # Output: Alice Johnson
print(bob.gpa)     # Output: 3.5

Student("Alice Johnson", "S12345", 3.8) と書くと、Python は次を行います。

  1. 空の Student インスタンスを新規作成する
  2. そのインスタンスを self として、あなたが渡した引数とともに __init__ を呼び出す
  3. 初期化されたインスタンスを返す

__init__ メソッドは値を明示的に返しません。属性を設定してインスタンスをその場で変更します。__init__ から値を返そうとすると、Python は TypeError を発生させます。

python
class Student:
    def __init__(self, name):
        self.name = name
        # __init__ からは何も返さないでください
        # return self  # Wrong! TypeError: __init__() should return None, not 'Student'

__init__ の動作

何が起きているのかを段階的に分解してみましょう。

python
class Student:
    def __init__(self, name, student_id, gpa):
        print(f"Initializing student: {name}")
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
        print(f"Initialization complete")
 
alice = Student("Alice Johnson", "S12345", 3.8)
# Output:
# Initializing student: Alice Johnson
# Initialization complete
 
print(alice.name)  # Output: Alice Johnson

self の後にある引数(namestudent_idgpa)は、インスタンス作成時の必須引数になります。指定しない場合、Python は TypeError を発生させます。

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
# student = Student()  # TypeError: __init__() missing 3 required positional arguments
# student = Student("Alice")  # TypeError: __init__() missing 2 required positional arguments
student = Student("Alice Johnson", "S12345", 3.8)  # Correct

手動で属性を割り当てるよりもずっと良いです。Python が、すべてのインスタンスが必須データを持って開始することを強制してくれます。

__init__ におけるデフォルト引数

__init__ でも、通常の関数と同様にデフォルト引数を使えます。

python
class Student:
    def __init__(self, name, student_id, gpa=0.0):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
# GPA は省略可能で、デフォルトは 0.0
alice = Student("Alice Johnson", "S12345", 3.8)
bob = Student("Bob Smith", "S12346")  # デフォルトの gpa=0.0 を使用
 
print(alice.gpa)  # Output: 3.8
print(bob.gpa)    # Output: 0.0

これは、妥当なデフォルト値がありつつ、必要に応じてカスタマイズできる属性に便利です。

__init__ でのバリデーション

__init__ で入力値を検証し、インスタンスが妥当な状態で開始することを保証できます。

python
class Student:
    def __init__(self, name, student_id, gpa):
        if not name:
            print("Error: Name cannot be empty")
            self.name = "Unknown"
        else:
            self.name = name
        
        self.student_id = student_id
        
        if 0.0 <= gpa <= 4.0:
            self.gpa = gpa
        else:
            print(f"Warning: Invalid GPA {gpa}, setting to 0.0")
            self.gpa = 0.0
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(alice.gpa)  # Output: 3.8
 
bob = Student("", "S12346", 5.0)
# Output:
# Error: Name cannot be empty
# Warning: Invalid GPA 5.0, setting to 0.0
print(bob.name)  # Output: Unknown
print(bob.gpa)   # Output: 0.0

これにより、たとえ不正なデータが渡されたとしても、インスタンスは妥当な状態に落ち着きます。

30.6) __str____repr__ による文字列表現

print() でインスタンスを表示したり、対話シェルで参照したりするとき、Python はそれを文字列に変換する必要があります。デフォルトでは、あまり役に立たない表示になります。

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(alice)  # Output: <__main__.Student object at 0x...>

デフォルトの出力はクラス名とメモリアドレスを示すだけで、Alice の実データは何も含みません。これを __str____repr__ という特殊メソッドでカスタマイズできます。

__str__ メソッド

__str__ メソッドは、print()str() がインスタンスを文字列に変換する方法を定義します。

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def __str__(self):
        return f"{self.name} ({self.student_id}) - GPA: {self.gpa}"
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(alice)  # Output: Alice Johnson (S12345) - GPA: 3.8
print(str(alice))  # Output: Alice Johnson (S12345) - GPA: 3.8

__str__ は、エンドユーザー向けに読みやすく有益な文字列を返すべきです。「親しみやすい」表現だと考えてください。

__repr__ メソッド

__repr__ メソッドは、REPL や repr() で使われる「公式」な文字列表現を定義します。

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def __repr__(self):
        return f"Student('{self.name}', '{self.student_id}', {self.gpa})"
 
alice = Student("Alice Johnson", "S12345", 3.8)
print(repr(alice))  # Output: Student('Alice Johnson', 'S12345', 3.8)

REPL では次のようになります。

python
>>> alice = Student("Alice Johnson", "S12345", 3.8)
>>> alice
Student('Alice Johnson', 'S12345', 3.8)

__repr__ は、そのオブジェクトを再生成できる有効な Python コードのように見える文字列を返すべきです。「開発者向け」表現だと考えてください。曖昧さがなく、デバッグに役立つ必要があります。

__str____repr__ の両方を使う

用途に応じて両方を定義できます。

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def __str__(self):
        # 親しみやすく、読みやすい形式
        return f"{self.name} - GPA: {self.gpa}"
    
    def __repr__(self):
        # 曖昧さのない、コードのような形式
        return f"Student('{self.name}', '{self.student_id}', {self.gpa})"
 
alice = Student("Alice Johnson", "S12345", 3.8)
 
print(alice)        # __str__ を使用
# Output: Alice Johnson - GPA: 3.8
 
print(repr(alice))  # __repr__ を使用
# Output: Student('Alice Johnson', 'S12345', 3.8)

REPL では次のようになります。

python
>>> alice = Student("Alice Johnson", "S12345", 3.8)
>>> alice  # __repr__ を使用
Student('Alice Johnson', 'S12345', 3.8)
>>> print(alice)  # __str__ を使用
Alice Johnson - GPA: 3.8

どちらのメソッドを定義すべきか

次が指針です。

  • __repr__ は常に定義する: REPL やデバッグツールで使われます。どちらか一方だけ定義するなら、これを定義してください。
  • ユーザー向けの読みやすい形式が必要なら __str__ を定義する: エンドユーザー向けに表示されるなら、読みやすい __str__ を用意します。
  • __repr__ だけを定義した場合: Python は repr() にそれを使い、str()__repr__ をフォールバックとして使います(つまり print() もそれを使います)。
  • __str__ だけを定義した場合: print()__str__ を使いますが、repr() と REPL はデフォルトの __repr__(メモリアドレスが表示される)を使います。これが、通常 __repr__ のほうが重要である理由です。
python
# __repr__ のみ定義
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price
    
    def __repr__(self):
        return f"Product('{self.name}', {self.price})"
 
item = Product("Laptop", 999.99)
print(item)        # フォールバックとして __repr__ を使用
# Output: Product('Laptop', 999.99)
print(repr(item))  # __repr__ を使用
# Output: Product('Laptop', 999.99)

コレクション内での文字列表現

インスタンスがコレクション(リスト、辞書など)の中にある場合、Python は __str__ ではなく __repr__ を使って表示します。

python
class Student:
    def __init__(self, name, gpa):
        self.name = name
        self.gpa = gpa
    
    def __str__(self):
        return f"{self.name}: {self.gpa}"
    
    def __repr__(self):
        return f"Student('{self.name}', {self.gpa})"
 
students = [
    Student("Alice", 3.8),
    Student("Bob", 3.5),
    Student("Carol", 3.9)
]
 
# リストの print は、各 student に対して __repr__ を使います
print(students)
# Output: [Student('Alice', 3.8), Student('Bob', 3.5), Student('Carol', 3.9)]
 
# 個々の student の print は __str__ を使います
for student in students:
    print(student)
# Output:
# Alice: 3.8
# Bob: 3.5
# Carol: 3.9

このため __repr__ は曖昧さがないべきです。デバッグ中にデータ構造の中身を理解する助けになります。リストを print するとき、Python は基本的に各要素に repr() を呼んで構造が分かるように表示します。

30.7) 複数の独立したインスタンスを作成する

クラスの最も強力な点の 1 つは、独自のデータを持つ独立したインスタンスを数多く作れることです。これを詳しく見ていきましょう。

各インスタンスは独自のデータを持つ

同じクラスから複数のインスタンスを作ると、それぞれが独立した属性を維持します。

python
class BankAccount:
    def __init__(self, account_number, holder_name, balance=0.0):
        self.account_number = account_number
        self.holder_name = holder_name
        self.balance = balance
    
    def deposit(self, amount):
        self.balance += amount
        print(f"Deposited ${amount:.2f}. New balance: ${self.balance:.2f}")
    
    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
            print(f"Withdrew ${amount:.2f}. New balance: ${self.balance:.2f}")
            return True
        else:
            print(f"Insufficient funds. Balance: ${self.balance:.2f}")
            return False
    
    def __str__(self):
        return f"{self.holder_name}'s account ({self.account_number}): ${self.balance:.2f}"
 
# 3 つの独立したアカウントを作成
alice_account = BankAccount("ACC-001", "Alice Johnson", 1000.0)
bob_account = BankAccount("ACC-002", "Bob Smith", 500.0)
carol_account = BankAccount("ACC-003", "Carol Davis", 2000.0)
 
# あるアカウントへの操作は他に影響しません
alice_account.deposit(500)
# Output: Deposited $500.00. New balance: $1500.00
 
bob_account.withdraw(200)
# Output: Withdrew $200.00. New balance: $300.00
 
# 各アカウントは独自の残高を維持します
print(alice_account)  # Output: Alice Johnson's account (ACC-001): $1500.00
print(bob_account)    # Output: Bob Smith's account (ACC-002): $300.00
print(carol_account)  # Output: Carol Davis's account (ACC-003): $2000.00

この独立性は、オブジェクト指向プログラミングの基本です。各インスタンスは、独自の状態を持つ別個の実体です。

コレクション内のインスタンス

インスタンスは、リストや辞書など任意のコレクションに格納できます。

python
class Student:
    def __init__(self, name, student_id, gpa):
        self.name = name
        self.student_id = student_id
        self.gpa = gpa
    
    def is_honors(self):
        return self.gpa >= 3.5
    
    def __repr__(self):
        return f"Student('{self.name}', '{self.student_id}', {self.gpa})"
 
# 学生のリストを作成
students = [
    Student("Alice Johnson", "S12345", 3.8),
    Student("Bob Smith", "S12346", 3.2),
    Student("Carol Davis", "S12347", 3.9),
    Student("David Lee", "S12348", 3.4)
]
 
# 優等生をすべて見つける
honors_students = []
for student in students:
    if student.is_honors():
        honors_students.append(student)
 
print("Honors students:")
for student in honors_students:
    print(f"  {student.name}: {student.gpa}")
# Output:
# Honors students:
#   Alice Johnson: 3.8
#   Carol Davis: 3.9
 
# GPA の平均を計算する
total_gpa = sum(student.gpa for student in students)
average_gpa = total_gpa / len(students)
print(f"Average GPA: {average_gpa:.2f}")  # Output: Average GPA: 3.58

これはよくあるパターンです。複数のインスタンスを作成し、コレクションに格納し、ループや内包表記で処理します。

インスタンスは他のインスタンスを参照できる

インスタンスの属性が別のインスタンスを参照することで、オブジェクト同士の関係を作れます。

python
class Course:
    def __init__(self, course_code, course_name):
        self.course_code = course_code
        self.course_name = course_name
    
    def __str__(self):
        return f"{self.course_code}: {self.course_name}"
 
class Student:
    def __init__(self, name, student_id):
        self.name = name
        self.student_id = student_id
        self.courses = []  # Course インスタンスのリスト
    
    def enroll(self, course):
        self.courses.append(course)
        print(f"{self.name} enrolled in {course.course_name}")
    
    def list_courses(self):
        print(f"{self.name}'s courses:")
        for course in self.courses:
            print(f"  {course}")
 
# コースを作成
python_course = Course("CS101", "Introduction to Python")
data_course = Course("CS102", "Data Structures")
web_course = Course("CS103", "Web Development")
 
# 学生を作成してコースに登録させる
alice = Student("Alice Johnson", "S12345")
alice.enroll(python_course)
alice.enroll(data_course)
# Output:
# Alice Johnson enrolled in Introduction to Python
# Alice Johnson enrolled in Data Structures
 
bob = Student("Bob Smith", "S12346")
bob.enroll(python_course)
bob.enroll(web_course)
# Output:
# Bob Smith enrolled in Introduction to Python
# Bob Smith enrolled in Web Development
 
# 各学生のコースを一覧表示する
alice.list_courses()
# Output:
# Alice Johnson's courses:
#   CS101: Introduction to Python
#   CS102: Data Structures
 
bob.list_courses()
# Output:
# Bob Smith's courses:
#   CS101: Introduction to Python
#   CS103: Web Development

Alice と Bob の両方が python_course に登録されている点に注目してください。2 人とも同じ Course インスタンスを参照しています。これは、複数の学生が同じコースを受講できるという現実世界の関係をモデル化しています。

インスタンスの同一性と等価性

同じデータを持っていても、各インスタンスは一意のオブジェクトです。

python
class Student:
    def __init__(self, name, gpa):
        self.name = name
        self.gpa = gpa
 
alice1 = Student("Alice", 3.8)
alice2 = Student("Alice", 3.8)
 
# 同一データでも別オブジェクト
print(alice1 is alice2)  # Output: False
print(id(alice1) == id(alice2))  # Output: False

デフォルトでは、== も等価性(同じデータか)ではなく同一性(同じオブジェクトか)をチェックします。第31章では、__eq__ 特殊メソッドを使って等価比較をカスタマイズする方法を学びます。


この章では、Python におけるオブジェクト指向プログラミングの基礎を紹介しました。クラスの定義、インスタンスの作成、メソッドの追加、__init__ によるインスタンス初期化、文字列表現の制御、複数の独立したインスタンスの扱いを学びました。これらの概念は、第31章と第32章で探求する、より高度な OOP 機能の土台になります。


© 2025. Primesoft Co., Ltd.
support@primesoft.ai