作者: 機械伯爵
日時: 2010/9/9(22:44)
 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__とかの機能をしっかり理解していないから、こういう妙な話になるんでしょうね。
 それにしても、説明サイトも公式マニュアル、杜撰と言われれば言い訳はできないかも。
 やや、私も他人事じゃないな、くわばらくわばら。
前の発言: 1291. Re: Python の演算子(operator) [Atsuo Ishimoto] 2010/5/21(22:02)
後の発言:
親発言:
子発言: