閉じる

printで違いが出る__str__と__repr__についてわかりやすく解説

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” % xstr(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
# 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の機密が露出してしまっています

実運用では重大な事故につながります

デフォルト表示の確認

どちらも定義しないとデフォルト表記になります

Python
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を使い分けます

Python
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と簡易的な再帰検出を使います。

Python
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になる点を体感しておきましょう。

Python
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を見て確かめるという習慣を付けると、問題の切り分けが格段に速くなります。

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

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

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

URLをコピーしました!