閉じる

Pythonのstrメソッド入門:クラスの見やすい文字列化テクニック

Pythonでクラスを定義するとき、インスタンスをprint()した際の「見た目」を意識したことはありますか。

__str__メソッドをうまく設計すると、ログやデバッグ、UI表示が一気にわかりやすくなります

この記事では、Pythonの__str__メソッドの基本から実践的なテクニックまで、図解とサンプルコードを交えながら丁寧に解説します。

__str__メソッドとは?Pythonにおける役割と基本

__str__メソッドの役割と__repr__との違い

Pythonの__str__メソッドは、オブジェクトを「人間にとって読みやすい文字列」に変換するための特別メソッドです。

主にprint()str()が呼ばれたときに使われます。

一方で__repr__メソッドは、開発者がデバッグするときなどに使う「公式な」表現を返すことが目的です。

可能であれば、その文字列から同じオブジェクトを再構築できることが理想とされています。

ここで、__str____repr__の違いを表にまとめます。

項目__str____repr__
主な目的ユーザー向けの読みやすい表示開発者向けの正確な表示
呼ばれる場面str(obj)print(obj) など対話環境の表示、repr(obj)、コンテナ内部の表示
推奨方針わかりやすさ重視正確さ・再現性重視
未実装時のフォールバック__repr__が使われるデフォルト実装

例えば、次のようなクラスを考えます。

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

    def __repr__(self):
        # なるべく再構築可能な表現
        return f"User(user_id={self.user_id!r}, name={self.name!r})"

    def __str__(self):
        # ユーザー向けに読みやすく
        return self.name


user = User(1, "Alice")

print(str(user))    # __str__が呼ばれる
print(repr(user))   # __repr__が呼ばれる
user                # 対話シェルでは__repr__が表示
実行結果
Alice
User(user_id=1, name='Alice')

このように、「ユーザーに何を見せたいか」と「開発者として何を知りたいか」を分けて設計することが重要です。

文字列表現が重要になるケース(ログ・デバッグ・UI表示など)

オブジェクトの文字列表現が重要になる場面は、実務では非常に多いです。

代表的なものとして、次のようなケースがあります。

1つ目はログ出力です。

エラーやイベントを記録する際、オブジェクトをそのままログに書き出すことがあります。

このとき読みやすい__str__が定義されていれば、ログから状況を素早く把握できます

2つ目はデバッグです。

デバッガや対話シェル(IPython、REPLなど)でオブジェクトを表示するとき、__repr____str__が分かりやすく実装されていると、内部状態を直感的に理解できます。

3つ目はUI表示です。

WebアプリケーションやGUIアプリケーション、レポート生成などで、モデルオブジェクトの情報をユーザーに見せる機会があります。

このときユーザー目線を意識した__str__があると、テンプレート側の記述もシンプルになります

__str__が定義されていない場合のデフォルト動作

クラスで__str__を定義していない場合、Pythonは自動的に__repr__を代わりに使います

さらに__repr__も定義されていなければ、組み込みのデフォルト実装が使われます。

具体例を見てみます。

Python
class Sample:
    def __init__(self, value):
        self.value = value


obj = Sample(10)

print(str(obj))   # __str__未定義 → __repr__が呼ばれる
print(repr(obj))  # デフォルトの__repr__が呼ばれる
実行結果
<__main__.Sample object at 0x7f8e2b4e0c10>
<__main__.Sample object at 0x7f8e2b4e0c10>

この<__main__.Sample object at 0x...>という形式が、デフォルトの__repr__の出力です。

クラス名とメモリアドレスしか分からないため、実務ではそのままではほぼ役に立ちません

そのため、少なくとも__repr__、できれば__str__も実装しておくことが望ましいです。

Pythonクラスにおける__str__メソッドの基本的な書き方

シンプルなクラスでの__str__実装例

まずは、ごくシンプルなクラスに__str__を実装する例を見てみます。

Python
class Product:
    def __init__(self, product_id, name, price):
        # 商品ID
        self.product_id = product_id
        # 商品名
        self.name = name
        # 価格(税抜き)
        self.price = price

    def __str__(self):
        # ユーザーに見せるときの分かりやすい文字列表現を返します
        return f"Product(id={self.product_id}, name='{self.name}', price={self.price}円)"


if __name__ == "__main__":
    apple = Product(1, "Apple", 120)
    print(apple)          # __str__が自動的に呼ばれる
    print(str(apple))     # 明示的にstr()を呼ぶ場合も同じ
実行結果
Product(id=1, name='Apple', price=120円)
Product(id=1, name='Apple', price=120円)

このようにprint(インスタンス)で直感的に内容が理解できるようになることが、__str__実装の第一歩です。

f文字列を使った読みやすい文字列表現の作り方

Python 3.6以降では、f文字列(f-string)を使うと、__str__の実装を簡潔に、かつ読みやすく書くことができます。

Python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        # f文字列を使うことで、変数展開を自然な形で書けます
        return f"({self.x}, {self.y})"


if __name__ == "__main__":
    p = Point(10, 20)
    print(p)
実行結果
(10, 20)

f文字列はf"...{変数}..."という形で変数を埋め込めるため、従来の"...{}...".format()よりも視認性が高く、__str__のようにフォーマットが重要な場面で特に有用です。

returnで返すべき文字列の設計ポイント

__str__必ず文字列(str型)を返す必要があります。

ここでは、どのような文字列を返すべきかという観点で設計ポイントを整理します。

1つ目のポイントは、「一目でオブジェクトの役割と主要な情報が分かること」です。

例えばユーザー情報であれば、IDだけでなく名前やメールアドレスなども含めた方が理解しやすくなります。

2つ目は、長すぎないことです。

すべての属性を羅列すると、ログやUIが読みにくくなります。

重要度の高いものだけを選び、必要に応じて__repr__側で詳細情報を持たせるという分担も有効です。

3つ目は、フォーマットの一貫性です。

同じ種類のオブジェクトは、常に同じ形式で表示されるようにします。

これにより、ログを横断的に読んだときにパターンをつかみやすくなります。

サンプルとして、ユーザーオブジェクトの__str__設計例を示します。

Python
class User:
    def __init__(self, user_id, name, email, is_active=True):
        self.user_id = user_id
        self.name = name
        self.email = email
        self.is_active = is_active

    def __str__(self):
        # 重要度の高い情報に絞って表現します
        status = "active" if self.is_active else "inactive"
        # 一貫したフォーマット: User[id=..., name=..., status=...]
        return f"User[id={self.user_id}, name={self.name}, status={status}]"


if __name__ == "__main__":
    u = User(1, "Alice", "alice@example.com", is_active=False)
    print(u)
実行結果
User[id=1, name=Alice, status=inactive]

このように、「誰がいつ見ても意味が取りやすい文字列」を意識して設計することが大切です。

見やすい文字列化テクニックと設計のコツ

ユーザー目線での「読みやすさ」を意識した__str__設計

__str__は基本的に「ユーザー目線」の表示を意識するとよいです。

ここで言うユーザーとは、アプリケーションの最終利用者だけでなく、将来コードを読む別の開発者も含めた「表示を読む人」全般を指します。

例えば、注文オブジェクトを考えてみます。

Python
class Order:
    def __init__(self, order_id, user_name, total_items, total_price, created_at):
        self.order_id = order_id
        self.user_name = user_name
        self.total_items = total_items
        self.total_price = total_price
        self.created_at = created_at

    def __str__(self):
        # 人間が読んで意味が通りやすい自然な文章に近づけます
        return (
            f"{self.created_at:%Y-%m-%d}の注文 "
            f"(お客様: {self.user_name}, 合計{self.total_items}点 / {self.total_price}円)"
        )

このように表示しておけば、ログや管理画面で「いつ・誰の・どんな注文か」がひと目で分かります

複数属性を整形して表示するテクニック

複数の属性をきれいに並べて表示するには、次のような工夫が有効です。

1つは、ラベルと値の対応を明確にすることです。

例えばname=Aliceのように、key=value形式にすると、人間にもパースしやすくなります。

2つ目は、順番を固定することです。

毎回表示順が変わると、比較がしにくくなります。

3つ目は、リストや辞書の要素数が多い場合は要約することです。

すべてを表示するのではなく、「…ほかN件」といった表現にして情報量をコントロールします。

サンプルとして、複数属性を一行で分かりやすく表示する例を示します。

Python
class Task:
    def __init__(self, task_id, title, tags, priority, done=False):
        self.task_id = task_id
        self.title = title
        self.tags = tags
        self.priority = priority
        self.done = done

    def __str__(self):
        # タグはカンマ区切りでまとめます
        tags_str = ", ".join(self.tags) if self.tags else "no tags"
        status = "DONE" if self.done else "TODO"

        # 順番とフォーマットを固定して整形します
        return (
            f"Task#{self.task_id} [{status}] "
            f"({self.priority}) {self.title} - tags: {tags_str}"
        )


if __name__ == "__main__":
    t = Task(1, "Write __str__ article", ["python", "blog"], "HIGH")
    print(t)
実行結果
Task#1 [TODO] (HIGH) Write __str__ article - tags: python, blog

必要な情報をコンパクトに詰め込むことで、1行のログでも状況が把握しやすくなります。

改行やインデントを使った複雑オブジェクトの文字列化

オブジェクトが複雑になり、ネストしたデータを持つようになると、1行の文字列だけでは読みづらくなってきます。

そのような場合は、改行やインデントを積極的に使うとよいです。

Python
class Report:
    def __init__(self, title, author, sections):
        # sectionsは文字列リストとします
        self.title = title
        self.author = author
        self.sections = sections

    def __str__(self):
        # ヘッダ部分
        header = f"Report: {self.title}\nAuthor: {self.author}\n"

        # セクション部分を番号付きで整形します
        if not self.sections:
            sections_str = "  (no sections)"
        else:
            # 各行にインデントを付けて読みやすくします
            lines = []
            for i, section in enumerate(self.sections, start=1):
                lines.append(f"  {i}. {section}")
            sections_str = "\n".join(lines)

        return header + "Sections:\n" + sections_str


if __name__ == "__main__":
    report = Report(
        "Python __str__ Guide",
        "Taro Yamada",
        ["Basics", "Design Tips", "Practical Patterns"],
    )
    print(report)
実行結果
Report: Python __str__ Guide
Author: Taro Yamada
Sections:
  1. Basics
  2. Design Tips
  3. Practical Patterns

このように複数行のフォーマットにすることで、構造化された情報をそのまま視覚的に理解できるようになります。

ログに出す場合は、扱いやすさとのバランスを考えながら採用するのがよいでしょう。

デバッグしやすい情報量と簡潔さのバランスの取り方

__str__に「すべての情報」を詰め込めば詳細にはなりますが、読み手にとってはノイズが多くなり、重要な情報が埋もれてしまいます

一方で、情報を削りすぎると、今度は何が起きているのか分からなくなってしまいます。

このジレンマを解決するひとつの考え方は、「__str__はサマリ、__repr__は詳細」という役割分担をすることです。

Python
class Session:
    def __init__(self, session_id, user_id, ip_address, user_agent, created_at, data):
        self.session_id = session_id
        self.user_id = user_id
        self.ip_address = ip_address
        self.user_agent = user_agent
        self.created_at = created_at
        self.data = data  # 辞書など大きなデータとする

    def __str__(self):
        # サマリ情報に限定します
        return (
            f"Session(id={self.session_id}, user_id={self.user_id}, "
            f"ip={self.ip_address}, created_at={self.created_at:%Y-%m-%d %H:%M:%S})"
        )

    def __repr__(self):
        # より詳細な情報(開発者向け)を含めます
        return (
            f"Session(session_id={self.session_id!r}, user_id={self.user_id!r}, "
            f"ip_address={self.ip_address!r}, user_agent={self.user_agent!r}, "
            f"created_at={self.created_at!r}, data={self.data!r})"
        )

このように設計すると、通常のログやUIでは__str__のサマリ表示を使い、repr()やデバッグ時には__repr__の詳細表示を確認するといった使い分けができます。

目的ごとに最適な情報量を決めておくことがバランスを取るコツです。

__str__メソッド活用の実践パターン

データクラス(dataclass)と__str__の活用

Python 3.7以降で使えるdataclassesモジュールは、データ保持用クラスのボイラープレートコードを自動生成してくれる仕組みです。

@dataclassを付けると、自動的に__init____repr__などが生成されます。

Python
from dataclasses import dataclass


@dataclass
class Point:
    x: int
    y: int


if __name__ == "__main__":
    p = Point(10, 20)
    print(p)         # dataclassが自動生成した__repr__が使われる
    print(str(p))    # __str__未定義なので__repr__がフォールバックされる
実行結果
Point(x=10, y=20)
Point(x=10, y=20)

データクラスでは、自動生成された__repr__はすでに開発者向けに十分役立つ形になっています。

そのうえで、ユーザー向けにカスタムの__str__を定義するのがよいパターンです。

Python
from dataclasses import dataclass


@dataclass
class UserProfile:
    user_id: int
    name: str
    age: int
    city: str

    def __str__(self):
        # プロフィールカード風の表示
        return f"{self.name} ({self.age}) - {self.city}"


if __name__ == "__main__":
    profile = UserProfile(1, "Alice", 30, "Tokyo")
    print(profile)          # __str__が呼ばれる
    print(repr(profile))    # dataclassの__repr__も利用可能
実行結果
Alice (30) - Tokyo
UserProfile(user_id=1, name='Alice', age=30, city='Tokyo')

このようにdataclass + カスタム__str__という組み合わせは、実務でも非常によく使われるパターンです。

既存クラスの継承時に__str__をオーバーライドするコツ

既存クラスを継承して新しいクラスを作るとき、__str__をオーバーライドして表示内容を拡張することがよくあります。

ここでのコツは、親クラスの__str__を再利用するかどうかを意識的に決めることです。

親の表示に「少しだけ情報を足したい」場合は、super().__str__()を呼び出すのが簡潔です。

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

    def __str__(self):
        return f"Person: {self.name}"


class Employee(Person):
    def __init__(self, name, employee_id, department):
        super().__init__(name)
        self.employee_id = employee_id
        self.department = department

    def __str__(self):
        # 親クラスの__str__を再利用しつつ、情報を付け足します
        base = super().__str__()  # "Person: <name>"
        return f"{base} (Employee ID: {self.employee_id}, Dept: {self.department})"


if __name__ == "__main__":
    e = Employee("Alice", "E123", "Engineering")
    print(e)
実行結果
Person: Alice (Employee ID: E123, Dept: Engineering)

一方で、子クラスではまったく別の表現にしたい場合は、親の__str__を使わずに、子クラスで独自のフォーマットを定義しても構いません。

継承階層全体で見たときの一貫性をどう保つかを意識しながら設計するとよいです。

ログ出力・例外メッセージでの__str__活用パターン

ログ出力では、loggingモジュールと__str__を組み合わせると、オブジェクトの内容を自然な形で記録できます。

Python
import logging

logging.basicConfig(level=logging.INFO)


class Order:
    def __init__(self, order_id, user_name, total_price):
        self.order_id = order_id
        self.user_name = user_name
        self.total_price = total_price

    def __str__(self):
        return f"Order#{self.order_id} by {self.user_name} ({self.total_price}円)"


if __name__ == "__main__":
    o = Order(1001, "Alice", 4500)
    logging.info("New order received: %s", o)  # %sで__str__が呼ばれる
実行結果
INFO:root:New order received: Order#1001 by Alice (4500円)

また、独自例外クラスを定義する際に__str__を実装しておくと、raiseしたときやログに例外情報を出力したときに、原因が分かりやすくなります。

Python
class InvalidOrderError(Exception):
    def __init__(self, order_id, reason):
        self.order_id = order_id
        self.reason = reason

    def __str__(self):
        # 例外メッセージとして、そのままユーザーにも見せられるよう意識します
        return f"Order#{self.order_id} is invalid: {self.reason}"


if __name__ == "__main__":
    try:
        raise InvalidOrderError(1002, "total price must be positive")
    except InvalidOrderError as e:
        print(e)  # __str__が呼ばれる
実行結果
Order#1002 is invalid: total price must be positive

このように、ログと例外メッセージの両方で__str__を設計しておくと、運用時のトラブルシューティングが格段に楽になります

テストコードで__str__を利用して可読性を高める方法

テストコードでは、テスト失敗時に出力されるメッセージの読みやすさが重要です。

assertEqualなどでオブジェクト同士を比較したとき、__str____repr__が読みやすく実装されていると、差分がひと目で分かるようになります。

Python
import unittest


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

    def __eq__(self, other):
        if not isinstance(other, User):
            return NotImplemented
        return self.user_id == other.user_id and self.name == other.name

    def __repr__(self):
        # unittestはrepr()を使って差分を表示することが多いので、
        # reprを丁寧に作っておくとテスト出力が分かりやすくなります
        return f"User(user_id={self.user_id!r}, name={self.name!r})"

    def __str__(self):
        # テストコード側でprint等する場合はこちらも有用です
        return f"{self.name} (id={self.user_id})"


class TestUser(unittest.TestCase):
    def test_user_equality(self):
        expected = User(1, "Alice")
        actual = User(1, "Alicia")  # nameが異なる

        # このassertが失敗したとき、repr()が分かりやすければ原因も分かりやすいです
        self.assertEqual(expected, actual)


if __name__ == "__main__":
    unittest.main()

失敗時の出力例(一部):

実行結果
AssertionError: User(user_id=1, name='Alice') != User(user_id=1, name='Alicia')

このように、テストで比較対象として使うクラスでは、特に__repr__と__str__を整えておくと、テストが保守しやすくなります

また、テストの中でprint(obj)してデバッグすることも多いため、__str__の存在も役に立ちます。

まとめ

__str__メソッドは、Pythonクラスの「顔」とも言える重要なメソッドです。

ユーザー目線で読みやすい文字列表現を設計することで、ログ、デバッグ、UI表示、テストなど、あらゆる場面で開発体験と運用性が向上します。

__repr__との役割分担を意識しつつ、f文字列や改行・インデントを活用して、オブジェクトごとに最適な表現を考えてみてください。

小さなクラスにも丁寧に__str__を実装する習慣が、プロジェクト全体の可読性と品質を底上げします。

クラスとオブジェクト指向

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!