TSNETスクリプト通信10号も出たことで、久々にこちらに。
ネタは例によってBruce.さんとこから孫引き
ときどきの雑記帖 めぐりあい電脳空間篇の2010年09月07日の「激論」より
http://www.kt.rim.or.jp/~kbk/zakkicho/
う〜ん、実は単純なことなんだけど、なんでこんなに妙な話になるんだか。
もとが2chだから、訂正しにいく勇気は無いへタレな私は、ここでちこっと書いておきます。
<引用>
234 デフォルトの名無しさん [sage] 2010/09/06(月) 23:47:52 ID: Be:
あるクラス X のオブジェクトを生成するには、Python では X() とします。
このとき X.__call__() が呼ばれると思ってたんですけど、違うんでしょうか。
自分で X.__call__() をクラスメソッドとして上書きしたのに、X() の挙動はかわりませんでした。
>>> class X(object):
... @classmethod
... def __call__(cls): # X() のとき呼ばれるはず
... print 'hello'
... print 'cls=%r' % cls
...
>>> x = X() # 挙動はかわらない
>>> x()
hello
cls=<class '__main__.X'>
なぜでしょうか。
</引用>
まず間違いその1。
X()はX.__call__ではなく、正確にはtype(X).__call__(X)が呼ばれます
x = X()とすれば、x()でX.__call__(x)が呼ばれます。
つまり'__call__'は、「インスタンスからメソッドに呼ばれた時に、関数呼び出しの形式をフックする」特殊書式ですね。
インスタンスで書いてみると判るのだけど……
>>> class mX:
... def __call__(self):
... print('class call')
...
>>> cx = mX()
>>> cx.__call__=lambda :print('instans call')
>>> cx()
class call
>>> cx.__call__()
instans call
>>>
つまりクラスオブジェクトであるX()の挙動を変えたいのなら、メタクラスの__call__を定義しなきゃダメ、ということ。
>>> class M(type):
... def __call__(self):
... print('meta class call')
...
>>> class C(metaclass=M):
... pass
...
>>> C()
meta class call
>>>
まず間違いその2。
'classmethod'は単にメソッドの第1引数をインスタンスではなくクラスにするだけで、クラスのメソッドを変更するなどというヘンな機能はありません。
だから、デコレータでクラスメソッドにしても全く無駄。
……と、こんだけの話なのに、なぜこんなに激論になるのか?
<引用>
243 デフォルトの名無しさん [sage] 2010/09/07(火) 09:25:07 ID: Be:
>>242
http://d.hatena.ne.jp/hope-echoes/20080409/1207732499
>この理解は後にメタクラスを学習する際に役立つ。
<中略/>
247 デフォルトの名無しさん [sage] 2010/09/07(火) 10:01:42 ID: Be:
>>243
そこのページにも
> ようするに、
> obj = Class()
> obj = Class.__call__()
> は同じ意味である。
と書かれてありますよね。
しかし X.__call__() を上書きしてみたのに、X() を実行しても
上書きされてないので、質問してるのですが、わかってもらえてますか?
</引用>
これは、参照ページの方の書き方が間違いです。
Class.__call__は、特にClass.__call__を定義していなければ、type.__call__(Class)を呼び出すのでそう見えるけど、__call__をtypeのインスタンスであるClassで上書きしていれば話は別(サブクラスではないけど、オーバーライドの一種かな?)
どうもこれが、話をさらにややこしくしたみたいですね。
さらに……
<引用>
なおPythonのドキュメントでは、
http://www.python.jp/doc/2.5/ref/callable-types.html
> x(arg1, arg2, ...) は x.__call__(arg1, arg2, ...) を短く書いたものに なります。
と書いてあります。
</引用>
これも……まさか、x.__call__を「インスタンス変数」として定義してる場合とかを想定していないのだろうけど、確かに誤解が生じるかも。
x()は必ずtype(x).__call__(x)を呼び出しますが、x.__call__()は、x.__call__が「インスタンス変数」として上書きされていない限りにおいてtype(x).__call__(x)を呼び出します。
>>> class C:
... def __call__(self):
... print('class call')
...
>>> c = C()
>>> c.__call__ = lambda : print('instans call')
>>> c()
class call
>>> c.__call__()
instans call
>>>
私もそうだったけど、Pythonの挙動(特にメソッドフックやメタクラス)に関しては、人に聞く前にまず自分でコンソール叩いて色々確かめて、がお勧めですね。
そもそも、classmethodとか__call__とかの機能をしっかり理解していないから、こういう妙な話になるんでしょうね。
それにしても、説明サイトも公式マニュアル、杜撰と言われれば言い訳はできないかも。
やや、私も他人事じゃないな、くわばらくわばら。