Pythonで自作クラスのオブジェクトを人間が読みやすい文字列にしたいときは__str__
を実装します。
この記事ではprintやf-stringで正しく効く書き方と落とし穴の回避を、具体例とテストのしかたまで段階的に解説します。
短く安定した表現を目指し、ロギングでも安心して使える実装に仕上げます。
Pythonの__str__とは
目的: オブジェクトの人間向け文字列表現
__str__
は人間が読むためのやさしい説明文を返す特殊メソッドです。
たとえば金額やイベント日時、簡潔な状態などを1行でまとめます。
ユーザー向け表示、ログ、診断メッセージに向いています。
逆に開発者向けの厳密な表現は__repr__
の役目です。
未実装時の表示と限界(デフォルトの__str__)
__str__
を実装しない場合、デフォルトでは型名とメモリアドレスのような機械的な文字列になります。
読みやすさに欠け、ユーザー向けには不親切です。
# __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__
があればそれが採用されます。
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__
にフォールバックします。
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__
に戻ります。
print(f"{t}") # __str__が使われる
Ticket(event=PyCon, price=12000 JPY)
ロギングやフォーマットでの使われ方
loggingでは、書式が"%s"
なら__str__
、"%r"
なら__repr__
が使われます。
ユーザー向けログは%s
、開発者向け詳細は%r
を使い分けます。
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になります。
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でないときのエラー例
class Bad:
def __str__(self): # -> str を付けても付けなくても、返す型が重要
return 123 # 間違い: strではない
print(str(Bad()))
TypeError: __str__ returned non-string (type int)
フィールドを読みやすく整形する
短く、必要十分な情報に絞ります。
区切り文字、単位、ラベルを整え、読者が迷わないようにします。
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__
を使います。
class BadLoop:
def __str__(self):
# return f"BadLoop: {self}" # これは無限再帰になる
cls = self.__class__.__name__
return f"{cls}(ok)"
# 問題回避: "BadLoop(ok)" のように安全に出力できる
数値や日付のフォーマット(簡潔に)
細かい整形は必要最小限に留め、冗長にならないよう注意します。
必要ならフォーマット指定子を使います。
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__
はログやデバッグ表示でアプリ全体のレスポンスを悪化させます。
避けたい例と改善
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__が例外を投げると、さらに上位のログや診断まで壊れます。
想定外の属性値があっても安全にフォールバックしましょう。
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)
を使い、重要なケースをカバーしましょう。
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
で期待文字列と比較し、表現を短く安定させることで運用やデバッグのコストを下げられます。