はじめに
こんにちは、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'
if __name__ == '__main__':
counter = Counter(1)
print(counter)
オーバーライドした_repr_()の戻り値である'repr'が返されます。
2. _repr_()なし、_str_()あり
class Counter:
def __init__(self, initialValue=0):
self.value = initialValue
def __str__(self):
return 'str'
if __name__ == '__main__':
counter = Counter(1)
print(counter)
オーバーライドした_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'
if __name__ == '__main__':
counter = Counter(1)
print(counter)
オーバーライドした_str_()の戻り値である'str'が返される。※_str_()が_repr_()より優先されます。
4. _repr_()なし、_str_()なし
class Counter:
def __init__(self, initialValue=0):
self.value = initialValue
if __name__ == '__main__':
counter = Counter(1)
print(counter)
_str_()がないので、_repr_()の戻り値が返されているはずです。
_str_()を定義して確認してみます。
class Counter:
def __init__(self, initialValue=0):
self.value = initialValue
def __str__(self):
return self.__repr__()
if __name__ == '__main__':
counter = Counter(1)
print(counter)
_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)
_repr_()の戻り値を使い、eval()でインスタンスを生成します。
if __name__ == '__main__':
counter = Counter()
print('counter value:%s' % counter.get())
counter.increment()
print('counter value:%s' % counter.get())
copy = eval(repr(counter))
print('copy value:%s' % copy.get())
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)))
今回は親クラス(Object)で同じように出力できるのでわざわざ自クラスで再定義する必要はありませんが、継承する親クラスによっては上書きたいシチュエーションがあるのでコードを記載しています。
_str_()をオーバーライドする
_str_():表示用の文字列を返す。
どういう情報が欲しいか次第ですが、自分なら_repr_()の情報に加えメンバ変数の情報を付与します。
デバッグログを出力するときにインスタンスのメンバ変数を見たいことが度々あるので。
def __str__(self):
return self.__repr__() + str(vars(self))
vars(self)を使うとすべてのメンバ変数をdictで取得することができます。
セキュリティの問題もあるので、実際にすべてのメンバ変数を返してよいかは個別に判断してください。