まえがき
はてなの方に投稿したちょっとした Python についての学び。
Qiita 向けか? というのが多少疑問符ではあったので向こうに書いたのだけれど、お試しという感じ。
以下再掲。
まくら
Python を扱い始めて割と誰もが一度は疑問に思うであろう関数定義について学んだので書き留める。
学習にあたって以下のサイトを参照させて頂いた。
記事内での技術的な理解の誤りについてはもちろん私に文責がある点はご注意を。
TL;DR
Python の関数定義における self について調べた
クラス定義内でも Python は関数定義の記法でメソッドを記述する
クラス定義内で記述した関数定義から メソッドオブジェクト が生成される
Python の関数定義
例によって? Python を書き始めて「何でメソッドの第一引数に self を書くんだろう」と疑問に思って調べ物。
class Dog:
# class variable shared by all instances
kind = 'canine'
def __init__(self, name):
# instance variable unique to each instance
self.name = name
Python チュートリアル - 9.3.5. クラスとインスタンス変数
もちろんオブジェクトの自己参照を定義している言語は珍しくないし、それに self という語句を割り当てている言語もある。
焦点はあくまでも、なぜメソッドを宣言するときに 第一引数 として記述が必要なのか。
結論としては、Python はクラスメソッドを記述する時に関数定義を記述する、というより メソッドを定義する特別な記法は持たない 言語だからという事らしい。
メソッドと関数定義
メソッドと関数の違いをそもそも知らないという人向けは以下をどうぞ。
Mastering Python #関数とメソッドの違いってなに?
Python において関数は第一級オブジェクトとして扱えて、トップレベルで関数定義を記述すると関数が宣言される。
メソッドを加える為になぜ関数定義の記法が使えるのかというと、クラス定義の内部で第一引数に self を与える関数定義を書くと メソッドオブジェクト が生成されるからだ。
その為、以下の様なメソッド呼び出しと関数の実行はどちらも可能で結果が等価になる。
# クラス定義
class C:
def __init__(self):
# self definition
def meth(self, arg):
return <anything>
# C のオブジェクト foo のメソッド meth を実行
foo.meth(arg)
# C という名前空間中で定義された関数 meth を実行
C.meth(foo, arg)
2019/07/13 追記
より正しくはクラスという名前空間中で関数を定義する事が可能で、インスタンス化する際に関数定義からメソッドオブジェクトが作成されるという順序のようだ。
ひょっとして JavaScript で function を new してオブジェクトを作成するのに近かったりする?
これは今まで自分が認識していた範囲だと珍しいスタイルだ。
少なくともオブジェクトの自己参照に self という語句を利用できる言語の数よりは圧倒的に少数派だと思う。
オブジェクト指向と関数型の両方のパラダイムを追いかける Scala だってメソッドを記述するためには def という 特別な宣言 を使う。
念の為書いておくと、Scala で処理を val で宣言した場合には関数オブジェクトをメンバに持つ事になる。
関数定義でメソッドが書けると Scala の様にメソッドを記述する為の特別な仕様を持たなくて済む、と言われると、確かに一貫性という意味では合理性を感じる。
第一引数に self を与える意義
Mastering Python では以下に説明あり。
Mastering Python #なんで self を書かないといけないの?
ただ。この点に関しては単純に super class のメソッド呼び出しがどうこうというよりも 記法が関数定義のためのものだから と言ってしまった方がメソッド・関数という概念にある程度慣れ親しんだ人には筋が良いのではと思った。
メソッドはオブジェクトに生えているあるデータに対する処理なので、それがあるオブジェクトに付随するものであるのは自明だ。
けれど関数はそれ単独で成立する処理なのだから実行時に必要になる要素を引数にとるのは自然な事で、その記法をメソッドオブジェクトの生成にも利用しようと思ったらふさわしい入力場所は第一引数になるだろう。
でも、より考え方を進めてオブジェクトを型クラスとして見るなら、ひょっとしたら記法が関数定義のものだからって self を引数で明示しなてもいいのかもしれない。
どっちやねんて。
Python と Ruby の比較について
ちゃんとリンク先を読んで頂けた方は承知だろうけれど、実はこの辺りの仕様の比較対象としては Ruby が用いられている。
「メソッドを記述する為の特別な仕様」を持つ言語として挙げられているのはリンク先では Ruby。
この辺りは実のところあまり適切ではないのかなーと感じたりもしたので一意見として残したい。
まず1点、Ruby はそもそも関数を第一級オブジェクトとして扱っていない言語だという事。
トップレベルでメソッドは宣言できるけれどもそれはあくまで Object クラスに対するメソッドとして宣言されるというだけだ。
python とは逆にあくまでその記法はメソッドのものという所がこれまでの文脈で言うと重要かも。
そしてもう一点、Ruby はメソッドの第一引数はレシーバであるという点で多くのオブジェクト指向の言語と異なると言う事。Ruby で foo.meth(arg)
とメソッド呼び出しを書いた記述は他の言語で記述するところの C.meth(foo, arg)
に意味が近いというのを踏まえると、例示としてはどうかなと思うところ。
この辺りは6月頭に Ruby の ホットな機能 としてパイプライン演算子が組み込まれた件で話題になった。Quora における Ruby の作者自身のパイプライン演算子についての言及はこちら。
Ruby2.7のpipeline operatorで+や-等の演算子をはじいたのはなぜなんでしょうか?
パイプライン演算子そのものの歴史的経緯や Ruby に組み込まれるに当たって議論になった要因を調べてみるのは面白かったので、以下の記事を蛇足的に挙げておく。
Golang の場合
2019/07/21 追記分
Go 言語の値レシーバとポインタレシーバ #メソッド定義で意識すべきこと
レシーバと引数の取り扱いについてちょっと掘ってみていたところ、上のリンクの様な記述を見つけた。
これによると Golang では構造体の名前空間でメソッド定義を記述すると内部的には第一引数にオブジェクトの自己参照を入力する様な関数定義が生成されるという事で、ちょうど Python の関数定義の逆のプロセスを辿っている。
面白い。
おわりに
冒頭に書いた通り、記事内での技術的な理解の誤りについてはもちろん私に文責があるのでご注意下さい。
参照した記事についての疑問を記述している箇所については読んだ上で自分の理解・知識の範囲で引っかかった点を記載したまでで、非難や中傷を意味するものでないこともご理解頂ける様お願いします。
上記の内容含め、この記事についてのご意見ご感想があればお待ちしております。
あとがき
はてなから Qiita に再掲してみて、多少の記法の手直しはあるものと思っていたけれども、リンクの埋め込みに大分差がある事に気付いた。
はてなすごい。
あと Markdown の解釈だけれど、改行がそのまま反映されてしまうのは好みが別れるのかな。
自分としては行間を開けない改行はそのまま一段落にしてくれた方が好き。
元々 platex ユーザーだからか?