Pythonではオブジェクトを文字列にする場面が非常に多くあります。
printやf文字列、ロギング、対話モードでの表示など、表現の仕方次第でデバッグ効率も可読性も大きく変わります。
本記事では__str__
と__repr__
の違いと設計指針を、初心者にも分かりやすく、実用的なサンプルつきで丁寧に解説します。
__str__と__repr__の基本と目的
Pythonオブジェクトを文字列にする仕組み
Pythonのオブジェクトは、ある場面では人間向けの読みやすい文字列に、別の場面では開発者向けの厳密な表現に変換されます。
この2つの役割を担うのが__str__
と__repr__
です。
前者はユーザーに見せるメッセージ、後者はデバッグや再現可能性を意識した表現を返します。
クラス定義時にこれらの特殊メソッドを実装すると、printやf文字列などの振る舞いが期待通りに整います。
用語のポイントとして、__str__(self)
は「読みやすさ」、__repr__(self)
は「正確さと再現性」を重視します。
どちらも実装しないと、デフォルトではクラス名とメモリアドレスのような情報だけが表示されます。
printやf文字列で使われる順序
printは基本的にstr(x)
を呼び、str
は未定義ならrepr(x)
にフォールバックします。
一方、repr(x)
は常に__repr__
を使います。
f文字列では、{x}
がstr(x)
、{x!r}
がrepr(x)
です。
コンテナ(list, dictなど)の中身は常にrepr
で表現される点も重要です。
以下の表で主な呼び出し元を整理します。
コンテキスト | 呼ばれる関数 | 備考 |
---|---|---|
print(x) | str(x) → なければ repr(x) | ユーザー向け表示 |
f”{x}” | str(x) | 既定は読みやすさ重視 |
f”{x!r}” | repr(x) | デバッグ時に便利 |
repr(x) | repr(x) | 厳密な表現 |
[x] や {x: …} の表示 | repr(x) | コンテナ要素はrepr固定 |
ログメッセージ “%s” % x | str(x) | %rを使うとrepr |
対話モードの評価結果 | repr(x) | 右辺値の自動表示はrepr |
「迷ったら!r
を付けるとデバッグしやすくなる」という実践知は覚えておくと便利です。
対話モードとエラーログでの表示の違い
対話モード(REPL)では、式の評価結果は常にrepr
で表示されます。
エラーやスタックトレースでも、引数やコレクション内の要素はrepr表記で出力されやすく、引用符やエスケープが分かるため原因追跡に役立ちます。
ユーザー向けの説明文はstr
で整える、開発者向けの診断はrepr
で厳密に、という住み分けが基本です。
違いと使い分けの要点
__str__はユーザー向けの読みやすさ重視
__str__
は短く、自然言語寄りで、文脈説明に適した文字列を返します。
UI、ログのヘッダー、ユーザー宛メッセージなどで「意味がすぐ伝わる」ことを優先します。
たとえば「山田太郎(29)」のような要約表現が好まれます。
__repr__は開発者向けの再現可能性重視
__repr__
は「その文字列から可能なら同じオブジェクトを再構築できる」方針(再現可能性)を目標にします。
完全に満たせない場合でも、型名と主要属性が明確に分かることが重要です。
属性はname='Taro'
のようにキー付きで並べ、引用とエスケープが分かるようにするのが一般的です。
どちらも無い時のデフォルト表示
どちらも実装しない場合、print(x)
もrepr(x)
も「<ClassName object at 0x...>
」の形式になります。
さらに、__str__
だけ未実装なら、str(x)
はrepr(x)
にフォールバックします。
つまりまず__repr__
を整えると、最低限の表示品質がすぐ確保できます。
実装の指針とベストプラクティス
まず__repr__を定義し情報を十分に含める
優先順位は__repr__
→__str__
の順です。
再現可能性と診断に必要な情報(型名、識別子、主要属性)を含めます。
文字列や辞書などは!r
でネストしたreprを活用し、曖昧さを避けます。
__repr__に型名と主要属性を入れる
「ClassName(field=value, ...)
」の形が分かりやすく、慣習にも沿っています。
属性順序は読み手が予測しやすいよう、コンストラクタ引数の順や辞書順に揃えます。
IDや状態フラグなど、原因調査に必要な最小限を選びます。
__str__は要約を返し省略表示を工夫する
__str__
は短く要約し、場面に応じて省略記号(…)を使うと読みやすくなります。
表示カラム幅やUI制約に合わせた「切り詰め」も検討します。
詳細はreprで辿れるため、strは「ひと目で分かる」ことを重視します。
パフォーマンスと機密情報への配慮
重い計算、大量データの完全展開、機密情報の露出は避けます。
特にアクセストークンやパスワードをreprに含めるのは厳禁です。
長いデータは長さだけを出したり、reprlib
で短縮したり、***
でマスクします。
__str__/__repr__は例外を投げない実装が望ましく、ログ中の表示でさらに例外を招くことを防ぎます。
サンプルとチェックリスト
良い__repr__と悪い__repr__の比較
ここではシンプルなクラスを使い、良い実装と悪い実装の違いを示します。
良い__repr__
は型名と属性が明確で、機密を出さず、__str__
は要約に徹します。
# Python 3.x
class User:
# オブジェクト生成時に初期設定する (__init__)
def __init__(self, name: str, age: int, token: str) -> None:
# selfはインスタンス自身を指す
self.name = name # インスタンス変数
self.age = age
self.token = token # 機密情報(表示しない)
def __repr__(self) -> str:
# 良いrepr: 型名 + 主要属性、機密は出さない。文字列は!rでrepr表示。
return f"User(name={self.name!r}, age={self.age})"
def __str__(self) -> str:
# 読みやすい要約表示(ユーザー向け)
return f"{self.name} ({self.age})"
# 悪い例: 機密を出す、情報が曖昧、型名が分からない、など
class BadUser:
def __init__(self, name: str, age: int, token: str) -> None:
self.name = name
self.age = age
self.token = token
def __repr__(self) -> str:
# 悪いrepr: 情報が少ない上に機密を漏洩している
return f"name={self.name}, age={self.age}, token={self.token}"
# __str__未定義 → str(obj)はrepr(obj)にフォールバック
# (printでもこのreprが使われてしまい危険)
u = User("Taro", 29, token="SECRET-123456")
b = BadUser("Jiro", 31, token="TOP-SECRET-ABC")
print(u) # __str__が使われる
print(repr(u)) # __repr__が使われる
print(f"{u}") # __str__
print(f"{u!r}") # __repr__
print([u, b]) # コンテナ表示は要素のreprを使う点に注意
Taro (29)
User(name='Taro', age=29)
Taro (29)
User(name='Taro', age=29)
[User(name='Taro', age=29), name=Jiro, age=31, token=TOP-SECRET-ABC]
コンテナの表示では要素のrepr
が使われるため、BadUser
の機密が露出してしまっています。
実運用では重大な事故につながります。
デフォルト表示の確認
どちらも定義しないとデフォルト表記になります。
class Plain:
pass
p = Plain()
print(p)
print(repr(p))
<__main__.Plain object at 0x7f...>
<__main__.Plain object at 0x7f...>
デバッグとロギングでの活用ポイント
ログやf文字列での使い分けは実務で差が出ます。
デバッグでは!r
や%r
、ユーザー通知では!s
や%s
を使い分けます。
import logging
class User:
# オブジェクト生成時に初期設定する (__init__)
def __init__(self, name: str, age: int, token: str) -> None:
# selfはインスタンス自身を指す
self.name = name # インスタンス変数
self.age = age
self.token = token # 機密情報(表示しない)
def __repr__(self) -> str:
# 良いrepr: 型名 + 主要属性、機密は出さない。文字列は!rでrepr表示。
return f"User(name={self.name!r}, age={self.age})"
def __str__(self) -> str:
# 読みやすい要約表示(ユーザー向け)
return f"{self.name} ({self.age})"
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
u = User("Taro", 29, token="SECRET-123456")
# ユーザー向け: 読みやすいstr
logging.info("処理対象ユーザー: %s", u) # %s → str(u)
# 開発者向け: 詳細なrepr
logging.info("詳細(デバッグ用): %r", u) # %r → repr(u)
# f文字列でも同様
print(f"対象: {u}") # __str__
print(f"詳細: {u!r}") # __repr__
INFO: 処理対象ユーザー: Taro (29)
INFO: 詳細(デバッグ用): User(name='Taro', age=29)
対象: Taro (29)
詳細: User(name='Taro', age=29)
「普段はstr
、調査時はrepr
」という切り替えができるよう、両方を適切に実装するのがコツです。
長いデータや循環参照への対処
大量データや参照が循環する構造では、短縮表示や再帰ガードが必要です。
ここではreprlib
と簡易的な再帰検出を使います。
import reprlib
from typing import Optional
class DataPacket:
def __init__(self, title: str, payload: str) -> None:
self.title = title
self.payload = payload # 大きい文字列を想定
def __repr__(self) -> str:
# 長い文字列はreprlibで短縮し、サイズも併記する
r = reprlib.Repr()
r.maxstring = 30 # 上限を調整可能
payload_preview = r.repr(self.payload)
return f"DataPacket(title={self.title!r}, payload={payload_preview}, size={len(self.payload)})"
def __str__(self) -> str:
# 要約: タイトルとサイズだけ
return f"{self.title} [{len(self.payload)} bytes]"
# 再帰的なリンクを持つノード
class Node:
def __init__(self, value: int, next: Optional["Node"]=None) -> None:
self.value = value
self.next = next
def __repr__(self) -> str:
# 簡易的な再帰ガード:
# 自己参照っぽいときは...で省略
nxt = "..." if self.next is self else repr(self.next)
return f"Node(value={self.value}, next={nxt})"
def __str__(self) -> str:
# 要約表示
return f"Node[{self.value}]"
packet = DataPacket("snapshot", "X" * 120)
n = Node(1)
n.next = n # 自己参照
print(packet) # __str__
print(repr(packet)) # __repr__ 短縮を確認
print(n) # __str__
print(repr(n)) # __repr__ 再帰省略を確認
snapshot [120 bytes]
DataPacket(title='snapshot', payload='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX...', size=120)
Node[1]
Node(value=1, next=...)
大きなデータはサイズや先頭のみを示すと、ログのノイズを抑えつつ有用な手がかりを残せます。
より複雑な再帰検出にはスレッドローカルやWeakSet
を使う実装もあります。
コンテナ内表示と!rの合わせ技
コンテナ表示は常に要素のrepr
になる点を体感しておきましょう。
users = [User("A", 20, "tok1"), User("B", 30, "tok2")]
print(users) # reprで並ぶ
print(", ".join(str(u) for u in users)) # 必要なら自分でstrを明示
print(f"{users!r}") # f文字列でも!rでreprを強制
[User(name='A', age=20), User(name='B', age=30)]
A (20), B (30)
[User(name='A', age=20), User(name='B', age=30)]
実装チェックリスト
短いチェックで質を担保できます。
次の観点を満たすと安全で使いやすい表示になります。
__repr__
は型名と主要属性をキー付きで示すか。- 機密情報や巨大データをそのまま出していないか。
__str__
は要約で短く、UIに載せても読みやすいか。- 例外を投げる可能性のある処理を避けているか(ファイルI/O、ネットワーク呼び出しなど)。
- コンテナ表示やf文字列
!r
での見え方をテストしたか。
まとめ
__str__
は「読みやすい要約」、__repr__
は「開発者向けの厳密な手がかり」という役割分担をまず押さえることが大切です。
printやf文字列、コンテナ表示、対話モードでの違いを理解し、まず__repr__
をしっかり設計してから__str__
で要約を足す方針にすると、デバッグ効率と安全性が大きく向上します。
長いデータは短縮し、機密はマスクし、例外を出さない堅牢な実装を心がけてください。
最後にもう一度、迷ったら!r
でreprを見て確かめるという習慣を付けると、問題の切り分けが格段に速くなります。