LoginSignup
59
70

More than 5 years have passed since last update.

Pythonで、呼び出し方によってメソッドの振る舞いを変える

Posted at

通常の定義と呼び出しの結果

Pythonのクラスのメソッドは3種類ある。

  • 通常のメソッド(インスタンスメソッド)
    • 第1引数は必須で、慣例としてselfにする。
    • インスタンス経由で呼び出すと、呼び出したインスタンスが第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とか)のメソッドでは、この方法は使えない。

59
70
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
59
70