通常の定義と呼び出しの結果
Pythonのクラスのメソッドは3種類ある。
- 通常のメソッド(インスタンスメソッド)
- 第1引数は必須で、慣例として
self
にする。 - インスタンス経由で呼び出すと、呼び出したインスタンスが第1引数に入る。
- クラス経由で呼び出すと、呼び出したときの引数がそのまま渡される。
- 第1引数は必須で、慣例として
- クラスメソッド
-
@classmethod
を付けて定義する。第1引数は必須で、慣例としてcls
にする。 - インスタンス経由で呼び出すと、呼び出したインスタンスのクラスが第1引数に入る。
- クラス経由で呼び出すと、そのクラスが第1引数に入る。
-
- スタティックメソッド
-
@staticmethod
を付けて定義する。引数は必須ではない。 - 呼び出したときの引数がそのまま渡される。
-
class C:
val = 20
def __init__(self):
self.val = 1
def normal_method(self, v):
return self.val + v + 2
@classmethod
def class_method(cls, v):
return cls.val + v + 3
@staticmethod
def static_method(v):
return C.val + v + 4
i = C()
i.normal_method(5) # i.val + 5 + 2 = 1 + 5 + 2 = 8
i.class_method(6) # C.val + 6 + 3 = 20 + 6 + 3 = 29
i.static_method(7) # C.val + 7 + 4 = 20 + 7 + 4 = 31
C.normal_method(5) # requires 2 args but 1: error
C.normal_method(i, 6) # i.val + 6 + 2 = 1 + 6 + 2 = 9
C.normal_method(C, 7) # C.val + 7 + 2 = 20 + 7 + 2 = 29
C.class_method(8) # C.val + 8 + 3 = 20 + 8 + 3 = 31
C.static_method(9) # C.val + 9 + 4 = 20 + 9 + 4 = 33
メソッドの呼び出し方で振る舞いを変える
通常のメソッドも関数であることに変わりはない。
- 第1引数が
self
というのは単なるお約束であって、self
の型については制約はない。 - インスタンス経由で呼び出すと、処理系が勝手に第1引数にそのインスタンスを入れている。
これを逆手にとって、第1引数によって振る舞いを変えることができる。
class C:
# 上記に追加
def trick_method(arg, v):
if isinstance(arg, C):
return arg.val * 2 * v
else:
return C.val + arg * v
i.trick_method(4) # i.val * 2 * 4 = 1 * 2 * 4 = 8
C.trick_method(5) # requires 2 args but 1: error
C.trick_method(6, 7) # C.val + 6 * 7 = 20 + 6 * 7 = 62
C.trick_method(i, 8) # i.val * 2 * 8 = 1 * 2 * 8 = 16
C.trick_method(C, 9) # C.val + C * v: error
上記のtrick_method()
は、インスタンス経由では通常のメソッドのように振る舞い、クラス経由ではスタティックメソッドのように振る舞っている。
本来であれば、振る舞いが違うのなら別メソッドにするべきである。
実例
こんな変な使い方は、インスタンスをグループで管理するクラスを製作中に思いついた。
class Student:
rooms = {}
def __init__(self, room):
self.myroom = room
if not room in Student.rooms:
Student.rooms [room] = []
Student.rooms [room].append(self)
def classmates(self_or_room):
if isinstance(self_or_room, Student):
self = self_or_room
for s in Student.rooms[self.myroom]:
if s != self:
yield s
elif self_or_room in Student.rooms:
room = self_or_room
return iter(Student.rooms[room])
Ann = Student('3-A')
Bob = Student('3-B')
Cathy = Student('3-B')
Dan = Student('3-A')
Ellen = Student('3-A')
Fred = Student('3-B')
mates_of_ann = Ann.classmates() # Dan, Ellen
mates_of_3A = Student.classmates('3-A') # Ann, Dan, Ellen
mates_of_bob = Bob.classmates() # Cathy, Fred
mates_of_3B = Student.classmates('3-B') # Bob, Cathy, Fred
注意
デコレータ付き(@property
とか@classmethod
とか)のメソッドでは、この方法は使えない。