__str__ は「人に見せるための文字列」、__repr__ は「開発者が中身を確かめるための文字列」を返す特殊メソッドです。 print() や str() は __str__ を、repr()・対話シェル・リストや辞書の中の表示は __repr__ を呼びます。そして __str__ を定義していないクラスでは、print() も __repr__ の結果を使います。

この記事では、print・str()・f文字列・f文字列の !r・repr()・対話シェル・リストや辞書の中・logging・例外メッセージのそれぞれでどちらが呼ばれるかを、Python 3.10.18/3.11.15/3.14.7で実行して早見表にしました。片方だけ定義した場合や <__main__.X object at 0x...> と表示される場合の4パターン、__init__・__eq__・__hash__・__lt__・__len__・__getitem__・__iter__・__enter__ などを「いつ呼ばれるか」で整理した特殊メソッド一覧、dataclass が自動で作るメソッドの対応表まで、コピーして動かせるコードつきでまとめています。

どちらが呼ばれるかの早見表

次のクラスのインスタンス b を、いろいろな方法で表示した結果です(Python 3.11.15。3.10.18と3.14.7でも同じ出力でした)。

  • __str__ の戻り値:『Python入門』2800円
  • __repr__ の戻り値:Book(title='Python入門', price=2800)
表示の方法 コード 呼ばれるもの 実行結果
print print(b) __str__ 『Python入門』2800円
str() str(b) __str__ 『Python入門』2800円
repr() repr(b) __repr__ Book(title='Python入門', price=2800)
f文字列 f"{b}" __str__ 『Python入門』2800円
f文字列の !r f"{b!r}" __repr__ Book(title='Python入門', price=2800)
f文字列の = f"{b=}" __repr__ b=Book(title='Python入門', price=2800)
format() "{}".format(b) __str__ 『Python入門』2800円
% 書式 "%s / %r" % (b, b) %s は __str__、%r は __repr__ 『Python入門』2800円 / Book(title='Python入門', price=2800)
対話シェルで変数名だけ入力 >>> b __repr__ Book('Python入門')(後述の別クラスでの結果)
リストの中 print([b]) __repr__ [Book(title='Python入門', price=2800)]
辞書の中 print({"key": b}) __repr__ {'key': Book(title='Python入門', price=2800)}
タプルの中 print((b,)) __repr__ (Book(title='Python入門', price=2800),)
logging の %s logging.info("%s", b) __str__ 『Python入門』2800円
logging の %r logging.info("%r", b) __repr__ Book(title='Python入門', price=2800)
例外メッセージ str(ValueError(b)) __str__ 『Python入門』2800円
例外の repr repr(ValueError(b)) __repr__ ValueError(Book(title='Python入門', price=2800))
KeyError のメッセージ str(KeyError(b)) __repr__ Book(title='Python入門', price=2800)

覚え方は「直接表示するときは __str__、何かの中に入っているときと、開発者向けの表示は __repr__」 です。

定義の有無による違いは次の4パターンです(詳しい実行結果は後の章にあります)。

定義したもの print(obj) repr(obj)・リストの中
どちらも定義しない <__main__.Neither object at 0x...> <__main__.Neither object at 0x...>
__str__ だけ __str__ の結果 <__main__.OnlyStr object at 0x...>
__repr__ だけ __repr__ の結果 __repr__ の結果
両方 __str__ の結果 __repr__ の結果

1つだけ書くなら __repr__ です。print でもリストの中でも読める表示になります。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python __str__と__repr__の違い|特殊メソッド一覧」

確認した環境

項目 内容
OS Linux(コンテナ)
主に使った版 Python 3.11.15
比べた版 Python 3.10.18/3.14.7
使ったもの 標準ライブラリのみ(logging、functools、dataclasses)

とくに断りのない出力は Python 3.11.15 のものです。0x7f... のアドレスは実行のたびに変わります。トレースバック内のファイルパスは短い名前に置き換えています。

早見表のもとになったコードと実行結果

早見表は次のコードの出力から作りました。そのまま貼って動きます。

import logging, sys

class Book:
    def __init__(self, title, price):
        self.title = title
        self.price = price

    def __str__(self):
        return f"『{self.title}』{self.price}円"

    def __repr__(self):
        return f"Book(title={self.title!r}, price={self.price!r})"


b = Book("Python入門", 2800)

print("1 print      :", end=" "); print(b)
print("2 str()      :", str(b))
print("3 repr()     :", repr(b))
print("4 f文字列    :", f"{b}")
print("5 f文字列 !r :", f"{b!r}")
print("6 f文字列 !s :", f"{b!s}")
print("7 f文字列 =  :", f"{b=}")
print("8 format()   :", "{}".format(b), "/", "{!r}".format(b), "/", format(b))
print("9 % 書式     :", "%s / %r" % (b, b))
print("10 リスト    :", [b])
print("11 辞書      :", {"key": b})
print("12 タプル    :", (b,))
print("13 str(list) :", str([b]))
print("14 join      :", ", ".join(map(str, [b, b])))
print("15 文字列連結:", "本: " + str(b))

logging.basicConfig(format="%(levelname)s %(message)s", stream=sys.stdout, level=logging.INFO)
logging.info("16 logging %%s: %s", b)
logging.info("17 logging %%r: %r", b)
logging.info(b)

try:
    raise ValueError(b)
except ValueError as e:
    print("19 例外 str(e) :", str(e))
    print("20 例外 repr(e):", repr(e))
    print("21 例外 e.args :", e.args)

try:
    raise KeyError(b)
except KeyError as e:
    print("22 KeyError str(e):", str(e))

try:
    assert False, b
except AssertionError as e:
    print("23 assert      :", e)

try:
    "x" + b
except TypeError as e:
    print("24 連結エラー  :", e)

実行結果:

1 print      : 『Python入門』2800円
2 str()      : 『Python入門』2800円
3 repr()     : Book(title='Python入門', price=2800)
4 f文字列    : 『Python入門』2800円
5 f文字列 !r : Book(title='Python入門', price=2800)
6 f文字列 !s : 『Python入門』2800円
7 f文字列 =  : b=Book(title='Python入門', price=2800)
8 format()   : 『Python入門』2800円 / Book(title='Python入門', price=2800) / 『Python入門』2800円
9 % 書式     : 『Python入門』2800円 / Book(title='Python入門', price=2800)
10 リスト    : [Book(title='Python入門', price=2800)]
11 辞書      : {'key': Book(title='Python入門', price=2800)}
12 タプル    : (Book(title='Python入門', price=2800),)
13 str(list) : [Book(title='Python入門', price=2800)]
14 join      : 『Python入門』2800円, 『Python入門』2800円
15 文字列連結: 本: 『Python入門』2800円
INFO 16 logging %s: 『Python入門』2800円
INFO 17 logging %r: Book(title='Python入門', price=2800)
INFO 『Python入門』2800円
19 例外 str(e) : 『Python入門』2800円
20 例外 repr(e): ValueError(Book(title='Python入門', price=2800))
21 例外 e.args : (Book(title='Python入門', price=2800),)
22 KeyError str(e): Book(title='Python入門', price=2800)
23 assert      : 『Python入門』2800円
24 連結エラー  : can only concatenate str (not "Book") to str

気づきにくい点を補足します。

番号 補足
7 f"{b=}" は「式=値」を表示する書き方で、値の側は __repr__ が使われる。確認用の出力に向いている
10〜13 リスト・辞書・タプルは、自分自身を文字列にするとき要素の __repr__ を呼ぶ。str([b]) でも同じ
14 要素を __str__ で並べたいときは ", ".join(map(str, リスト))
18(logging.info(b)) メッセージにオブジェクトをそのまま渡すと __str__ が使われる
22 KeyError は見つからなかったキーを repr で表示するため、__repr__ の結果になる
24 "x" + b は自動では文字列にならず TypeError。str(b) で明示的に変換する

例外を送出したときのトレースバック

例外にオブジェクトを渡してそのまま送出すると、最終行には __str__ の結果が出ます。

class Book:
    def __init__(self, title):
        self.title = title
    def __str__(self):
        return f"『{self.title}』"
    def __repr__(self):
        return f"Book(title={self.title!r})"

raise ValueError(Book("Python入門"))
Traceback (most recent call last):
  File "book.py", line 9, in <module>
    raise ValueError(Book("Python入門"))
ValueError: 『Python入門』

対話シェルでの表示

対話シェル(python3 を引数なしで起動した画面)では、変数名だけを入力すると __repr__ が使われます。Python 3.11.15で次の入力を流した結果です。

>>> class Book:
...     def __init__(self, t): self.t = t
...     def __str__(self): return f"『{self.t}』"
...     def __repr__(self): return f"Book({self.t!r})"
...
>>> b = Book("Python入門")
>>> b
Book('Python入門')
>>> print(b)
『Python入門』
>>> [b]
[Book('Python入門')]
>>> str(b)
'『Python入門』'

最後の str(b) の結果に引用符が付いているのは、対話シェルが「str(b) が返した文字列」をさらに repr で表示しているためです。対話シェルやデバッガで変数を確かめるときに見えるのは __repr__、と覚えておくと混乱しません。3.13以降の新しい対話シェルでの表示は今回は未実行です。

4パターンで比べる:定義の有無と表示

__str__ と __repr__ を「どちらも無し/片方だけ/両方」の4通りで定義して比べます。

class Neither:
    def __init__(self, v): self.v = v

class OnlyStr:
    def __init__(self, v): self.v = v
    def __str__(self): return f"str:{self.v}"

class OnlyRepr:
    def __init__(self, v): self.v = v
    def __repr__(self): return f"OnlyRepr({self.v!r})"

class Both:
    def __init__(self, v): self.v = v
    def __str__(self): return f"str:{self.v}"
    def __repr__(self): return f"Both({self.v!r})"

for cls in (Neither, OnlyStr, OnlyRepr, Both):
    o = cls(1)
    print(f"--- {cls.__name__}")
    print("print :", o)
    print("repr  :", repr(o))
    print("list  :", [o])
--- Neither
print : <__main__.Neither object at 0x7f6386d2a7d0>
repr  : <__main__.Neither object at 0x7f6386d2a7d0>
list  : [<__main__.Neither object at 0x7f6386d2a7d0>]
--- OnlyStr
print : str:1
repr  : <__main__.OnlyStr object at 0x7f6386d2a810>
list  : [<__main__.OnlyStr object at 0x7f6386d2a810>]
--- OnlyRepr
print : OnlyRepr(1)
repr  : OnlyRepr(1)
list  : [OnlyRepr(1)]
--- Both
print : str:1
repr  : Both(1)
list  : [Both(1)]

なぜこうなるのか

仕組みは2つのルールで説明できます。

  1. すべてのクラスは object から既定の __repr__ を受け継いでいる。 その既定の実装が <モジュール名.クラス名 object at アドレス> という文字列を返す
  2. 既定の __str__ は __repr__ を呼ぶ。 そのため __str__ を書かなければ、print の結果は __repr__ と同じになる

Python公式ドキュメントのデータモデルのページ(今回開いて確認)にも、__str__ が定義されていない場合は __repr__ が使われること、__repr__ はできれば「同じ値のオブジェクトを作り直せるPythonの式」に見える文字列にすること、が書かれています。

パターン 起きること 困りやすい場面
どちらも無し どこでも <... object at 0x...> 中身がまったくわからない
__str__ だけ print は読めるが、リストの中やデバッガでは <... object at 0x...> のまま 一覧を表示したときに中身が見えない
__repr__ だけ どこでも __repr__ の結果 利用者向けの見た目を別にしたいときだけ物足りない
両方 場面に応じて使い分けられる ―

「print(obj) では読めるのに、print(リスト) にすると <__main__.X object at 0x...> が並ぶ」という現象は、__str__ だけを定義しているときに起きます。__repr__ を足せば解決します。

読みやすい __repr__ と __str__ の書き方

__repr__ のひな形

__repr__ は次の形にしておくと、どのクラスにもそのまま使えます。

class Member:
    def __init__(self, name, tags):
        self.name, self.tags = name, tags

    def __repr__(self):
        return f"{type(self).__name__}(name={self.name!r}, tags={self.tags!r})"

m = Member("佐藤", ["a", "b"])
print(repr(m))
print(eval(repr(m)).name)
Member(name='佐藤', tags=['a', 'b'])
佐藤
書き方のポイント 理由
クラス名は type(self).__name__ で出す 子クラスを作ったとき、子クラスの名前で表示される
各属性に !r を付ける 文字列に引用符が付き、'1' と 1、空文字と空白の区別がつく
クラス名(引数=値, ...) の形にする そのままコードに貼ってオブジェクトを作り直せる
1行に収める ログや一覧で読みやすい

上の例では eval(repr(m)) で同じ内容のオブジェクトを作り直せています。これは「作り直せる形になっている」ことを見せるための確認で、ふだんのコードで外から来た文字列を eval に渡すことはしません。

標準の型も同じ考え方で作られている

import datetime
d = datetime.date(2026, 10, 3)
print(str(d), "|", repr(d))
print(repr("ab\n"), repr(1.0), str(1.0))
2026-10-03 | datetime.date(2026, 10, 3)
'ab\n' 1.0 1.0

str は読みやすい表示(2026-10-03)、repr は作り直せる式(datetime.date(2026, 10, 3))です。文字列の repr は引用符付きで、改行が \n と見える形になります。

比べる点 __str__ __repr__
読む人 利用者、画面や帳票を見る人 開発者、ログやデバッガを見る人
目指す形 自然で読みやすい あいまいさが無く、できれば作り直せる式
省略したとき __repr__ が使われる <... object at 0x...>
書く優先度 必要になってから まずこちら

f文字列で幅や寄せを指定するとき

自作クラスのインスタンスに :>12 のような書式指定を付けると、TypeError になります。!s か str() を挟めば、文字列として書式指定できます。

class Book:
    def __init__(self, title):
        self.title = title
    def __str__(self):
        return f"『{self.title}』"
    def __repr__(self):
        return f"Book({self.title!r})"

b = Book("Python入門")
try:
    print(f"[{b:>12}]")
except TypeError as e:
    print("TypeError:", e)
print(f"[{b!s:>12}]")
print(f"[{b!r:>20}]")
TypeError: unsupported format string passed to Book.__format__
[  『Python入門』]
[    Book('Python入門')]

書式指定は __format__ という別の特殊メソッドが受け取ります。既定の __format__ は、指定が空のときだけ str() と同じ結果を返すので、幅や寄せを指定したいときは !s/!r で先に文字列へ変換します。

戻り値は必ず文字列にする

__str__ や __repr__ が文字列以外を返すと、次のエラーになります。

class Bad:
    def __str__(self):
        return 123
try:
    print(Bad())
except TypeError as e:
    print("TypeError:", e)
TypeError: __str__ returned non-string (type int)

数値などを返したくなったら str(...) で包むか、f文字列にします。

特殊メソッド一覧:いつ呼ばれるかで整理

__str__ や __repr__ のように、前後を下線2つで囲んだメソッドを特殊メソッド(ダンダーメソッドとも呼びます)と言います。自分で直接呼ぶのではなく、決まった書き方をしたときにPythonが呼び出すメソッドです。

生成・表示

メソッド いつ呼ばれるか 書き方の例
__init__(self, ...) インスタンスを作った直後の初期化 Book("Python入門", 2800)
__repr__(self) repr()、対話シェル、コンテナの中、!r、%r repr(b)、[b]
__str__(self) print()、str()、f文字列、%s print(b)
__format__(self, spec) f文字列や format() で書式指定したとき f"{b:>10}"
__bool__(self) 真偽値として評価されたとき if b:、bool(b)

比較・ハッシュ

メソッド いつ呼ばれるか 書き方の例
__eq__(self, other) 等しいかを比べるとき a == b、a in リスト、リスト.count(a)
__ne__(self, other) 等しくないかを比べるとき(省略すると __eq__ の逆) a != b
__lt__/__le__/__gt__/__ge__ 大小を比べるとき a < b、sorted(...)、max(...)
__hash__(self) ハッシュ値が必要なとき hash(a)、{a}、{a: 1}

演算

メソッド いつ呼ばれるか 書き方の例
__add__/__sub__/__mul__ 算術演算子を使ったとき a + b、a - b、a * b
__truediv__/__floordiv__/__mod__ 割り算・切り捨て割り算・余り a / b、a // b、a % b
__radd__ など 左側の型が演算に対応していないとき 1 + a
__iadd__ など 累算代入のとき a += b
__neg__/__abs__ 単項演算・絶対値 -a、abs(a)

コンテナ・くり返し

メソッド いつ呼ばれるか 書き方の例
__len__(self) 長さを求めるとき。__bool__ が無ければ真偽値の判定にも使われる len(s)、if s:
__getitem__(self, key) 添字やスライスで読むとき s[0]、s[1:3]
__setitem__(self, key, value) 添字で代入するとき s[0] = x
__delitem__(self, key) 添字で削除するとき del s[0]
__contains__(self, item) 含まれるかを調べるとき x in s
__iter__(self) くり返しを始めるとき for x in s:、list(s)
__next__(self) 次の要素を取り出すとき(イテレータ側) next(it)

呼び出し・with文・属性

メソッド いつ呼ばれるか 書き方の例
__call__(self, ...) インスタンスを関数のように呼んだとき obj(...)
__enter__(self) with ブロックに入るとき with obj as x:
__exit__(self, exc_type, exc, tb) with ブロックを出るとき(例外が起きても呼ばれる) 同上
__getattr__(self, name) 通常の方法で属性が見つからなかったとき obj.unknown
__setattr__(self, name, value) 属性に代入するとき obj.x = 1
__del__(self) オブジェクトが破棄されるとき(タイミングは保証されない) ―

この表のうち、__init__・__repr__・__str__・__format__・__bool__・__eq__・__hash__・__lt__・__add__・__len__・__getitem__・__setitem__・__delitem__・__contains__・__iter__・__call__・__enter__・__exit__ は、次の章のコードで実際に呼ばれることを確かめています。それ以外は一般に知られた仕様として載せており、今回は未実行です。

比較と演算の特殊メソッドを動かす

バージョン番号を表すクラスで、比較・ハッシュ・演算・書式をまとめて確認します。

from functools import total_ordering


@total_ordering
class Version:
    def __init__(self, major, minor):          # 生成時
        print(f"  __init__({major}, {minor})")
        self.major, self.minor = major, minor

    def __repr__(self):
        return f"Version({self.major}, {self.minor})"

    def __str__(self):
        return f"v{self.major}.{self.minor}"

    def __eq__(self, other):                   # ==
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor) == (other.major, other.minor)

    def __hash__(self):                        # hash(), set, dict のキー
        return hash((self.major, self.minor))

    def __lt__(self, other):                   # <, sorted
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor) < (other.major, other.minor)

    def __bool__(self):                        # if obj:
        return (self.major, self.minor) != (0, 0)

    def __add__(self, n):                      # +
        return Version(self.major, self.minor + n)

    def __format__(self, spec):                # f"{obj:spec}"
        return f"{self.major}.{self.minor:0{spec or 1}d}"


a, b, c = Version(1, 2), Version(1, 10), Version(1, 2)
print(a == c, a != b, a is c)
print(a < b, a >= b, sorted([b, a]), max(a, b))
print(len({a, b, c}), {a: "first"}[c])
print(bool(Version(0, 0)), bool(a))
print(a + 3, f"{a:03}", f"{a}")
print(a == "v1.2", a.__eq__("v1.2"))
try:
    a < 5
except TypeError as e:
    print("TypeError:", e)
  __init__(1, 2)
  __init__(1, 10)
  __init__(1, 2)
True True False
True False [Version(1, 2), Version(1, 10)] v1.10
2 first
  __init__(0, 0)
False True
  __init__(1, 5)
v1.5 1.002 1.2
False NotImplemented
TypeError: '<' not supported between instances of 'Version' and 'int'

読み取れるポイントです。

行 わかること
a == c が True、a is c が False == は __eq__ で中身を比べ、is は同じオブジェクトかを比べる
a != b が True __ne__ を書かなくても、__eq__ の逆として動く
a >= b が動く __lt__ と __eq__ だけ書き、functools.total_ordering が残りの比較を補っている
sorted([b, a]) 並べ替えは __lt__ を使う。リストの中の表示は __repr__、max(a, b) を print した表示は __str__
len({a, b, c}) が 2 __eq__ と __hash__ がそろっているので、同じ中身は集合の中で1つにまとまる
a == "v1.2" が False 相手が別の型のとき NotImplemented を返すと、Pythonが「等しくない」と判断する
a < 5 が TypeError 大小比較で両方が NotImplemented を返すと、わかりやすい例外になる

別の型と比べられたときは False を直接返すのではなく NotImplemented を返すのが定石です。相手側のクラスに比較を任せる余地が残ります。

__eq__ と __hash__ の関係

__eq__ を定義するときに一緒に知っておきたいのが __hash__ です。__eq__ だけを定義すると、そのクラスのインスタンスは集合に入れたり辞書のキーにしたりできなくなります。

class P1:                       # 何も定義しない
    def __init__(self, x): self.x = x

class P2:                       # __eq__ だけ
    def __init__(self, x): self.x = x
    def __eq__(self, other): return isinstance(other, P2) and self.x == other.x

class P3:                       # __eq__ と __hash__
    def __init__(self, x): self.x = x
    def __eq__(self, other): return isinstance(other, P3) and self.x == other.x
    def __hash__(self): return hash(self.x)

print("P1:", P1(1) == P1(1), len({P1(1), P1(1)}))
print("P2:", P2(1) == P2(1), P2.__hash__)
try:
    {P2(1)}
except TypeError as e:
    print("P2 TypeError:", e)
print("P3:", P3(1) == P3(1), len({P3(1), P3(1)}), P3(1) in [P3(1)], P3(1) != P3(2))
print([P1(1)].count(P1(1)), [P3(1)].count(P3(1)))

Python 3.11.15:

P1: False 2
P2: True None
P2 TypeError: unhashable type: 'P2'
P3: True 1 True True
0 1

Python 3.14.7では、3行目が次のように、どの操作で起きたかまで伝える文になっていました。

P2 TypeError: cannot use 'P2' as a set element (unhashable type: 'P2')
クラス == の結果 集合・辞書のキー 理由
P1(何も定義しない) 中身が同じでも False 使える(別物として2件) 既定の __eq__ は「同じオブジェクトか」で比べる
P2(__eq__ だけ) True 使えない __eq__ を定義すると __hash__ が自動で None になる
P3(両方) True 使える(1件にまとまる) 等しいもの同士が同じハッシュ値を返す

公式ドキュメントのデータモデルのページにも、__eq__ を上書きして __hash__ を定義しないクラスは __hash__ が暗黙に None になること、等しいと判定されるオブジェクトは同じハッシュ値を持つ必要があることが書かれています。

__hash__ を書くときの目安は次のとおりです。

目安 内容
使う属性をそろえる __eq__ で比べている属性と同じものをタプルにして hash(...) に渡す
変わらない値だけを使う 集合に入れたあとで属性を書き換えると、探せなくなることがある
変更するクラスなら無理に定義しない 辞書のキーにしない使い方なら、__hash__ が None のままで問題ない

コンテナ・with文の特殊メソッドを動かす

次は「本棚」を表すクラスで、len()・添字・in・for・呼び出し・with の動きを確かめます。

class Shelf:
    def __init__(self, *books):
        self._books = list(books)

    def __len__(self):                    # len(obj), 真偽値の判定
        return len(self._books)

    def __getitem__(self, index):         # obj[i], obj[a:b]
        return self._books[index]

    def __setitem__(self, index, value):  # obj[i] = v
        self._books[index] = value

    def __delitem__(self, index):         # del obj[i]
        del self._books[index]

    def __contains__(self, item):         # x in obj
        return item in self._books

    def __iter__(self):                   # for x in obj
        return iter(self._books)

    def __call__(self, word):             # obj(...)
        return [b for b in self._books if word in b]

    def __enter__(self):                  # with obj as x:
        print("  __enter__")
        return self

    def __exit__(self, exc_type, exc, tb):
        print("  __exit__", exc_type.__name__ if exc_type else None)
        return False


s = Shelf("Python入門", "SQL入門", "Git入門")
print(len(s), s[0], s[-1], s[0:2])
s[1] = "Linux入門"; del s[2]
print(list(s), "Linux入門" in s, bool(s), bool(Shelf()))
print(s("Py"))
with Shelf("A") as t:
    print("  with の中", len(t))
try:
    with Shelf("B"):
        raise ValueError("中で例外")
except ValueError as e:
    print("ValueError:", e)
3 Python入門 Git入門 ['Python入門', 'SQL入門']
['Python入門', 'Linux入門'] True True False
['Python入門']
  __enter__
  with の中 1
  __exit__ None
  __enter__
  __exit__ ValueError
ValueError: 中で例外
  • bool(Shelf()) が False になっているのは、__bool__ が無いときに __len__ の結果が0かどうかで真偽が決まるため
  • __exit__ は、with の中で例外が起きても呼ばれる。False(または何も返さない)なら例外はそのまま外へ伝わる

特殊メソッドが無いときのエラー文

対応する特殊メソッドを持たないクラスで各操作をしたときのメッセージです。エラー文から「どのメソッドを足せばよいか」を逆引きできます。

操作 エラーメッセージ(Python 3.11.15) 足すメソッド
len(x) TypeError: object of type 'X' has no len() __len__
x[0] TypeError: 'X' object is not subscriptable __getitem__
list(x)/for TypeError: 'X' object is not iterable __iter__(または __getitem__)
x + x TypeError: unsupported operand type(s) for +: 'X' and 'X' __add__
x < x TypeError: '<' not supported between instances of 'X' and 'X' __lt__
x() TypeError: 'X' object is not callable __call__
with x: TypeError: 'X' object does not support the context manager protocol __enter__ と __exit__
{x}(__eq__ だけ定義) TypeError: unhashable type: 'P2' __hash__

with のメッセージは版で違い、Python 3.10.18では AttributeError: __enter__、3.14.7では TypeError: 'X' object does not support the context manager protocol (missed __exit__ method) でした。

なお、__getitem__ だけを定義したクラスも、0から順に添字で読んで IndexError で止まる形なら for や in が使えることを確かめました(list(...) が [0, 10, 20]、20 in ... が True)。ただし len() は __len__ が無いと使えません。

dataclassが自動で作るメソッドの対応表

@dataclass を付けると、ここまで手で書いてきた特殊メソッドの一部が自動で作られます。何が作られ、何が作られないかを、クラスの __dict__ を調べて確かめました。

from dataclasses import dataclass

@dataclass
class A:
    name: str
    price: int

@dataclass(order=True)
class B:
    name: str
    price: int

@dataclass(frozen=True)
class C:
    name: str
    price: int

@dataclass(eq=False)
class D:
    name: str
    price: int

@dataclass(slots=True)
class E:
    name: str
    price: int

names = ["__init__", "__repr__", "__eq__", "__lt__", "__le__", "__gt__", "__ge__",
         "__hash__", "__setattr__", "__delattr__", "__match_args__", "__slots__", "__str__"]
for cls in (A, B, C, D, E):
    made = [n for n in names if n in cls.__dict__]
    print(cls.__name__, made, "hash=", cls.__dict__.get("__hash__", "(継承)"))
A ['__init__', '__repr__', '__eq__', '__hash__', '__match_args__'] hash= None
B ['__init__', '__repr__', '__eq__', '__lt__', '__le__', '__gt__', '__ge__', '__hash__', '__match_args__'] hash= None
C ['__init__', '__repr__', '__eq__', '__hash__', '__setattr__', '__delattr__', '__match_args__'] hash= <function C.__hash__ at 0x7fa54edde7a0>
D ['__init__', '__repr__', '__match_args__'] hash= (継承)
E ['__init__', '__repr__', '__eq__', '__hash__', '__match_args__', '__slots__'] hash= None

表にまとめると次のとおりです。

メソッド @dataclass order=True frozen=True eq=False slots=True
__init__ 作られる 作られる 作られる 作られる 作られる
__repr__ 作られる 作られる 作られる 作られる 作られる
__eq__ 作られる 作られる 作られる 作られない 作られる
__lt__/__le__/__gt__/__ge__ 作られない 作られる 作られない 作られない 作られない
__hash__ None(ハッシュ不可) None 作られる object のものを継承 None
__setattr__/__delattr__ 作られない 作られない 作られる(代入を止める) 作られない 作られない
__match_args__ 作られる 作られる 作られる 作られる 作られる
__slots__ 作られない 作られない 作られない 作られない 作られる
__str__ 作られない 作られない 作られない 作られない 作られない

__str__ はどの指定でも作られません。 print したときは、自動で作られた __repr__ が使われます。動きも確かめます。

a = A("ノート", 300)
print(a, "|", repr(a), "|", f"{a}", "|", [a])
print(a == A("ノート", 300), A.__str__ is object.__str__)
try:
    hash(a)
except TypeError as e:
    print("TypeError:", e)
print(hash(C("ノート", 300)) == hash(C("ノート", 300)), len({C("a", 1), C("a", 1)}))
print(B("a", 2) < B("b", 1), sorted([B("b", 1), B("a", 2)]))
try:
    a < a
except TypeError as e:
    print("TypeError:", e)
print(D("a", 1) == D("a", 1))

@dataclass
class Money:
    amount: int
    unit: str = "円"
    def __str__(self):
        return f"{self.amount:,}{self.unit}"

m = Money(12000)
print(m, "|", repr(m), "|", [m])
A(name='ノート', price=300) | A(name='ノート', price=300) | A(name='ノート', price=300) | [A(name='ノート', price=300)]
True True
TypeError: unhashable type: 'A'
True 1
True [B(name='a', price=2), B(name='b', price=1)]
TypeError: '<' not supported between instances of 'A' and 'A'
False
12,000円 | Money(amount=12000, unit='円') | [Money(amount=12000, unit='円')]
わかること 内容
既定の dataclass は集合に入れられない __eq__ が作られ、__hash__ が None になるため(前の章と同じ仕組み)
集合や辞書のキーにしたいなら frozen=True __hash__ が作られ、同じ中身は1件にまとまる
大小比較は order=True フィールドを定義順に並べたタプルとして比べる
eq=False は「同じオブジェクトか」で比べる 中身が同じでも False
利用者向けの表示は __str__ を自分で足す Money の例のように、print と一覧表示を分けられる

「データを入れるだけのクラスなら dataclass に任せ、表示を整えたいときだけ __str__ を足す」と、手で書く量を大きく減らせます。

つまずきやすい点と確認リスト

現象 原因 対応
print(リスト) で <__main__.X object at 0x...> が並ぶ __repr__ が無い(__str__ だけ定義している) __repr__ を定義する
"文字" + obj で TypeError + では自動で文字列にならない str(obj) か f文字列を使う
f"{obj:>10}" で TypeError 既定の __format__ は書式指定を受け付けない f"{obj!s:>10}"
__eq__ を足したら集合に入らなくなった __hash__ が None になった __hash__ も定義する、または frozen な dataclass にする
== が中身を比べてくれない __eq__ が無い __eq__ を定義する、または dataclass にする
__str__ が TypeError: __str__ returned non-string 文字列以外を返している f文字列や str() で文字列にして返す
ログに __str__ の表示しか出ず、中身がわからない %s で渡している 確認用のログは %r や f"{obj!r}"

書き終えたら見直したい項目です。

確認すること 目安
__repr__ があるか クラス名(属性=値, ...) の形で、属性に !r を付けている
__str__ は必要か 利用者向けの見た目が __repr__ と違うときだけ書く
__eq__ と __hash__ がそろっているか 同じ属性を使っている
別の型との比較で NotImplemented を返しているか isinstance で確かめてから比べる
dataclass で済むか __init__・__repr__・__eq__ を手で書いているなら置き換えを考える

よくある質問

Q. __str__ と __repr__ はどちらを先に書けばよいですか?

A. __repr__ です。__str__ が無いときは print も __repr__ を使うので、1つ書くだけで print・リストの中・対話シェル・デバッガのすべてが読める表示になります。利用者向けに別の見た目が必要になったら、そのときに __str__ を足します。

Q. print では読めるのに、リストに入れて print すると <__main__.X object at 0x...> になります。

A. __str__ だけを定義しているためです。リストや辞書は、要素を表示するときに __repr__ を呼びます。__repr__ を定義すれば、リストの中でも読める表示になります。要素を __str__ の形で並べたいときは ", ".join(map(str, リスト)) を使います。

Q. f文字列の !r は何をしていますか?

A. 値を repr() で文字列にしてから埋め込みます。f"{b!r}" は f"{repr(b)}" と同じ結果です。文字列に引用符が付くので、空文字や前後の空白がはっきり見えます。f"{b=}" と書くと「変数名=repr の結果」の形で表示され、確認用の出力に便利です。

Q. dataclass にすれば __str__ も作られますか?

A. 作られません。今回の確認では、@dataclass が作ったのは __init__・__repr__・__eq__ などで、__str__ は object のもの(__repr__ を呼ぶ)がそのまま使われていました。利用者向けの表示が必要なら、dataclass の中に __str__ を自分で書き足します。

まとめ

  • __str__ は人に見せる表示、__repr__ は開発者が中身を確かめる表示。print・str()・f文字列は __str__、repr()・対話シェル・!r・コンテナの中は __repr__
  • __str__ が無ければ __repr__ が使われる。1つだけ書くなら __repr__
  • リストや辞書の中は必ず __repr__。<__main__.X object at 0x...> が並ぶときは __repr__ を足す
  • __repr__ は クラス名(属性=値) の形にし、属性には !r を付ける
  • __eq__ を定義すると __hash__ が None になる。集合や辞書のキーに使うなら __hash__ もそろえる
  • 特殊メソッドは「決まった書き方をしたときにPythonが呼ぶ」 。エラー文から足すメソッドを逆引きできる
  • dataclass は __init__・__repr__・__eq__ を自動で作る。__str__ は作られないので必要なら自分で足す

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python __str__と__repr__の違い|特殊メソッド一覧」

株式会社RJCでは、Pythonの特殊メソッドやクラスの書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?