【Python3】__str__()と__repr__()は何を返すのが正解か

はじめに

こんにちは、SHOJIです。

本記事はPython3のログ設計を行っていた際に疑問に思った_str_()_repr_()は何を返すのがよいかの検討結果です。


すみません、ちょっと語弊がありました。検討結果というほどコレだ!というものには残念ながら行き着いていないです。

本記事はあくまで自分なりに検討した結果、これが良さそうかな?程度の暫定的な案です。

_repr_とは何か

repr() 組み込み関数によって呼び出され、オブジェクトを表す「公式の (official)」文字列を計算します。可能なら、これは (適切な環境が与えられれば) 同じ値のオブジェクトを再生成するのに使える、有効な Python 式のようなものであるべきです。できないなら、 <...some useful description...> 形式の文字列が返されるべきです。戻り値は文字列オブジェクトでなければなりません。クラスが __repr__() を定義していて __str__() は定義していなければ、そのクラスのインスタンスの「非公式の (informal)」文字列表現が要求されたときにも __repr__() が使われます。

この関数はデバッグの際によく用いられるので、たくさんの情報を含み、あいまいでないような表記にすることが重要です。

3. データモデル — Python 3.8.5 ドキュメント

_str_とは何か

オブジェクトの「非公式の (informal)」あるいは表示に適した文字列表現を計算するために、 str(object) と組み込み関数 format(), print() によって呼ばれます。戻り値は string オブジェクトでなければなりません。

__str__() が有効な Python 表現を返すことが期待されないという点で、このメソッドは object.__repr__() とは異なります: より便利な、または簡潔な表現を使用することができます。

組み込み型 object によって定義されたデフォルト実装は、 object.__repr__() を呼び出します。

3. データモデル — Python 3.8.5 ドキュメント

整理すると

_repr_()eval()で評価できるようなPython式に相当する文字列を返す。

_str_():表示用の文字列を返す。


よく使うstr()print()で呼ばれるのは_str_()の方です。

ただ、_str_()が定義されていなければ_repr_()の戻り値を返します。

動作を確認してみる

Counterクラスのインスタンスcounter)をprint(counter)で出力してみます。

1. _repr_()あり、_str_()なし

class Counter:
    def __init__(self, initialValue=0):
        self.value = initialValue

    def __repr__(self):
        return 'repr'

# start process.
if __name__ == '__main__':
    counter = Counter(1)
    print(counter)  # repr が出力される

オーバーライドした_repr_()の戻り値である'repr'が返されます。

2. _repr_()なし、_str_()あり

class Counter:
    def __init__(self, initialValue=0):
        self.value = initialValue

    def __str__(self):
        return 'str'

# start process.
if __name__ == '__main__':
    counter = Counter(1)
    print(counter)  # str が出力される

オーバーライドした_str_()の戻り値である'str'が返されます。

3. _repr_()あり、_str_()あり

class Counter:
    def __init__(self, initialValue=0):
        self.value = initialValue

    def __str__(self):
        return 'str'

    def __repr__(self):
        return 'repr'

# start process.
if __name__ == '__main__':
    counter = Counter(1)
    print(counter)  # str が出力される

オーバーライドした_str_()の戻り値である'str'が返される。※_str_()_repr_()より優先されます。

4. _repr_()なし、_str_()なし

class Counter:
    def __init__(self, initialValue=0):
        self.value = initialValue

# start process.
if __name__ == '__main__':
    counter = Counter(1)
    print(counter)  # <__main__.Counter object at 0x7fb4685c1c50> が出力される

_str_()がないので、_repr_()の戻り値が返されているはずです。

_str_()を定義して確認してみます。

class Counter:
    def __init__(self, initialValue=0):
        self.value = initialValue
        
    def __str__(self):
        return self.__repr__()

# start process.
if __name__ == '__main__':
    counter = Counter(1)
    print(counter)  # <__main__.Counter object at 0x7fb4685c1c50> が出力される

_str_()では自クラスで未定義の_repr_()を返します。

この戻り値が「<_main_.Counter object at 0x7fb4685c1c50>」であることから、「_str_()なし、_repr_()なし」は親クラス(object)の_repr_()を返していると分かります1

_repr_()_str_()では何を返すべきか

Counterクラスで考える

class Counter:
    def __init__(self, initialValue=0):
        self.value = initialValue

    def increment(self):
        self.value += 1

    def get(self):
        return self.value

Counterクラスは、インスタンス生成時にvalueに初期値を設定し、increment()を呼び出すごとにvalueをカウントアップし、get()valueの値を返します。

_repr_()をオーバーライドする

_repr_()eval()で評価できるようなPython式に相当する文字列を返す。

これを踏まえて、Counterクラスのインスタンスと等価のインスタンスを生成できる文字列を返す_repr_()を追加します。

class Counter:
    def __init__(self, initialValue=0):
        self.value = initialValue

    def increment(self):
        self.value += 1

    def get(self):
        return self.value
    
    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, self.value)  # Counter(1) を返す

_repr_()の戻り値を使い、eval()インスタンスを生成します。

# start process.
if __name__ == '__main__':
    counter = Counter()
    print('counter value:%s' % counter.get())   # counter value:0
    counter.increment()
    print('counter value:%s' % counter.get())   # counter value:1

    copy = eval(repr(counter))                  # counterと等価のインスタンスを生成
    print('copy value:%s' % copy.get())         # copy value:1

copyインスタンスが、一度increment()したcounterインスタンスと同じ1を出力しているため、期待通りの結果です。


ただし、これはCounterクラスがインスタンス生成時に初期値を設定できるから成立する話です。Counterクラスがインスタンス生成時に初期値を取らない場合、等価なインスタンスを作ることはできません。

その場合は「<_main_.Counter object at 0x7fb4685c1c50>」にならい、「<モジュール名.クラス名 object at 識別子>」がベターと思います。

def __repr__(self):
    return '<%s.%s object at %s>' % (self.__module__,
                                     self.__class__.__name__, 
                                     '0x{:x}'.format(id(self)))
# <__main__.Counter object at 0x7fb4685c1c50> を返す。

今回は親クラス(Object)で同じように出力できるのでわざわざ自クラスで再定義する必要はありませんが、継承する親クラスによっては上書きたいシチュエーションがあるのでコードを記載しています。

_str_()をオーバーライドする

_str_():表示用の文字列を返す。

どういう情報が欲しいか次第ですが、自分なら_repr_()の情報に加えメンバ変数の情報を付与します。

デバッグログを出力するときにインスタンスのメンバ変数を見たいことが度々あるので。

def __str__(self):
    return self.__repr__() + str(vars(self))
# <__main__.Counter object at 0x7fb4685c1c50>{'value': 1} を返す。

vars(self)を使うとすべてのメンバ変数をdictで取得することができます。

セキュリティの問題もあるので、実際にすべてのメンバ変数を返してよいかは個別に判断してください。


  1. Counterは何も継承していないように見えますが、Python3では何も継承しないクラスはobjectクラスを継承しています。