クラスの挙動まとめ #2
前回 クラスの挙動まとめ #1
- 内容
- 関数とメソッドの違い
以降は、新形式クラスのみに焦点を当てる。
中には旧形式クラスにも適用される動作もあるけど、無視する。区別が面倒だから。
オブジェクトを比較する際に == と is を使い分けているけど、理由があるので無視するように。
理由は最後のほうのおまけに書いてある。
それぞれの違いは、 Python リファレンスマニュアル - 5.9 比較 (comparison) を参照。
簡単にいうと
なお、このエントリーに書かれている全ての例において、
- 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__ 系のカスタマイズ。