Python | OOP:メソッドチェーン

Python Python
スポンサーリンク

概要(メソッドチェーンは「操作をつなげて一気に書く」設計)

メソッドチェーンは、複数のメソッド呼び出しをドットで連ねて、処理の流れをそのまま一行で表現する書き方です。ポイントは“次のメソッドを呼べる値を返す”設計にすること。具体的には「自分自身(self)を返す」か「同じ型の新しいインスタンスを返す」ことで、obj.method1().method2().method3()のような連鎖が可能になります。


基本のパターン(return selfで“流暢な”操作をつなぐ)

最小例(数値を加工するクラス)

class NumberOps:
    def __init__(self, v: float):
        self.v = v

    def add(self, x: float) -> "NumberOps":
        self.v += x
        return self  # 連鎖のためにselfを返す

    def mul(self, x: float) -> "NumberOps":
        self.v *= x
        return self

    def result(self) -> float:
        return self.v

n = NumberOps(10).add(5).mul(2).result()  # 10 + 5 → * 2 → 30
print(n)
Python

各メソッドがselfを返すと、その場で同じオブジェクトに積み重ねていけます。副作用(内部状態の変更)が前提の“フルエントインターフェース”の基本形です。


不変(イミュータブル)設計でのメソッドチェーン(新しいインスタンスを返す)

副作用なしで安全につなぐ

class Text:
    def __init__(self, s: str):
        self._s = s

    def strip(self) -> "Text":
        return Text(self._s.strip())  # 新インスタンスで返す

    def lower(self) -> "Text":
        return Text(self._s.lower())

    def replace(self, a: str, b: str) -> "Text":
        return Text(self._s.replace(a, b))

    def value(self) -> str:
        return self._s

t = Text("  Hello World  ").strip().lower().replace(" ", "_").value()
print(t)  # hello_world
Python

各メソッドが“新しい同型のインスタンス”を返す設計は、原本が書き換わらず、並行処理や再利用に強いのが利点です。


実務での使いどころ(前処理、クエリ構築、ビルダー)

データ前処理のパイプラインを一行で見通す

class Users:
    def __init__(self, rows: list[dict]):
        self._rows = rows

    def filter_active(self) -> "Users":
        return Users([r for r in self._rows if r.get("active")])

    def normalize(self) -> "Users":
        return Users([{"id": str(r.get("id", "")), "name": (r.get("name") or "unknown").strip()}
                      for r in self._rows])

    def take(self, n: int) -> "Users":
        return Users(self._rows[:n])

    def to_list(self) -> list[dict]:
        return self._rows

data = Users([{"id": 1, "name": "  Taro ", "active": True},
              {"id": 2, "name": None, "active": False}]) \
          .filter_active().normalize().take(1).to_list()
print(data)  # [{'id': '1', 'name': 'Taro'}]
Python

工程が左から右へ視覚的に流れるため、複雑な前処理も読みやすく保てます。

クエリビルダー/ビルダーパターンで段階的に構築

class Query:
    def __init__(self):
        self._parts = []
    def select(self, *cols) -> "Query":
        self._parts.append(("SELECT", cols)); return self
    def where(self, cond: str) -> "Query":
        self._parts.append(("WHERE", cond)); return self
    def order_by(self, key: str) -> "Query":
        self._parts.append(("ORDER", key)); return self
    def build(self) -> str:
        # 実装例は簡略化
        sel = ", ".join(self._parts[0][1])
        where = self._parts[1][1]
        order = self._parts[2][1]
        return f"SELECT {sel} FROM users WHERE {where} ORDER BY {order}"

sql = Query().select("id", "name").where("active=1").order_by("name").build()
print(sql)
Python

“作る→条件→並び”のような組み立てを、自然な順序で表現できます。


重要ポイントの深掘り(チェーン継続条件、例外、可読性)

何を返せばチェーンが続くかを設計で固定する

チェーンを続けるには「次のメソッドが存在する型」を返す必要があります。副作用ありならself、不変設計なら同型の新インスタンス。途中で最終結果(数値や文字列など)を返した時点でチェーンは終わるので、戻り値の型を明確に設計しましょう。

例外の扱いは“早期・明確”

チェーン途中の検証に失敗したら、ただちにValueErrorなどを送出します。エラーを握りつぶしてNoneを返すと、その後のメソッド呼び出しで「NoneTypeにメソッドがない」失敗が起きやすく、原因が見えにくくなります。

可読性を最優先に(長すぎる一行は分割)

3〜5段程度までなら読みやすさが上がりますが、10段を超えるチェーンは保守が難しくなります。適宜、改行・コメント・中間変数で区切り、意図を明確に保ちましょう。

result = (
    Text("  Hello World  ")
    .strip()
    .lower()
    .replace(" ", "_")
    .value()
)
Python

よくある落とし穴(Noneを返してチェーン断絶、副作用の過多、順序依存の罠)

Noneを返すメソッドを混ぜるとチェーンが壊れる

Pythonの慣習で「更新系メソッドはNoneを返す」ことがあります(例:list.sort())。チェーン設計にするなら、更新後のselfを返すか“不変版の別名”を用意して区別しましょう。

副作用だらけでテストが難しくなる

チェーンの各段で外部I/Oや重い処理を行うと、失敗時の切り分けが困難になります。チェーンは変換・検証など“軽い処理”を中心にし、I/Oは明示メソッドに分離するのが安全です。

呼び出し順の意味が強すぎる

同じメソッドでも順序で結果が変わる設計は、利用者がミスしやすいです。できるだけ順序に依存しないようにするか、依存するならドキュメントで強調し、分岐を減らす工夫を入れましょう。


例題(テキストクレンザーとHTTPリクエストビルダー)

テキストクレンザー(不変チェーンで安全に整形)

class CleanText:
    def __init__(self, s: str): self._s = s
    def trim(self) -> "CleanText": return CleanText(self._s.strip())
    def ascii(self) -> "CleanText": return CleanText(self._s.encode("ascii", "ignore").decode())
    def to_snake(self) -> "CleanText": return CleanText(self._s.lower().replace(" ", "_"))
    def value(self) -> str: return self._s

res = CleanText("  Café Latte  ").trim().ascii().to_snake().value()
print(res)  # cafe_latte
Python

HTTPリクエストビルダー(段階的に構築して最後に送る)

class RequestBuilder:
    def __init__(self):
        self._url = ""
        self._params = {}
        self._headers = {}

    def url(self, u: str) -> "RequestBuilder":
        self._url = u; return self

    def param(self, k: str, v: str) -> "RequestBuilder":
        self._params[k] = v; return self

    def header(self, k: str, v: str) -> "RequestBuilder":
        self._headers[k] = v; return self

    def send(self) -> dict:
        import requests
        r = requests.get(self._url, params=self._params, headers=self._headers, timeout=5.0)
        r.raise_for_status()
        return r.json()

data = RequestBuilder().url("https://api.example.com/users").param("page", "1").header("Authorization", "Bearer TOKEN").send()
print(data)
Python

“設定→送信”の流れがそのままチェーンに表現でき、読みやすく拡張しやすい形になります。


まとめ(メソッドチェーンは「返す型」をデザインするだけで劇的に効く)

メソッドチェーンは、各メソッドがselfか同型インスタンスを返す設計にすることで、処理の流れを直感的に表現できます。副作用ありのフルエントならreturn self、不変なら新インスタンスを返す。途中で結果型に切り替えた瞬間にチェーンは終わるため、戻り値の型を明確に。None返却の混入や過度な副作用、順序依存には注意し、必要なら改行や中間変数で可読性を担保する。これを徹底すれば、初心者でも“読みやすく、差し替えに強く、テストしやすい”設計を自然に書けます。

タイトルとURLをコピーしました