クラスの挙動まとめ #2

前回 クラスの挙動まとめ #1

内容
関数とメソッドの違い


以降は、新形式クラスのみに焦点を当てる。
中には旧形式クラスにも適用される動作もあるけど、無視する。区別が面倒だから。


オブジェクトを比較する際に == と is を使い分けているけど、理由があるので無視するように。
理由は最後のほうのおまけに書いてある。
それぞれの違いは、 Python リファレンスマニュアル - 5.9 比較 (comparison) を参照。
簡単にいうと

is
厳密な一致。同一のオブジェクトを指している場合に True 。オーバーロード不可
==
緩い一致。意味的に同一な場合に True 。オーバーロード


なお、このエントリーに書かれている全ての例において、

  • is が True ならば == も True である
  • == が False ならば is も False である

の 2 つが成立している。
よって、 is が True な場合、もしくは == が False の場合、もう一方での比較結果は省略する。*1

関数 (function) とメソッド (method) の違い

Python では関数とメソッドもオブジェクトであることを理解しておく。

# 関数
def func(self):
    print self, "Function"

# クラス
class C(object):
    def method(self):
        print self, "Method"

# インスタンス
obj = C()

func       # <function func at アドレス>
C.method   # <unbound method C.method>
obj.method # <bound method C.method of <__main__.C object at アドレス>>
C.method == obj.method # False

C.func = func
obj.another = func

C.func   # <unbound method C.func>
obj.func # <bound method C.func of <__main__.C object at アドレス>>
obj.another # <function func at アドレス>
C.func == func      # False
obj.func == func    # False
obj.another is func # True

import types

isinstance(func, types.FunctionType)   # True
isinstance(C.func, types.MethodType)   # True
isinstance(obj.func, types.MethodType) # True

このように、関数とメソッドは違うものとして扱われている。
中でも、関数オブジェクトをクラスオブジェクトのプロパティへ代入すると、メソッドオブジェクトに変換されることに気をつける。



メソッドオブジェクト

メソッドオブジェクトは im_class, im_func, im_self というプロパティを持っている。
具体的には以下のようになる。

def foo(self):
    pass

class C(object):
    pass

C.foo = foo
obj = C()

C.foo.im_func is obj.foo.im_func is foo # True
C.foo.im_class is C    # True
C.foo.im_self is None  # True
obj.foo.im_class is C  # True
obj.foo.im_self is obj # True

まとめると

  • 関数オブジェクト + クラスオブジェクト = メソッドオブジェクト (unbound)
  • 関数オブジェクト + クラスオブジェクト + インスタンス = メソッドオブジェクト (bound)

となる。

new.instancemethod による動的なメソッド作成

メソッドオブジェクトは明示的に作成することもできる。
new モジュールの instancemethod クラスを使用する。

import new

# new.instancemethod(関数オブジェクト, インスタンスオブジェクト, クラスオブジェクト)

def foo(self):
    print self, "OK"

class C(object):
    pass

obj = C()

C.foo = foo
C.bar = new.instancemethod(foo, None, C) # C.foo = foo と同じ意味

C.foo() # TypeError: unbound method foo() must be called with C instance as first argument (got nothing instead)
C.bar() # TypeError: unbound method foo() must be called with C instance as first argument (got nothing instead)

obj.foo() # <__main__.C object at アドレス> OK
obj.bar() # <__main__.C object at アドレス> OK

obj.baz = new.instancemethod(foo, obj, C)
obj.baz() # <__main__.C object at アドレス> OK

C.likeStatic = new.instancemethod(foo, C, type(C)) # クラスオブジェクトは type クラスのインスタンス。
C.likeStatic() # <class '__main__.C'> OK
obj.likeStatic() # <class '__main__.C'> OK (インスタンスが C クラスのオブジェクトで既に束縛されている)

class D(C):
    pass

obj2 = D()

D.likeStatic()    # <class '__main__.C'> OK
obj2.likeStatic() # <class '__main__.C'> OK
# @classmethod とは異なり、暗黙のクラスオブジェクトは定義元に固定されている

some_method = obj2.likeStatic()
some_method() # <class '__main__.C'> OK

おまけ クラスで定義されているメソッドは、必要に応じて生成されている

== 演算子オーバーロードされているので分かりづらいが、クラスで定義されているメソッドは、アクセスされるたびにメソッドオブジェクトが新しく生成されている。

class C(object):
    def foo(self):
        pass

method1 = C.foo
method1 == C.foo # True
method1 is C.foo # False

method2 = C.foo
method1 == method2 # True
method1 is method2 # False

obj = C()
method1 = obj.foo
method1 == obj.foo # True
method1 is obj.foo # False

C.foo is C.foo     # False
obj.foo is obj.foo # False
# うはーなにこれ

import new

def bar(self):
    pass

method1 = new.instancemethod(bar, obj, C)
method2 = new.instancemethod(bar, obj, C)

method1 == method2 # True
method1 is method2 # False

method3 = new.instancemethod(bar, None, C)

C.baz = bar
C.baz == method3 # True
C.baz is method3 # False
obj.baz == method1 # True
obj.baz is method1 # False

# メソッドオブジェクトの == 比較は、
# im_class im_self im_func がそれぞれ等しいなら True として考えて良い

インスタンスメソッドについては、代入を行うことで同一のものを使うようにできる。

class C(object):
    def foo(self):
        pass

c = C()

c.foo is c.foo # False
c.foo = c.foo  # 代入しなおす
c.foo is c.foo # True

C.foo is C.foo # False
C.foo = C.foo
C.foo is C.foo # False クラスオブジェクトではできない

おまけ 2 継承を使わないメソッドの流用

他のクラスで定義されているメソッドを、継承することなしに使いたい場合、次のようにできる。

class C(object):
    def foo(self):
        print self, "C.foo"

class D(object):
    def foo(self):
        print self, "D.foo"
    bar = C.foo
    baz = C.foo.im_func

obj = D()

obj.foo   # <bound method D.foo of <__main__.D object at アドレス>>
obj.foo() # <__main__.D object at アドレス> D.foo

obj.bar   # <unbound method C.foo>
obj.bar() # TypeError: unbound method foo() must be called with C instance as first argument (got nothing instead)

obj.baz   # <bound method D.foo of <__main__.D object at アドレス>>
obj.baz() # <__main__.D object at アドレス> C.foo

C.foo == D.bar # False
C.foo.im_func is D.bar.im_func is D.baz.im_func # True
D.bar.im_class is C # True
D.baz.im_class is D # True
D.bar.im_self is D.baz.im_class is None # True

obj.bar.im_class is C   # True
obj.bar.im_self is None # True
obj.baz.im_class is D   # True
obj.baz.im_self is obj  # True

おまけ 3 new.instancemethod is types.MethodType

import types
import new

new.instancemethod is types.MethodType # True

これだけ


中々把握しにくい挙動になってるなー。
次こそは __name__ 系のカスタマイズ。

*1:これだから演算子オーバーロードは嫌いだ