メソッドのself (3)
- mopemopeのおらっちゃ富山県民やちゃあ: selfの話 (http://d.hatena.ne.jp/mopemope/20081128/p2)
デコレータの問題点を挙げて頂いた。ありがとうございます。
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をもう少し調べてみるかな。
メソッドとクロージャとオブジェクト
- ときどきの雑記帖 i戦士篇: Rubyについて Part 33 (http://www.kt.rim.or.jp/~kbk/zakkicho/08/zakkicho0811c.html#D20081125-5)
メソッドとクロージャに関して、
表層的には
・ブロックが関数と同等でない(ブロックをとれないなど)
・内部関数(Schemeのinternal defineみたいの)を書けない
Rubyに純粋な意味での「関数」は無いからね。
文の塊をつくる手段は所属するオブジェクトが必要な「メソッド」と
オブジェクトそのものである「ブロック/Proc」の2種類。
というのもRubyで「メソッド=クロージャ」…としてしまうと
public属性にアクセスできない=クロージャにもアクセスできなくなるんだよね。
確か『初めてのRuby』にメソッドはファーストクラスオブジェクト(以下FCO)ではないが、ProcオブジェクトにしてしまえばFCOになるので問題ないみたいなことが書いてあったような気がするが、それほど単純ではないらしい。
実装側がメソッドとクロージャで違うのはまだ良いとして、呼び出し側で呼び出し方が違うのはいただけない。しかし、メソッド=クロージャとするとpublic属性にアクセスできないってどういうことだろう?
Groovyは確か属性(インスタンス変数)がデフォルトでプロパティになるのでgetterをオーバーライドしなければ直接値が見えるということだったと思う。Rubyと似ているが、Rubyはわざわざattr_getterを呼び出してあげないといけないので、デフォルトで見えないようになっていて、挙動としてはデフォルトの扱いが違うというだけだと思う。Groovyでもメソッドとクロージャの区別あるのだろうか?
あと内部関数を書けないというのを調べたら、関数内で関数をdefで定義できないと書いてあった。lambdaやProcならできるらしいが、これは混乱の元だと思う。実際irbで試したらエラーが起きなかったが、正しい挙動をしてくれない。Ruby 1.8.6でしか調べてなく1.9では不明。その辺、整理されたのだろうか?メソッドとクロージャと関数を表面上は同じ扱いとして欲しい。