メソッドのself (3)

デコレータの問題点を挙げて頂いた。ありがとうございます。

def deco(fun):
    def meth(arg):
        #このselfって??  
        self.val = "decorator"
        return self.val
    return meth

将来的にあるクラスのメソッドになりえるものを関数として定義しちゃうケースだとself何を指すのか?
関数でself???関数だよ?
ということになっちゃうよね?って話。

結論から言うと、今のPythonでは無理ですね。恐らくPythonはレキシカルスコープしか存在しないと思うので上記のmeth内のselfはレキシカルに解決できないので、selfは未定義となりますね。


私の提案は、selfだけ例外的にダイナミックスコープにするというもの。つまり実行時に呼び出される文脈によりselfの値が決まる。関数内ではselfの値にレシーバをバインドする。関数の外ではselfが見えない。また、レシーバを使用しないで呼び出した場合は、selfがNoneになる(もしくはselfを含む関数はメソッドとしてレシーバを伴わないと呼び出せないように制限を付ける)。Pythonの互換性は無視。


関数を定義する場合は通常selfを書かないので問題ない。selfを書いた場合は、メソッドとして扱われるのを前提として書いているはず。例えば関数内にyieldというキーワードを書いたとたんに、それは関数でなくジェネレータとなるのことをプログラマが意識する必要あるのと同様。制限として、関数内にselfがあれば、関数として呼び出せなくしても良い。もしくは、呼び出し可能だが、selfの値が未定義値(None)で初期化されているという仕様でも良いと思う(不正な呼び出しはAttributeErrorで気づく。できれば別のエラーメッセージの仕組みを考えた方が良いかも)。


基本的に関数とメソッドの違いは、関数内でselfを使うかどうかだと思う。関数内にselfがないのであればクラスメソッドと同じこと。クラス内でdef meth(arg)としたときに、def meth(self, arg)のシンタックスシュガーと考えれば自然だと思うが、実際はシンタックスシュガーにしない方が良い。メリット、デメリットをまとめると以下のようになると思う(ただしPythonの互換性は無視)。


メリット

  • クラスメソッドに対して@classmethodや@staticmethodを付ける必要ない
  • メソッド呼び出しで引数の個数間違えたときのエラーメッセージが分かりやすい
  • メソッド定義でいちいちselfを書かなくてよい(クラスメソッドにまで引数にselfを書くのは冗長)


デメリット

  • selfをキーワードにする(C++Javaにもthisがあるので、移民組にも問題にならないと思われる)
  • ダイナミックスコープという概念をselfのみ追加する必要がある(これが大きい理由かな?)
  • 関数内ではselfはレシーバが自動的にバインドされるというルールを覚える必要がある
  • インスタンスメソッドとクラスメソッドの区別が曖昧(というか区別がない?)


他にも気づいたら追記する予定。


補足:

selfをダイナミックスコープにするというより、レシーバがメソッドを呼び出したタイミングだけselfにインスタンス(レシーバ)をバインドしてあげて、メソッドを抜けたらNone(未定義値)に戻してあげれば良いので、そういうルールにすればルールも割とシンプルになるかも。以下のようにグローバル変数を使用して疑似的に再現できる。この方法は結構悪くないのではないか。もしかしたら継承関係が複雑になると問題が起きてくるのかな?

self = None

def deco(func):
  def f(myself, arg):
    global self
    self = myself
    ret = func(arg)
    self = None
    return ret
  return f
    
class C:
  @deco
  def foo(arg):
    self.v = arg
    return self.v
    
c = C()
print c.foo(1)  #=> 1
print c.v       #=> 1
print self      #=> None
  • selfをキーワードにする
  • receiver.meth(...)で、methの前と後にそれぞれ、self=receiver、self=Noneを自動的に代入してあげる
  • 関数内でselfを書くと特別な意味を持つ


この3つのルールを適用してあげれば、メソッド定義時の書き方だけは互換性が崩れるが、呼び出し側はそのまま変更する必要ないので、Pythonで実現するのも割といけると思う。Python処理系ではメソッド昇格や型の判定など色々な箇所の実装に影響を与えそうなので採用される可能性はないと思うけど。


補足2:

この方法だと1個ルール増えるが、1個ルール減る。「関数内でselfは特別な意味を持つ」というルールが増えて、「実装側でメソッドの第一引数にselfを書き、呼び出し側では不要」というルールが減る。私はPythonベースの新しい言語を作るとしたら明らかにこのやり方にメリットを感じる。Pythonファンの人を100人集めたら、Pythonの暗黙より明示というポリシーを理由に99人くらいから反対されそうだなあ。でもオブジェクト指向自体が特殊な概念なので特殊なルールを付けても良いと思う。私自身はGroovyをきっかけに色々考え方が変わった。あとでGroovyのOOをもう少し調べてみるかな。