classの標準出力を特殊な文字列にしたい【__str__|__repr__】

classの標準出力を特殊な文字列にしたい【__str__|__repr__】

Pythonでクラスのインスタンスを生成して格納した変数を出力すると、class名とメモリ番地みたいなやつが出力されてしまいます。Pythonユーザーで機械学習とかで遊んでいる方はわかると思いますが、numpyはnp.ndarrayというクラスを持ちながら出力では行列が出力されます。線形代数計算ライブラリを自作している際、そのような特殊な出力にしたいことがありました。実際に行った方法をお話しします。

正直タイトルにあるように、出落ちなのですが......

*__str__*というPython独自の特殊メソッドを用いて、やりたいことを実現していきたいと思います。

やりたいこと

まずは、実際にやりたいことを明確にしていきます。numpyのような物を参考にしますが、今回は簡単のため、Scalarクラスを作ります。

このクラスは、数値(intもしくはfloat)を受け取って、それをdataとして格納させます。

求める最終形はこちらです。

>>> scalar = Scalar(5)
>>> print(scalar)
Scalar(5)

本来であれば、*<__main__.Scalar object at 0x7f85f3a00150>*のように表されてしまいますが、*Scalar(5)*というような特殊文字列にします。

Scalarクラスを作る

さて、これからScalarクラスを作ります。

初めは、最低限クラスとして機能するようなコードを書いていきます。

Scalarクラスの仕様は以下とします。

  • インスタンス生成時にint又はfloatを受け取る
  • 上の条件以外ならば、TypeErrorを発出

これらの条件を満たすようにクラスを書いたのが以下になります。

class Scalar:
    def __init__(self, data):
        if isinstance(data, int) or isinstance(data, float):
            self.data = data
        else:
            raise TypeError('expect int or float, but {}', type(data))

これでメインルーティンを書いていきます。

以下を試してみます。

  • intを入力
  • floatを入力
  • listを入力

さて、書きたします。先ほどのクラスを書いたファイルを拡張していきます。

class Scalar:
    def __init__(self, data):
        if isinstance(data, int) or isinstance(data, float):
            self.data = data
        else:
            raise TypeError('expect int or float, but {}', type(data))
            
if __name__ == "__main__":
    scalar = Scalar(3)
    print(scalar)
    scalar = Scalar(3.0)
    print(scalar)
    scalar = Scalar([3])
    print(scalar)

ターミナルで実行してみます。

$ python scalar.py
<__main__.Scalar object at 0x7fddd4300150>
<__main__.Scalar object at 0x7fddd4300190>
Traceback (most recent call last):
  File "scalar.py", line 13, in <module>
    scalar = Scalar([3])
  File "scalar.py", line 6, in __init__
    raise TypeError('expect int or float, but {}', type(data))
TypeError: ('expect int or float, but {}', class 'list')

3と3.0の入力は受け取れましたが、どうやら[3]の入力は期待した通りエラーを出してくれています。

しかし、やはりこのままではクラスをそのまま出力してしまっています。

str】の出番

それでは、__str__という特殊メソッドを用いて、出力結果を変更していきます。

先までのクラスを以下のように変えていきます。

class Scalar:
    def __init__(self, data):
        if isinstance(data, int) or isinstance(data, float):
            self.data = data
        else:
            raise TypeError('expect int or float, but {}', type(data))
            
    <span class="comment"># 以下を追加
    def __str__(self):
        return 'Scalar({})'.format(self.data)

最後の2行を追加する感じです。

それでは、intfloat以外はエラーを出すことを確認しているのでメインルーティンも同時に変えて以下のファイルとして完成させましょう。

class Scalar:
    def __init__(self, data):
        if isinstance(data, int) or isinstance(data, float):
            self.data = data
        else:
            raise TypeError('expect int or float, but {}', type(data))
            
    def __str__(self):
        return 'Scalar({})'.format(self.data)
        
if __name__ == "__main__":
    scalar = Scalar(3)
    print(scalar)
    scalar = Scalar(3.0)
    print(scalar)

この出力結果はどうなるでしょうか......(ドキドキ??)

$ python scalar.py
Scalar(3)
Scalar(3.0)

期待した通りの結果ですね!!

このように、クラスのインスタンスの出力を制御するには**str**という特殊メソッドを使うと良いみたいです。

ただし、__reprというものもあります。今回のソースに関しては正直いうと、この**repr**の方が適している気がします。(evalを使えば可逆的に戻すことができるため)

詳しくはこちらを参照されたい(https://docs.python.org/ja/3/reference/datamodel.html)

今回のソースの推奨

class Scalar:
    def __init__(self, data):
        if isinstance(data, int) or isinstance(data, float):
            self.data = data
        else:
            raise TypeError('expect int or float, but {}', type(data))
    def __repr__(self):
        return 'Scalar({})'.format(self.data)
        
if __name__ == "__main__":
    scalar = Scalar(3)
    print(scalar)
    scalar = Scalar(3.0)
    print(scalar)

タメになったらSHARE!!!