このサイトについて

和訳 なぜPythonのメソッド引数に明示的にselfと書くのか

和訳 なぜPythonのメソッド引数に明示的にselfと書くのか

Pythonの生みの親Guidoパパのブログエントリを翻訳してみました。「Why explicit self has to stay - なぜPythonのメソッド引数に明示的にselfと書くのか」。Pythonのメソッド引数に書く「self」はしばしば,他言語からのPython移民を中心に「ウザイ」「キモイ」「消えてなくなれ」と攻撃の対象となることが多いのです。GuidoパパはPythonが生まれて10年,もう何度も似たようなことを聞かされ,そのたびに明快な反論を行ってきました。言語の設計者には,技術的な素養はもちろんのこと,情熱と根気,そして我慢強さが必要なんだなあとつくづく思い知らされます。

ではお楽しみ下さい:-D。


Bruce EckelがPythonのメソッドの引数からselfを取り除く提案をブログに書いた。わたしはこれから,なぜこの提案がうまくないのか説明したいと思う。

Bruceの提案

Pythonのメソッドの内部で,インスタンスオブジェクトと他の変数を区別する方法が依然として必要であるということをBruceは理解している。なので,彼は"self"を予約語にすることを提案した。たとえば,メソッドを一つ持つクラスについて考えてみよう。

class C:
   def meth(self, arg):
      self.val = arg
      return self.val


Bruceの提案によると,このクラスは以下のようになる。

class C:
   def meth(arg):  # 見てよママ,selfがないよ!
      self.val = arg
      return self.val


それぞれのメソッドから6個の文字を省略できるというわけだ。しかしながら,私はBruceはタイプ量を削減するためにこの提案をしたのだとは思わない。selfを書く必要がない他の言語からPythonにやってきたプログラマが,selfをたまたま忘れてしまうことによって無駄にする時間を,彼は気にしているのだと思う(そのようなプログラマがPythonの仕様をよく知っていても,習慣は強い影響力を持っているので,そういうことは起こるものだ)。引数にselfを忘れた時に出るエラーメッセージの内容は,インスタンス変数の前やメソッド参照の前のselfを忘れたときのエラーに比べると確かに曖昧だ。多分,最悪なエラーメッセージは,Bruceが述べているとおり,メソッドの宣言は正しくて,呼び出し時に与える引数の数が正しくない場合に出力されるエラーだろう。以下の例を見てみよう。

Traceback (most recent call last):
File "classes.py", line 9, in
   obj.m2(1)
TypeError: m2() takes exactly 3 arguments (2 given)


このようなエラーメッセージが混乱の元であることを私は認める。しかし,言語仕様を変えるよりも,エラーメッセージを変える方がよいと私は思う。

なぜBruceの提案が意味をなさないか


まず,Bruceの提案に反論するための論拠を2,3紹介したいと思う。
ここにとても明快な論拠がある。"self"を明示的に引数として記述することによって,以下の2つの関数呼び出しが論理的に同じであることを補強できる,というのである。つまり,メソッドfooはクラスCのインスタンスであることを論理的に示せるわけだ。

foo.meth(arg) == C.meth(foo, arg)


もう一つの論拠は,"self"を明示的に引数として記述することで,関数を追加して,クラスを動的に書き換えることができるようになる,というものである。つまり,複数のクラスで共用できるメソッドを作れるというわけである。たとえば,以下のようにして,クラスCとまったく同じクラスを作ることができる。

# メソッドを持たないクラスを定義:
class C:
   pass

# グローバル関数を定義:
def meth(myself, arg):
   myself.val = arg
   return myself.val

# メソッドをクラスに追加:
C.meth = meth


(文法的な意味において)メソッドではなく関数を定義していることを強調するために,"self"というパラメータが"myself"に変更されていることに注意して欲しい。クラスCのインスタンスは一つの引数を持つ"meth"というメソッドを持つようになった。このメソッドの挙動は明示的で,メソッドになる前とまったく変わらない。このメソッドは,クラスCにmethメソッドが追加される前に生成されたクラスCのインスタンス上でも動く。
Bruceは1番目にあげた共通性について特に注意を払っていないのではないかと私は思う。私は彼の意見は論理的な重要さから来ているのだということには同意する。私が考え得るたった一つの例外は,スーパークラスのメソッドを呼ぶときに使われる古い書き方は,エラーを起こしやすい,ということである(これは間違いなく,selfを明示的に記述する必要があるということから来ている - 訳注:SuerpClass.method(self, arg)のような?)。だからこそ,私はPython 3000ではすべての場合においてsuper()を使うように勧めている。
Bruceは,2番目にあげた共用性がどのような場面で有効かについては考えが及んだかも知れない。実際,重要な実例がいくつか見受けられる。Bruceがこの提案を作るためにどのくらいの時間をかけたのかは知らない。しかし,彼は単に,クラス定義の内部に書かれたメソッドの定義時にselfという名前の引数を自動的に追加する,というアイデアについてだけ考えていたのだと思う(メソッドの内部に定義された内包関数をこのルールから除外するには,動的にselfを追加しなければならないわけだが)。彼の手法に従うなら,1番目にあげた共通性は維持できる。
しかしながら,Bruceがコンピュータに超能力者でも仕込まない限り解決できない状況が一つある。デコレータである。私は,彼がデコレータについて考えなかったことが,彼の提案の最も大きな欠点だと思っている。
メソッドがデコレートされたときに,self引数が渡されるかどうかを知ることはできない。というのは,デコレータはメソッドをスタティックメソッドに変えることもあるし(この場合selfはない),クラスメソッドに変えることもあるし(この場合は,selfの変わりにクラスオブジェクトが渡される),まったく違ったことをすることもできる(@staticmethodや@classmethodと同じ働きをするデコレータをPythonで書くことは希だと思うが)。つまり,デコレータが何をするかを知り得なければ,メソッドに明示的にselfが定義されているかどうかを知ることができないのである。
"@classmethod"や"@staticmethod"を特別な記法で代替するハックを私は却下する。また,メソッドの定義だけを検査して,自動的にクラスメソッドかインスタンスメソッドかを決めるような手法が良いアイデアだとも思わない(実際だれかがBruceのブログにコメントする形で提案していたが)。そのような仕様を採用すると,メソッドの前にはdefという宣言があるだけなのに,メソッドは複数の異なった呼ばれ方をすることになる。どのように呼ばれるかを判断しづらくなる。
Bruceの提案を支持するいくつかの行き過ぎたコメントがあったが,おおむねそれらは,言語仕様を複雑にしすぎたり,言語の深い部分のどこかに変更が必要だったり,Python 3.1で実装するにはコストが掛かりすぎる,といった風である。3.1のことを言えば,後方互換性を維持する機能変更だけが許されるのである。
実現が可能そうな提案が一つだけあった(そしてそれは後方互換である)が,それは単純に

def self.foo(arg): ...


上記のような記述は,クラスの内部では以下のように解釈されるというものである。

def foo(self, arg): ...


この提案がどんな理由で"self"を予約語にする必要があるのか,また前に置くのが"self"である必要があるのか分からない。たとえば,クラスメソッドなら次のようにした方が分かりやすいではないか。

@classmethod
def cls.foo(arg): ...


この提案が現状より良いものであるかどうかについては語らないでおこう。しかし,私はこの提案はBruceの提案や,コメントとして寄せられたより極端な提案よりずっと良いと思う。この提案は後方互換であるという意味で大きな利点を持っているし,軽いリファレンス実装を伴ったPEPになりうる可能性を持っていると思う。Bruceも,ちゃんとしたPEPを書いていれば,彼の提案にいくつもの欠点があることに気づいたはずである。
もっと続けることもできるが,今日は天気の良い日曜日でもあるし,予定もあるのでこのへんで:-)。


オマケ:LL温泉Pythonチュートリアルでは,ここで展開されているようなお話をもう少しかみ砕いてお話ししようと思っています:D。Pythonの名前空間についてよく知ることが,Pythonについて良く知る鍵になりますので。。。


 


2010-08-27 04:49