Python | OOP:メソッド

Python Python
スポンサーリンク
  1. 概要(メソッドは「そのオブジェクトができること」を表す操作)
  2. メソッドの基本(self・引数・返り値の型を整える)
    1. selfの意味(インスタンス自身を指す参照)
    2. 引数と返り値の型ヒント(契約を明確に)
  3. 3種類のメソッド(インスタンス・クラス・スタティック)
    1. インスタンスメソッド(最も一般的な振る舞い)
    2. クラスメソッド(クラスに紐づく工場や設定操作)
    3. スタティックメソッド(文脈上ここに置きたい純粋関数)
  4. Web/APIで使う形(クライアント・サービス・モデル)
    1. APIクライアントのメソッド設計(設定を握り、操作を提供)
    2. サービス層のメソッド(ドメイン整形を担当)
    3. モデルのメソッド(表現・変換・検証)
  5. 使いやすいインターフェース(プロパティ・特殊メソッド・チェーン)
    1. @propertyで自然なアクセス(読み取り・検証付き書き込み)
    2. 特殊メソッドで“ふるまい”を統合
    3. メソッドチェーン(戻り値でつなぐ)
  6. よくあるつまずきと回避(self・責務・可変データの扱い)
    1. selfの書き忘れと引数不一致
    2. なんでもメソッドに詰め込みすぎる
    3. 可変データを共有して壊す
  7. 例題(小さなAPIラッパーを設計して動かす)
    1. 設定→クライアント→サービスの3層でメソッドを配置
  8. まとめ(「誰が何をするか」をメソッドに刻む)

概要(メソッドは「そのオブジェクトができること」を表す操作)

メソッドは、クラスに属する関数で「オブジェクトの振る舞い」を定義します。インスタンスの状態(属性)を読み書きしたり、外部と連携したり、目的に応じて処理をまとめられます。まず押さえるべきは、インスタンスメソッド・クラスメソッド・スタティックメソッドの違い、selfの意味、そして「引数→検証→処理→返り値」の設計の型です。


メソッドの基本(self・引数・返り値の型を整える)

selfの意味(インスタンス自身を指す参照)

インスタンスメソッドの第1引数selfは「このオブジェクト自身」です。self.nameのように属性へアクセスし、状態を使って処理します。呼び出し側はselfを渡しません(Pythonが自動で渡します)。

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

    def greet(self) -> str:
        return f"こんにちは、{self.name}{self.age}歳)です!"

p = Person("太郎", 23)
print(p.greet())
Python

引数と返り値の型ヒント(契約を明確に)

型ヒントを付けると、補完が効いて誤りを減らせます。デフォルト値で使いやすくし、バリデーションは入口で行います。

class Calculator:
    def total(self, price: float, tax_rate: float = 0.1) -> float:
        if price < 0 or not (0 <= tax_rate <= 1):
            raise ValueError("priceは0以上、tax_rateは0〜1")
        return price * (1 + tax_rate)
Python

3種類のメソッド(インスタンス・クラス・スタティック)

インスタンスメソッド(最も一般的な振る舞い)

インスタンスの状態を使う処理に向きます。selfにアクセスして属性を読む・書くのが前提です。

class Counter:
    def __init__(self):
        self.n = 0
    def inc(self) -> None:
        self.n += 1
    def value(self) -> int:
        return self.n
Python

クラスメソッド(クラスに紐づく工場や設定操作)

@classmethodは第1引数にcls(クラス自身)を受けます。代替コンストラクタや、クラス全体の設定変更に向いています。

class User:
    def __init__(self, user_id: str, name: str):
        self.user_id = user_id
        self.name = name

    @classmethod
    def from_dict(cls, data: dict) -> "User":
        return cls(data["id"], data.get("name", "unknown"))
Python

スタティックメソッド(文脈上ここに置きたい純粋関数)

@staticmethodはself/clsを受けません。クラスに関連するが状態に依存しない“補助関数”をまとめる場所として使います。

class StringUtil:
    @staticmethod
    def slugify(s: str) -> str:
        return s.lower().replace(" ", "-")
Python

Web/APIで使う形(クライアント・サービス・モデル)

APIクライアントのメソッド設計(設定を握り、操作を提供)

セッションやヘッダーをinitで準備し、HTTP操作をメソッドに分割します。タイムアウトやエラー処理を各メソッドで一貫させます。

import requests

class ApiClient:
    def __init__(self, base_url: str, api_key: str, timeout: float = 5.0):
        if not base_url.startswith("https://"):
            raise ValueError("HTTPSのみ許可")
        self.base_url = base_url
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {api_key}"})

    def get_user(self, uid: str) -> dict:
        r = self.session.get(f"{self.base_url}/users/{uid}", timeout=self.timeout)
        r.raise_for_status()
        return r.json()
Python

サービス層のメソッド(ドメイン整形を担当)

外部I/Oとロジックを分離し、返却値を「使いやすい形」に整えます。ここで境界値や欠損の扱いを統一します。

class UserService:
    def __init__(self, client: ApiClient):
        self.client = client

    def profile(self, uid: str) -> dict:
        data = self.client.get_user(uid)
        return {"id": data.get("id", ""), "name": data.get("name") or "unknown"}
Python

モデルのメソッド(表現・変換・検証)

データモデルはto_dictやvalidateのような“自分に関する操作”をメソッドとして持つと扱いやすくなります。

class UserModel:
    def __init__(self, user_id: str, name: str | None = None):
        if not user_id:
            raise ValueError("user_idは必須")
        self.user_id = user_id
        self.name = name or "unknown"

    def to_dict(self) -> dict:
        return {"id": self.user_id, "name": self.name}
Python

使いやすいインターフェース(プロパティ・特殊メソッド・チェーン)

@propertyで自然なアクセス(読み取り・検証付き書き込み)

計算値や不変条件を守りたい属性はプロパティ化します。setterで検証すれば、常に整合した状態を保てます。

class Rectangle:
    def __init__(self, w: float, h: float):
        self._w = w; self._h = h
    @property
    def area(self) -> float:
        return self._w * self._h
Python

特殊メソッドで“ふるまい”を統合

strで表示、eqで比較、reprでデバッグ、callで「関数のように呼べる」など、Pythonらしい使い心地を提供できます。

class Price:
    def __init__(self, amount: float, currency: str = "JPY"):
        self.amount = amount; self.currency = currency
    def __str__(self) -> str:
        return f"{self.amount:.2f} {self.currency}"
    def __eq__(self, other: object) -> bool:
        return isinstance(other, Price) and (self.amount, self.currency) == (other.amount, other.currency)
Python

メソッドチェーン(戻り値でつなぐ)

副作用を抑えるなら、変更後の新インスタンスを返して連鎖させます。読みやすい“手続きの線”が作れます。

class Query:
    def __init__(self, base: str, params: dict | None = None):
        self.base = base; self.params = params or {}
    def add(self, k: str, v: str) -> "Query":
        new = dict(self.params); new[k] = v
        return Query(self.base, new)
    def build(self) -> str:
        qs = "&".join(f"{k}={v}" for k, v in self.params.items())
        return f"{self.base}?{qs}"

url = Query("https://example.com").add("q", "python").add("page", "1").build()
Python

よくあるつまずきと回避(self・責務・可変データの扱い)

selfの書き忘れと引数不一致

メソッド定義の第1引数にselfを忘れるとTypeErrorになります。エディタのシグネチャ警告を活かし、雛形から書く癖をつけましょう。

なんでもメソッドに詰め込みすぎる

「検証→処理→整形」を1メソッドでやり過ぎるとテストが難しくなります。I/O(外部)とロジック(内部)は別メソッド・別クラスに分け、単体テスト可能な粒度に保ちます。

可変データを共有して壊す

クラス属性にlist/dictを置くと全インスタンスで共有されます。インスタンス固有の状態はself側に持ち、変更の影響範囲を限定しましょう。


例題(小さなAPIラッパーを設計して動かす)

設定→クライアント→サービスの3層でメソッドを配置

from dataclasses import dataclass
import requests

@dataclass
class Settings:
    base_url: str
    api_key: str

class Client:
    def __init__(self, s: Settings, timeout: float = 5.0):
        self.base_url = s.base_url
        self.timeout = timeout
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {s.api_key}"})

    def get(self, path: str) -> dict:
        r = self.session.get(f"{self.base_url}{path}", timeout=self.timeout)
        r.raise_for_status()
        return r.json()

class UserService:
    def __init__(self, client: Client):
        self.client = client

    def user(self, uid: str) -> dict:
        data = self.client.get(f"/users/{uid}")
        return {"id": data.get("id", ""), "name": data.get("name") or "unknown"}
Python

この構成だと、I/OはClientのメソッド、ドメイン整形はServiceのメソッドと役割が明確で、テストもモックしやすくなります。


まとめ(「誰が何をするか」をメソッドに刻む)

メソッドは、オブジェクトの振る舞いそのもの。selfで状態に触れるインスタンスメソッドを基本に、クラス全体の文脈は@classmethod、純粋な補助は@staticmethodへ分けます。引数と返り値の契約を型ヒントで明示し、検証は入口で、I/Oとロジックは分離してテストしやすく。プロパティや特殊メソッドで使い心地を整え、チェーンや不変設計で副作用を抑える。この型を体に入れれば、初心者でもメソッドを「読みやすく、壊れにくく、拡張しやすく」設計できます。

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