ヌルポインター親衛隊

社内でひとりエンジニアやってます。

Python学習4日め(クラス変数、インスタンス変数、特殊メソッド)

クラスの世界に浸っていると、「(あっ、アレはこういうふうな構成でクラスを作ってプログラムを実装してるんだろうな)」みたいな、オブジェクト指向的な感覚が身につく気がします。これは、C言語を学習しているときには決して持ち得なかった感覚で、この感覚が身についてくるに従って、C言語で大規模なプログラムを書くのってすげえ難しいんじゃないか?と思うようになってきました。でも僕が今使ってるPythonはCで実装されてるんだぜ...。

本日の学習内容は、独学プログラマー第14章と第15章です。

クラス変数とインスタンス変数

クラスには、2つの変数があります。以下のコードを見てみませう。

class Bike:
    def __init__(self, name):
        self.name = name

このコンストラクタの内部で定義されているself.nameは、インスタンス変数です。これは、Bikeクラス本体に紐付いた変数ではなく、Bikeクラスから生成されるインスタンス(オブジェクト)固有の変数です。そのため、オブジェクトごとに違った値を保持します。
次のコードを見てみましょう。

class BikeList:
    bike_list = []
    def __init__(self):
        pass

    def append_bike(self, name):
        self.bike_list.append(name)

b_list1 = BikeList()
b_list1.append_bike("Super Cub")

b_list2 = BikeList()
print(b_list2.bike_list)

何をしたいのかよくわからないクラスなことは置いておき、このBikeListクラスでは、コンストラクタの外にself.を付けずに定義されているリストがあります。下の方で、b_list1がappend_bike()でbike_listに"Super Cub"を追加しましたが、b_list2はappend_bike()していません。それでも、print(b_list2.bike_list)では["Super Cub"]が出力されます。
これは、bike_listがクラス変数であるためです。クラス変数は、クラスに紐付いた変数で、そのクラスから生成されたすべてのインスタンスからアクセスが可能です。しかし、クラス変数はミュータブルである必要があります。
また、次のコードでそれぞれのbike_listのIDを取得すると、同値であることがわかります。このことからも、参照先は同じ――C言語風に言い換えるなら、参照先のアドレスが同じです。

print(id(b_list1.bike_list))
print(id(b_list2.bike_list))

id(object)¶ オブジェクトの "識別値" を返します。この値は整数で、このオブジェクトの有効期間中は一意かつ定数であることが保証されています。有効期間が重ならない 2 つのオブジェクトは同じ id() 値を持つかもしれません。 --Python 3.7.2 ドキュメント

備考:ミュータブルじゃないクラス変数を定義すると...?

場合によっては同じIDを示しますが、例えばクラス変数をstr型で定義して、コンストラクタ内で更新してしまった場合等は、違ったIDを示しました。まあこんなコード書くほうがおかしいので、たぶん普通に考えたらわかる気がします。自分はわかりませんでした。

特殊メソッド

Pythonのメソッドには、暗示的な特殊キーワードがあります。例えば、C++等の言語でのコンストラクタはクラス名と同じですが、Pythonではinit()です。このような特別な働きをするメソッドのことを特殊メソッドと良います。以下のリストに例を示します(Python3.72ドキュメントを参照)。

コード 働き
__init__() コンストラクタ。オブジェクト生成時に呼び出される
__del__() デストラクタ。オブジェクト破壊時に呼び出される。
__str__() print()の引数にオブジェクトを与えたときなどに呼び出される。
__repr__() __str__()が定義されていない場合で、こちらが定義されていれば、これが呼び出される。 デバッグなどに使う場合が多い。
__format__() format()の引数にされたときに呼び出される。
__add__() オブジェクトが「+」演算子の左側に指定されたときに呼ばれる。

Python Sugeeeeeeeって感じなんですけど、特に面白いな、と思ったのがadd()メソッドです。以下のコードを御覧ください。

class TashizanJaNaiyoHikizanDayo:
    def __init__(self, val):
        self.val = val
    
    def __add__(self, opponent):
        return self.val - opponent.val

a = TashizanJaNaiyoHikizanDayo(10)
b = TashizanJaNaiyoHikizanDayo(3)

print(a + b)

足し算じゃないよ引き算だよつー如何にも小学生が考えそうなコードです。これを実行すると、 a + b = 10 + 3 = 7になります。アホくさ。

おわりに

唐突にですます調になりました。今後どうなるかはわかりません。