🔨

Pythonでclassを使う:method編(1): 基本的な特殊メソッド

2022/12/08に公開

導入

今回はよく目にする4つの特殊メソッドは__init__,__call__,__str__,__repr__に加えて__eq__,__class__.__name__,__dict__を紹介します。

そして想定される実用的な使い方についてもいくつか紹介します。

以下のクラスを使って説明していきます。

class A:
    z = 1
    def __init__(self,x = 1):
        self.x = x
    def __call__(self):
        return f'call:{self.__class__.__name__}'
    def __str__(self):
        return f'str:{self.__class__.__name__}' 
    def __repr__(self):
        return f'A(x={self.x})'
    def __eq__(self, other):
        return self.__dict__ == other.__dict__ 

__class__.__name__

自身のクラス名を表します。

__dict__

{インスタンス変数名:値}の辞書を表します。この時インスタンス変数名はstr型となります。
またクラス変数は返さないので注意が必要です。

In : a = A(); a.__dict__
Out: {'x': 1} #'x'はstr, クラス変数のzは辞書に入らない。

__init__

__init__はインスタンス化したときに呼ばれます。書かなくてもclassの定義はできますが、インスタンス変数を持たないclassにそこまで価値はないです。(例外はdataclassなど)

上記の話は、classの設計でまた記事にする予定です。

__call__

{インスタンス}.(){インスタンス}.__call__()と等価になります。

In : a = A(); a()
Out: 'call:A'

一般的に__call__は、class特有の処理をする場合に定義されることが多いです。

例えば機械学習のmodelのforward(入力に対して演算処理する)の処理をforwardメソッドとして定義するのではなく、__call__で定義するなどです。

__call__はclassの名前がわかりにくいと返って何の処理をしているかわからないので、使う際は注意が必要です。

__str__

インスタンスに対して、インスタンスを'{インスタンス}'のように文字列として扱った場合の処理を定義できます。

In : a = A(); f'{a}'
Out: 'str:A'

__repr__

同じ値のオブジェクトを再生成するために必要な文字列を定義するメソッドであり[1]、組み込み関数のreprで文字列が得られます。

In : a = A(); repr(a)
Out: 'A(x=1)' 

またインスタンスをそのまま呼ぶと__repr__で定義した文字列が返ってきます。

In : a = A(x=2); a #そのまま呼ぶ
Out: A(x=2) 

__repr__について、インスタンス変数の個数や種類に限らずに汎化させると以下のようなコードになります。

class A:
    def __repr__(self):
        members = (",").[f"{key}={value}," for k, v in self.__dict__.items()]
        return f'{self.__class__.__name__}({members})'

__eq__

{インスタンスA} == {インスタンスB}{インスタンスA}.__eq__({インスタンスB})と等価になります。
インスタンスの同士の比較の際には必須のテクニックです。

In : a,b,c = A(1),A(1),A(2); a == b , a == c
Out: (True, False)

ちなみに__eq__でクラスの比較をする際に、同一クラスかどうか確かめるためにはisinstanceを使えばよいです。
さらに同一クラスでなかった時に、等価演算を行うべきではないことを表すNotImplemented[2]を返すように実装すると以下のようになります。

class A:
    def __eq__(self, other):
      if not isinstance(other, self.__class__):
            return NotImplemented
        return self.__dict__ == other.__dict__ 

最後に

今回は以上です。method編は回数は決めていないのですが、あと2,3回くらいはやろうと思います。

classに関してはmethod編の他にも継承編、設計編を企画しています!

参考

[1] repr: https://docs.python.org/3.3/reference/datamodel.html#object.repr
[2] NotImplemented: https://docs.python.org/3/library/constants.html#NotImplemented

Discussion