閉じる

printやf-stringで効くstrの実装と注意点(Python)

Pythonで自作クラスのオブジェクトを人間が読みやすい文字列にしたいときは__str__を実装します。

この記事ではprintやf-stringで正しく効く書き方落とし穴の回避を、具体例とテストのしかたまで段階的に解説します。

短く安定した表現を目指し、ロギングでも安心して使える実装に仕上げます。

Pythonの__str__とは

目的: オブジェクトの人間向け文字列表現

__str__人間が読むためのやさしい説明文を返す特殊メソッドです。

たとえば金額やイベント日時、簡潔な状態などを1行でまとめます。

ユーザー向け表示、ログ、診断メッセージに向いています。

逆に開発者向けの厳密な表現は__repr__の役目です。

未実装時の表示と限界(デフォルトの__str__)

__str__を実装しない場合、デフォルトでは型名とメモリアドレスのような機械的な文字列になります。

読みやすさに欠け、ユーザー向けには不親切です。

Python
# __str__未実装の例
class Ticket:
    def __init__(self, event, price):
        self.event = event
        self.price = price

t = Ticket("PyCon", 12000)
print(t)  # デフォルトの表示
実行結果
# 出力例: 実行環境によりメモリアドレス部分は異なります
<__main__.Ticket object at 0x10f4c8cd0>

何がいくらのチケットなのかが伝わらず、デバッグやログでも有用性が下がります。

__str__が呼ばれる場面(print, str, f-string)

print(obj)での呼び出し

print(obj)は内部でstr(obj)を呼びます。

つまり__str__があればそれが採用されます。

Python
class Ticket:
    def __init__(self, event, price):
        self.event = event
        self.price = price
    def __str__(self) -> str:
        # 人間に優しい表現
        return f"Ticket(event={self.event}, price={self.price} JPY)"

t = Ticket("PyCon", 12000)
print(t)
実行結果
Ticket(event=PyCon, price=12000 JPY)

str(obj)との関係

str(obj)同じく__str__を使います。

未実装なら__repr__にフォールバックします。

Python
print(str(t))  # printと同じ結果
実行結果
Ticket(event=PyCon, price=12000 JPY)

f-string(f”{obj}”)での評価

f"{obj}"は基本的にformat(obj)を呼び、デフォルトでは__str__に委ねます。

フォーマット指定子がある場合(例: f"{dt:%Y-%m-%d}")は__format__が関与しますが、未定義なら__str__に戻ります。

Python
print(f"{t}")  # __str__が使われる
実行結果
Ticket(event=PyCon, price=12000 JPY)

ロギングやフォーマットでの使われ方

loggingでは、書式が"%s"なら__str__"%r"なら__repr__が使われます。

ユーザー向けログは%s、開発者向け詳細は%rを使い分けます。

Python
import logging
logging.basicConfig(level=logging.INFO)

logging.info("Purchased: %s", t)  # __str__
logging.info("Debug: %r", t)     # __repr__ (未定義ならobjectのrepr)
実行結果
INFO:root:Purchased: Ticket(event=PyCon, price=12000 JPY)
INFO:root:Debug: <__main__.Ticket object at 0x...>

参考までに、"{}".format(obj)"%s" % obj__str__を利用します。

__str__の実装手順と例(Python)

基本シグネチャと戻り値はstr型のみ

__str__引数なし(ただしselfは受け取る)必ずstrを返す必要があります。

整数やNoneを返すとTypeErrorになります。

Python
class Event:
    def __init__(self, name, start_dt, place, seats):
        self.name = name
        self.start_dt = start_dt
        self.place = place
        self.seats = seats

    def __str__(self) -> str:
        # 常にstrを返すこと
        return f"Event(name={self.name}, start={self.start_dt}, place={self.place}, seats={self.seats})"

返り値がstrでないときのエラー例

Python
class Bad:
    def __str__(self):  # -> str を付けても付けなくても、返す型が重要
        return 123  # 間違い: strではない

print(str(Bad()))
実行結果
TypeError: __str__ returned non-string (type int)

フィールドを読みやすく整形する

短く、必要十分な情報に絞ります。

区切り文字、単位、ラベルを整え、読者が迷わないようにします。

Python
from datetime import datetime

class Ticket:
    def __init__(self, event, price_jpy, issued_at):
        self.event = event
        self.price_jpy = price_jpy
        self.issued_at = issued_at

    def __str__(self) -> str:
        # ユーザーが読める1行要約
        # - 価格はカンマ区切り
        # - 日時はYYYY-MM-DD HH:MM
        price = f"{self.price_jpy:,} JPY"
        issued = self.issued_at.strftime("%Y-%m-%d %H:%M")
        return f"Ticket[{self.event}] {price} (issued {issued})"

t = Ticket("PyCon", 12000, datetime(2025, 5, 1, 9, 30))
print(t)
実行結果
Ticket[PyCon] 12,000 JPY (issued 2025-05-01 09:30)

無限再帰を避ける(f”{self}”を使わない)

__str__の中でf"{self}"str(self)を使うと、自分自身を再び文字列化して無限再帰になります。

クラス名が欲しいときはself.__class__.__name__を使います。

Python
class BadLoop:
    def __str__(self):
        # return f"BadLoop: {self}"  # これは無限再帰になる
        cls = self.__class__.__name__
        return f"{cls}(ok)"
実行結果
# 問題回避: "BadLoop(ok)" のように安全に出力できる

数値や日付のフォーマット(簡潔に)

細かい整形は必要最小限に留め、冗長にならないよう注意します。

必要ならフォーマット指定子を使います。

Python
from datetime import date

class Invoice:
    def __init__(self, number, total, due):
        self.number = number
        self.total = total
        self.due = due

    def __str__(self) -> str:
        # 金額は2桁小数、請求番号短縮、期日はISO風
        return f"Invoice#{self.number} total={self.total:,.2f} JPY due={self.due:%Y-%m-%d}"

inv = Invoice("2025-0012", 98765.4, date(2025, 6, 30))
print(inv)
実行結果
Invoice#2025-0012 total=98,765.40 JPY due=2025-06-30

__str__の注意点とテスト

副作用を避ける(計算やI/Oはしない)

__str__は軽量であるべきです。

ネットワークやファイルアクセス、時間のかかる計算は行いません。

遅い__str__はログやデバッグ表示でアプリ全体のレスポンスを悪化させます。

避けたい例と改善

Python
class Slow:
    def __str__(self):
        # NG: ファイル読込やDBアクセスなどのI/Oは行わない
        # with open("large.txt") as f: ...
        return "Slow(...)"

class Fast:
    def __str__(self):
        # OK: 既存の属性をシンプルにまとめるだけ
        return "Fast(...)"

例外を出さない設計(安全に失敗させる)

__str__が例外を投げると、さらに上位のログや診断まで壊れます

想定外の属性値があっても安全にフォールバックしましょう。

Python
class SafeTicket:
    def __init__(self, event, price_jpy, issued_at):
        self.event = event
        self.price_jpy = price_jpy
        self.issued_at = issued_at

    def __str__(self) -> str:
        try:
            price = f"{int(self.price_jpy):,} JPY"
        except Exception:
            price = "<n/a>"
        try:
            issued = self.issued_at.strftime("%Y-%m-%d %H:%M")
        except Exception:
            issued = "<unknown>"
        return f"Ticket[{self.event}] {price} (issued {self.issued_at and issued})"

print(SafeTicket("PyCon", "12000", None))
実行結果
Ticket[PyCon] 12,000 JPY (issued None)

完璧でなくても落ちないことが重要です。

必要なら[invalid]など明示的な印を入れてもよいでしょう。

表現は短く安定させる(仕様化)

表示は短く、順序と形式を固定すると、ログ検索やテストが安定します。

将来の変更は互換性に配慮し、変更点を明文化します。

長文化しそうなら__repr__や専用のto_dict()を別途用意します。

テスト方法(assertとf-stringで確認)

期待文字列を固定して比較します。

f"{obj}"str(obj)を使い、重要なケースをカバーしましょう。

Python
import unittest
from datetime import datetime

class Ticket:
    def __init__(self, event, price, issued_at):
        self.event = event
        self.price = price
        self.issued_at = issued_at
    def __str__(self) -> str:
        return f"Ticket[{self.event}] {self.price:,} JPY (issued {self.issued_at:%Y-%m-%d})"

class TestStr(unittest.TestCase):
    def test_str_basic(self):
        t = Ticket("PyCon", 12000, datetime(2025, 5, 1))
        self.assertEqual(str(t), "Ticket[PyCon] 12,000 JPY (issued 2025-05-01)")
    def test_fstring(self):
        t = Ticket("DjangoCon", 8000, datetime(2025, 9, 10))
        self.assertEqual(f"{t}", "Ticket[DjangoCon] 8,000 JPY (issued 2025-09-10)")

if __name__ == "__main__":
    unittest.main(argv=["-v"], exit=False)
実行結果
test_fstring (__main__.TestStr) ... ok
test_str_basic (__main__.TestStr) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

よくあるエラー(TypeErrorやパフォーマンス)

  • TypeError: __str__str以外を返すと発生します。必ず文字列に整形してください。
  • 無限再帰: __str__内でstr(self)f"{self}"を呼ぶと再帰します。クラス名はself.__class__.__name__で取得します。
  • 遅さ: 重い処理や大量データの連結は避けます。必要なら要約に留め、詳細は別メソッドで提供します。

参考として、場面ごとの呼び出し先を簡単にまとめます。

呼び出し元使用されるメソッド備考
print(obj)__str__実質的にstr(obj)
str(obj)__str__未実装なら__repr__
f”{obj}”__format__→未定義なら__str__書式指定子ありで動作変化
logging “%s”__str__ユーザー向け
logging “%r”__repr__開発者向け

「誰に何を伝えるのか」を意識すれば、短く役立つ文字列表現が作れます。

まとめ

__str__は人間向けの1行要約を返すメソッドです。

print、str、f-string、loggingで広く使われるため、戻り値は必ずstrにし、無限再帰不要な副作用を避けてください。

数値や日付は見やすく整形し、例外時は安全にフォールバックします。

テストではassertで期待文字列と比較し、表現を短く安定させることで運用やデバッグのコストを下げられます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

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

URLをコピーしました!