Python OOP入門:特殊メソッド(init, str, __repr__など)の基本と実装

Pythonのオブジェクト指向プログラミング(OOP)では、クラス定義だけでなく「特殊メソッド」を実装することで、オブジェクトの振る舞いを細かく制御できます。

本記事では、初心者向けに代表的な特殊メソッド(__init__, __str__, __repr__)の役割と使い方を丁寧に解説し、実践的なサンプルコードと演習問題を用意しました。


特殊メソッドとは?

特殊メソッドは、Pythonが内部で呼び出すメソッドで、メソッド名が両端にダブルアンダースコア(__)で囲まれています。

例)__init__, __str__, __repr__, __len__, __eq__ など。

クラス定義にこれらを実装すると、オブジェクト生成時の処理や、文字列化、比較処理などをカスタマイズ可能。


__init__:コンストラクタ(初期化処理)

概要

  • インスタンス生成時に自動実行され、属性の初期化を行う。
  • 引数を受け取って内部状態を設定できる。

サンプルコード

class Person:
    def __init__(self, name, age):
        # インスタンス変数の定義
        self.name = name
        self.age = age

# インスタンス生成
p = Person("Alice", 30)
print(p.name)  # Alice
print(p.age)   # 30

ポイント

  1. メソッド名は必ず__init__。
  2. 最初の引数は常にself。
  3. 外部から受け取った値をselfに格納し、後続のメソッドから参照可能にする。

__str__:ユーザ向け文字列表現

概要

  • print(obj)やstr(obj)を呼んだときに返される文字列を定義。
  • 主にエンドユーザやログ向けの見やすい表示を実装できる。

サンプルコード

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

    def __str__(self):
        # 見やすさ重視の文字列
        return f"Person(name={self.name}, age={self.age})"

p = Person("Bob", 25)
print(p)  # Person(name=Bob, age=25)

ポイント

  1. 戻り値は文字列であること。
  2. 読みやすさ重視でフォーマットを工夫すると、デバッグやログ出力が楽になる。

__repr__:開発者向け文字列表現

概要

  • repr(obj)を呼んだとき、あるいはインタラクティブシェル上でオブジェクトを評価したときに返される文字列を定義。
  • 可能ならば同じコードを再実行して同じオブジェクトを作れるように書くのが理想。

サンプルコード

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

    def __repr__(self):
        # eval(repr(obj)) で同じインスタンスが作れるイメージ
        return f"Person({self.name!r}, {self.age!r})"

p = Person("Carol", 22)
p  # 出力→ Person('Carol', 22)

!r と !s の使い分け

  • !r:repr()を使って文字列化
  • !s:str()を使って文字列化
    通常、__repr__ではエスケープを含む!rを使い、__str__では!sを使う。

シーケンス・マッピング関連

__len__

  • 役割: len(obj) が呼ばれたときに返す長さを定義
  • 呼び出しタイミング: 組み込み関数 len() 実行時
class MyList:
    def __init__(self, data):
        self.data = list(data)

    def __len__(self):
        return len(self.data)

ml = MyList([1,2,3,4])
print(len(ml))  # → 4

__getitem__, __setitem__, __delitem__

  • 役割: obj[key] 、代入、削除 の振る舞いを定義
  • 呼び出しタイミング: 添字アクセス・代入・削除時
class MyDict:
    def __init__(self):
        self._d = {}

    def __getitem__(self, key):
        return self._d[key]

    def __setitem__(self, key, value):
        self._d[key] = value

    def __delitem__(self, key):
        del self._d[key]

md = MyDict()
md['a'] = 10
print(md['a'])  # → 10
del md['a']

__contains__

  • 役割: key in obj の判定を定義
  • 呼び出しタイミング: in 演算子使用時
class EvenContainer:
    def __contains__(self, item):
        return isinstance(item, int) and item % 2 == 0

ev = EvenContainer()
print(4 in ev)  # → True
print(3 in ev)  # → False

イテレーター・コンテナ

__iter__ と __next__

  • 役割: オブジェクトをイテレーター化し、 for ループなどで反復可能にする
  • 呼び出しタイミング: iter(obj) → __iter__、次の要素取得で __next__
class CountUp:
    def __init__(self, max):
        self.max = max
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.max:
            raise StopIteration
        val = self.current
        self.current += 1
        return val

for i in CountUp(5):
    print(i)  # 0,1,2,3,4

比較・算術演算子

比較演算子

  • __eq__, __ne__, __lt__, __le__, __gt__, __ge__
  • 役割: ==, !=, < などの挙動を定義
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __eq__(self, other):
        return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)

p1 = Point(1,2)
p2 = Point(1,2)
print(p1 == p2)  # → True

算術演算子

  • __add__, __sub__, __mul__, __truediv__ など
  • 役割: +, -, *, / の振る舞いを定義
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(1,1)
v2 = Vector(2,3)
print(v1 + v2)  # → Vector(3, 4)

呼び出し可能オブジェクト

__call__

  • 役割: インスタンスを関数のように呼び出せるようにする
  • 呼び出しタイミング: obj(…) 実行時
class Greeter:
    def __init__(self, greeting):
        self.greeting = greeting

    def __call__(self, name):
        return f"{self.greeting}, {name}!"

g = Greeter("Hello")
print(g("World"))  # → Hello, World!

コンテキストマネージャ

__enter__ と __exit__

  • 役割: with 文に対応した開始処理と終了処理を定義
  • 呼び出しタイミング: with obj as var: を使ったとき
class Timer:
    import time
    def __enter__(self):
        self.start = self.time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        elapsed = self.time.time() - self.start
        print(f"Elapsed: {elapsed:.4f}s")

with Timer():
    sum(range(1000000))

属性アクセスの制御

__getattr__ と __setattr__

  • 役割: 存在しない属性へのアクセスや、属性設定時の挙動をカスタマイズ
  • 注意: 無限再帰に注意して、基底クラスのメソッドを呼ぶこと
class LazyAttributes:
    def __init__(self):
        self._cache = {}

    def __getattr__(self, name):
        # _cache にない属性は計算して保存
        if name.startswith("value_"):
            val = len(name)
            self._cache[name] = val
            return val
        raise AttributeError(f"{name} not found")

obj = LazyAttributes()
print(obj.value_hello)  # → 11

初級者が陥りやすいポイント

  1. __repr__と__str__の混同
    __repr__は開発者向け、__str__はユーザ向け。どちらも実装するのがベター。
  2. 戻り値を返さない
    __str__や__repr__ではreturnを忘れるとNoneが返ってしまう。
  3. selfの扱い
    引数リストからselfを忘れるとエラーに。必ずメソッドの第1引数として宣言。

まとめと次のステップ

  • __init__:インスタンス生成時の初期化
  • __str__:人が読むための文字列表現
  • __repr__:開発・デバッグ用の文字列表現
  • 他にも数多くの特殊メソッドがあり、実践プロジェクトで徐々に学ぶと効果的

次は「演習問題」で学びを定着させましょう!


演習問題

以下の設問に対して、特殊メソッドを用いてクラスを実装し、解答例と比較してみましょう。

問題1:Dogクラスの実装

  • 属性:name(名前)、age(年齢)
  • コンストラクタ:__init__
  • ユーザ向け表示:__str__ で「Dog(name=〇〇, age=△△歳)」
  • 開発者向け表示:__repr__ で Dog(‘〇〇’, △△)

問題2:Rectangleクラスの実装

  • 属性:width, height
  • __init__で設定
  • __repr__で Rectangle(width=幅, height=高さ)
  • __eq__を実装し、面積が等しければ同一視

問題3:Counterクラスの実装

  • 属性:辞書型でキーと出現回数を管理
  • __init__で初期データを受け取る(例:{“a”: 2, “b”: 3})
  • __len__を実装し、全キーの合計出現回数を返す

解答例

# 解答例1:Dogクラス
class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"Dog(name={self.name}, age={self.age}歳)"

    def __repr__(self):
        return f"Dog({self.name!r}, {self.age!r})"

# テスト
d = Dog("Pochi", 5)
print(str(d))   # Dog(name=Pochi, age=5歳)
print(repr(d))  # Dog('Pochi', 5)


# 解答例2:Rectangleクラス
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def __repr__(self):
        return f"Rectangle(width={self.width}, height={self.height})"

    def __eq__(self, other):
        if not isinstance(other, Rectangle):
            return NotImplemented
        return self.width * self.height == other.width * other.height

# テスト
r1 = Rectangle(4, 5)
r2 = Rectangle(2, 10)
print(repr(r1))           # Rectangle(width=4, height=5)
print(r1 == r2)           # True


# 解答例3:Counterクラス
class Counter:
    def __init__(self, data=None):
        self.counts = data.copy() if data else {}

    def __len__(self):
        return sum(self.counts.values())

# テスト
c = Counter({"a": 2, "b": 3})
print(len(c))  # 5

以上が「PythonのOOPにおける特殊メソッド」の基礎解説と演習問題でした。

特殊メソッドをマスターすると、Pythonらしい直感的で強力なクラス設計が可能になります。ぜひ手を動かして理解を深めてください!