# なんで self を書かないと
いけないの?
いろんなことができるから
# はじめに
Python の self は関数の第一引数です。 「self とはなにか」については、以下の記事で見てきました。
Python の生みの親である Guido van Rossum 氏が 説明してくれたメールを補足する形で、 「なんで self を書かないといけないのか」について考えていきます。
上記の記事は Pythonの生みの親 Guido によって書かれたブログを和訳したものです。 Bruce という人が 「第一引数にわざわざ self を与えなくたって、 暗黙的に呼び出し元のインスタンスオブジェクトを self に代入すればいいじゃん」 と提案したのに対して、Guido が、それがなぜダメなのかの理由を述べています。
「なんで、ってなんで?」って感じですが。 メソッドで self って書かなくてもいいようにした方が楽ではないでしょうか?
# 本物
class GirlFriend:
def __init__(self, name):
self.name = name
girl_frined = GirlFriend('岩倉玲音')
girl_friend.name
# 岩倉玲音
# 偽物
class GirlFriend:
def __init__(name):
self.name = name
girl_frined = GirlFriend('岩倉玲音')
girl_friend.name
# 岩倉玲音
それにしても何故クラスメソッドの引数にselfをつけなければならないのだろうか。
— ちくわぶ (@Air_Cat3) November 22, 2019
別に
class Dog:
name = “none”
def __init__(name):
self .name = name
でも良くない?
何か訳があるんだろうけど、何故だろう。。
このような言語に Java, JavaScript, Kotlin, TypeScript があります。
反面 self
を書かないといけないような言語に Go, Rust があります。
そんな self
って書かない方が楽なんじゃいの?って疑問について、ここでは考えていきます。
ただ Guido のメールは結構難しくて、なかなかついていけないところがあります。
pythonのself、調べれば調べるほど何が分からないか分からなくなってまさにコレ pic.twitter.com/g15zgeeBnG
— くりぷ (@CRYPTANNEWS) August 17, 2018
おそらく Guido が言いたいのは、
「self
を書いた方が、いろんなことができるから」だろうなと、ぼんやりと感じています。
なんとなく、その雰囲気だけ伝わればと思いました。
簡単に「ふーん」と読み流していただけると幸いです。
# 事前知識
Python の関数は、色々と使い回せます。
# ◯ メソッドを変更する方法
関数をクラスオブジェクトの属性に代入するだけで、 メソッドとして利用できます。 関数は、関数として利用したり、 あるいは複数の異なるクラスのメソッドとして共有して利用したりすることができます。
メソッドを変更したい時は、
クラスオブジェクトの属性に関数を代入します。
#
# 対話モード >>> に
# コピペで実行できます。
#
class GirlFriend:
def change_name(self):
self.name = 'バラライカ'
girl_friend = GirlFriend()
girl_friend.change_name()
print(girl_friend) # バラライカ
# Step 1:
# change_name 関数を定義しました。
def change_name(self, name):
self.name = name
# Step 2:
# 正 クラスオブジェクトの属性に代入する
# GirlFriend.change_name = change_name
# 誤 インスタンスオブジェクトの属性ではない。
# girl_friend1.change_name = change_name
GirlFriend.change_name = change_name
# Step 3:
# インスタンスオブジェクトのメソッドは変更される。
girl_friend.change_name('岩倉玲音')
print(girl_friend.name) # 岩倉玲音
クラスオブジェクトの属性(関数)を変更すると そのクラスオブジェクトから生成された 全てのインスタンスオブジェクトの属性(メソッド)が変更されます。
# ◯ クラスオブジェクト
さっきからクラスオブジェクト、 クラスオブジェクトと連呼していますが、これは何者でしょうか? この記事は以下の記事からの続きになります。
クラスオブジェクトとは、何かについて見ています。 ごくごくざっくりと押さえておいていただけると、 理解しやすいかなと感じます。
些細な疑問のようで クラスという連載の一番最後になます。
Python の関数は、使い回しが効く。
# 疑問: なんで明示的に self を書くの?
答え: 関数として再利用できなくなるから
明示的であることは暗黙的であるより良い。
Explicit is better than implicit.
PEP 20 - The Zen of Python (opens new window)
「暗黙的に self に関数を呼び出してきたインスタンスオブジェクトを代入するような設計」も 考えることができます。
# 「暗黙的に self に関数を呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。
# self を書かなくても
# 使えるようにすればいいじゃん!
class GirlFriend:
def __init__(name):
self.name = name
girl_frined = GirlFriend('岩倉玲音')
girl_friend.name
# 岩倉玲音
しかし、このように設計すると関数を再利用したい時に、 困ることが3つあります。この後、それを見ていきたいと思います。
# 例 1. 親クラスの関数を呼び出す時
Guido のこの文章は何を指しているのでしょうか?
ここにとても明快な論拠がある。"self" を明示的に引数として記述することによって, 以下の2つの関数呼び出しが論理的に同じであることを補強できる,というのである。
つまり,メソッド foo はクラス C のインスタンスであることを論理的に示せるわけだ。
foo.meth(arg) == C.meth(foo, arg)
# 1. 可読性
論理的に foo.meth(arg) == C.meth(foo, arg)
と示せて何が嬉しいんでしょうか?
最初に見たとき、本当に意味がわかりませんでした。
しかし、いざ自分がこうやって記事を書いているうちに、
関数とメソッドの違いを説明するために、
この等式を使って 「self
は第一引数ですよ」、
「メソッドは関数に変換されているだけで大したことはしてないんですよ」という記事を書いていました。
理解しやすいかどうかは置いておいても、とても説明しやすいのです。 Google の検索でもアクセス数は自分のサイトでは結構あるページなので、 悪い説明ではないのかなと思います。
If the implementation is easy to explain, it may be a good idea.
説明がカンタンなら、その実装は良いのかも
PEP 20 -- The Zen of Python (opens new window)
また Python は数学的な一貫性を大切にしている気配があります。
例えば関数定義文では def
という予約語を使います。
大抵関数 function からとった func
, fun
または型名と公開範囲の接頭辞 private
, public
です。
プログラミング言語の関数/メソッド定義の修飾子
— gomiryo (@gomiryo) May 23, 2019
c/c++... function
Fortrun... function
php... function
julia... function
lisp... defun
Go... func
swift... func
python... def
ruby... def
kotlin... fun
Elixcer... fn
c/c++/c#/java/bash... なし(?)
※個人的にはfnでよくね?と思います。
range(n)
の範囲が 1...n ではなく 0 ... n -1 なのも恐らく
Dykstra の文献を基にしていると思われます。
またタプル tuple も数学用語です。
# 2. 実装面
親クラスのメソッドを呼び出したい時があります。 もし「self に関数を呼び出してきたインスタンスオブジェクトを暗黙的に代入するような設計」だと、 self にクラスモジュールが代入されてしまい、再利用できなくなります。
#
# 「暗黙的に self にメソッドを呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。
#
class A(object):
message = "Hello, world!"
def f():
print(self.message)
class B(A):
message = "Nihao, shijie!" # 你好,世界!
def g():
# 親クラスの関数 f を再利用して
# "Nihao, shijie!" を表示したいけど...
# self にはクラスオブジェクトの A が暗黙的に代入されて
# "Hello, world!" が表示されてしまう。
A.g()
なぜメソッドの定義や呼び出しにおいて ‘self’ を明示しなければならないのですか? - Python よくある質問 (opens new window)
第二に、特定のクラスからメソッドを明示的に参照または呼び出ししたい時に、 特別な構文が必要なくなります。C++ では、派生クラスでオーバーライドされた基底クラスからメソッドを使うには、
::
演算子を使わなければなりません。Python では、
baseclass.methodname(self, <argument list>)
と書けます。 これは特に、 __init__() メソッドに便利ですし、派生クラスのメソッドが、 基底クラスにある同じ名前のメソッドを拡張するために、 基底クラスのメソッドをどうにかして呼び出したい時にも便利です。
super
使って呼び出せばいいじゃんという話があるかもしれません。
できない訳ではないと思いますが、
ただ、親クラスの関数 A.f
を参照できなくなるため
super
を実装することそのものが、複雑になってしまいます。
# self を省略しないなら
class B(A):
def g(self):
# super().f() と同じ動作をする
# Python のコードは簡単に書ける。
super().f()
A.f(self)
# 「暗黙的に self に関数を呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。
class B(A):
def f():
# super().f() と同じ動作をする
# Python のコードは簡単に書けない。
super().f()
super(A, self).f()
???
# -> super クラスを実装するために
# 特別な機能を新しく作らないといけない
super
は、親クラスを参照するための機能ではありません。
親クラスを参照するには A.f(self)
とすればいいだけです。
では super
は何をしているのでしょうか?
ひし形継承した時に2回メソッドを呼び出すのを避けるための機能です。
super
については、この記事が一番わかりやすいです。
正直、自分は未だにあんまりちゃんと理解できてません。
super を使うメリットは、コンストラクタのような クラス階層全体にまたがるメソッドチェーンを、 ダイヤモンド型の多重継承を行った場合でも実現できることである。
Python の super のメリットとデメリット - methaneのブログ (opens new window)
このようにして Python では親クラスを、とても簡単に参照することができます。 しかし、言語の中身の実装なんか、どうだっていいじゃない?という意見もあるかもしれません。
ここで Flask の開発者である Armin Ronacher(イケメン) (opens new window) が、 Python は、文法はいいんだけど、内部の実装がちょっと複雑だよね。と怒っている記事を紹介します。 ちょっと難しいかもしれません。
# 例 2. ある処理をメソッドとしても関数としても使いたい時
もう一つの論拠は,"self" を明示的に引数として記述することで, 関数を追加して,クラスを動的に書き換えることができるようになる,というものである。 つまり,複数のクラスで共用できるメソッドを作れるというわけである。 たとえば,以下のようにして,クラス C とまったく同じクラスを作ることができる。
# メソッドを持たないクラスを定義: class C: pass # グローバル関数を定義: def meth(myself, arg): myself.val = arg return myself.val # メソッドをクラスに追加: C.meth = meth
汎用的な関数を定義した時、この手の関数はメソッドとして再利用したい時があります。 もし「self に関数を呼び出してきたインスタンスオブジェクトを暗黙的に代入するような設計」だと、 関数をメソッドとして再利用することがかなり煩雑になります。
# 「暗黙的に self に関数を呼び出してきた
# インスタンスオブジェクトを代入するような設計」がされているなら。
# 1. 関数はメソッドとして再利用できない。
def change_name(person, name):
person.name = name
class BoyFriend:
# こういうのはできない...
# change_name = change_name
# 面倒だけど呼び出す必要がある
def change_name(name):
global change_name # <--- そのままだと再帰呼び出しになるので
# global 文を使う
change_name(self, name)
boy_friend = BoyFriend()
# 関数
change_name(boy_friend, 'やる夫')
# メソッド
boy_friend.change_name('やる夫')
global (opens new window) 文は、 ここで擬似的に作ったものではなく、ちゃんと Python にあります。 global 文で指定された変数は、グローバルスコープを参照します。
上のコードで言えば BoyFriend クラスの中で定義した change_name(name)
ではなく、
一番上で定義した change_name(person, name)
を呼び出すようになります。
あとこうやって、他の関数だけを呼び出すだけのことを、 たまにカッコつけて 委譲 (opens new window) とか表現されていたりします。
初めて委譲という言葉を知った時、相当すごいことするのかと思ったのですが、 調べてみたら、そうでもありませんでした。
# 例 3. 第一引数 self を操作するデコレータを定義したいとき
しかしながら, Bruce がコンピュータに 超能力者でも仕込まない限り解決できない状況が一つある。 デコレータである。私は,彼がデコレータについて考えなかったことが, 彼の提案の最も大きな欠点だと思っている。 メソッドがデコレートされたときに,self 引数が渡されるかどうかを知ることはできない。
なぜ Python のメソッド引数に明示的に self と書くのか (opens new window)
関数やメソッドに共通の前処理、後処理を定義する際には、デコレータを使います。
第一引数 self を操作するデコレータを定義したいとき、 self が明示されていれば実装できます。 例えば classmethod デコレータも, staticmethod デコレータは、 self を操作するデコレータです。
ところが、もし「self に関数を呼び出してきたインスタンスオブジェクト あるいはモジュールを暗黙的に代入するような設計」をされてしまうと、 第一引数 self を扱えなくなります。 classmethod デコレータや staticmethod デコレータと同じ動作をどうやって実装しましょう?
そのような動作は、クロージャだけでは実装できなくなります。 そうなってくると処理系のレベルで、classmethod, staticmetod デコレータのための特別な実装が必要になります。 Guido はそのような実装をすることを却下しています。
"@classmethod" や "@staticmethod" を特別な記法で代替するハックを私は却下する。 また,メソッドの定義だけを検査して, 自動的にクラスメソッドかインスタンスメソッドかを決めるような手法が良いアイデアだとも思わない (実際だれかがBruceのブログにコメントする形で提案していたが)。 そのような仕様を採用すると,メソッドの前にはdefという宣言があるだけなのに, メソッドは複数の異なった呼ばれ方をすることになる。どのように呼ばれるかを判断しづらくなる。
なぜ Python のメソッド引数に明示的に self と書くのか (opens new window)
# 疑問: なんで self って書くことが不自然に感じるんだろう?
答え: メソッドを定義していると思っているから
自分も self を頭に書くと可読性という実用性がひどく損なわれる気がしたので Bruce さんと同じこと考えてました。 しかも、これさえなければ Python は素晴らしいのに、とさえ思っていました。なので Bruce さんがコテンパンにされてるのを見て凹みました。
では、なぜ self を第一引数に書くと不自然だと感じるのでしょうか? それは「メソッドを定義していると思っているから」です。 厳密に言えばクラス定義文の中で書いてるのは、関数定義文です。 メソッドではなく関数を定義しています。
実際に Python 言語リファレンスを覗いてみても funcdef 関数定義文 と言うのはあったのですが methoddef のようなメソッド定義文に該当しそうなものはありませんでした。
compound_stmt ::= if_stmt
| while_stmt
| for_stmt
| try_stmt
| with_stmt
| funcdef <--------- 関数定義文
| classdef
| async_with_stmt
| async_for_stmt
| async_funcdef
これは EBNF (opens new window) と呼ばれる記法です。 この EBNF で Python の文法が記述されています。
# ◯ 関数に傾きがちな Python
どうやら関数というのは、とても汎用的な表現のようです。 Python は、できるのであれば、関数という枠の中に収めようとしている気配を感じます。 メソッドの定義以外にも、そのようなものが2つあります。
メソッド定義文を導入せず、都度 self 書いてでも、関数定義文で書くメソッド定義
ジェネレータ関数定義文を導入せず、関数とは動作が違うにもかかわらず、関数定義文で書くジェネレータ関数定義
慈悲深き終身独裁官による判決 - PEP 255 Simple Generatorsメソッドを定義してから、それを関数で呼び出すという若干二度手間を踏む感のある len, str, repr, abs といた処理
Python の len はなんでメソッドではなく関数なの?
# ◯ まとめ
「なぜ self を書くのか?」という疑問は、「なぜ関数定義文でメソッドを定義するのか?」に言い換えることができますし、 おそらくそっちの方が考えやすいかなと思います。さっそく、質問の文章を変えて、もう一度、考え直してみたいと思います。
# Ruby の文法
このあと Python と Ruby の文法を比較しながら、 第一引数がない場合、上記3つの処理を実現するには言語的に 新しい機能を追加することを見ていきたいと思います。 その前に簡単に Ruby の実行環境と文法についてご紹介いたします。
# ◯ 実行環境
Ruby のコードもコピペで動くようにしてあります。
# 1. paiza.io
これが一番便利です。
# 2. IRB(Interactive Ruby)
Ruby のシェル, IRB(Interactive Ruby) を起動するには irb
を実行します。
下記のサンプルコードも、シェルにコピペで実行できるようになっています。
$ irb
irb(main):001:0>
$
# ◯ 文法
実際に Ruby のコードを読む前に、いくつか異なる点を 簡単に Ruby の文法をご紹介します。 以下の Python のコードを
#
# Python
#
class GirlFriend:
def __init__(self, name):
self.name = name
girl_frined = GirlFriend('岩倉玲音')
print(girl_friend.name)
girl_friend.name = 'バラライカ'
print(girl_friend.name)
Ruby に書き直すと以下のようになります。
#
# Ruby
#
class GirlFriend
attr_accessor :name
def initialize name
@name = name
end
end
girl_friend = GirlFriend.new '岩倉玲音'
puts girl_friend.name
girl_friend.name= 'サーバルちゃん'
puts girl_friend.name
これに簡単にコメントを添えます。
#
# Ruby
#
class GirlFriend
# 1) インデントはスペース 2 文字
# 2) 使用する属性を明記
attr_accessor :name
# 3) __init__ ではなく initialize
# 4) メソッドの定義に括弧 () が不要
def initialize name
# 5) self ではなく @
@name = name
end
end
# 6) メソッドの呼び出しに括弧 () が不要
girl_friend = GirlFriend.new '岩倉玲音'
puts girl_friend.name
girl_friend.name= 'サーバルちゃん'
puts girl_friend.name
# 1. インデントはスペース 2 文字
Ruby の正式なコーディング規約はありませんが、 2 文字であることが多いそうです。
Rubyの正式なコーディング規約はありません。
コーディング規約 (opens new window)
ちなみに Python では PEP 8 でインデントが 4 文字に定められている理由は、 ネストを深くして欲しくないからだと個人的に思っています。
ネストは浅い方が良い
Flat is better than nested.
PEP 20 - The Zen of Python (opens new window)
# 2. initialize メソッド
initialize
メソッドは Python の __init__
です。
def initialize name
@name = name
# 3. メソッドの括弧
あれ?括弧ないじゃん?と思われるかもしれません。Ruby は括弧が省略できるそうです。 メソッドの定義と呼び出しに括弧がいりません。 ちなみに括弧をつけてもメッソドを定義したり、呼び出したりできます。
# 呼び出しの時も不要
girl_friend = GirlFriend.new '岩倉玲音'
puts girl_friend.name
# 4. インスタンスの属性
Python で言う所の self
には @
を使います。
def initialize name
@name = name
# 5. メソッドの引数
この initialize
メソッドの引数に
Python の self
に当たるものがありません。
# 6. getter と setter を書かないといけない。
基本的には属性に代入したり、あるいは属性を参照するのにメソッドが必要です。
そのため attr_accessor
を使わない場合は、
ちゃんと定義しないと属性に代入したり、属性を参照できません。
# Ruby
class GirlFriend
def initialize name
@name = name
end
# 慣習的に setter には末尾に = をつける。
# setter= で関数名
def name= name
@name = name
end
def name
@name
end
end
girl_friend = GirlFriend.new '岩倉玲音'
puts girl_friend.name
girl_friend.name= 'サーバルちゃん'
puts girl_friend.name
# なんと例外的に末尾の = は離してもかける
girl_friend.name = 'バラライカ'
puts girl_friend.name
ただ、それだと面倒なので attr_accessor
というものがあります。
attr_accessor :name
はメソッドを呼び出しています。
attr_accessor
は getter と setter を定義してくれるメソッドらしいです。
# 7. シンボル
:name
はシンボルです。
先頭にコロン :
のついた文字列をシンボルと言うそうです。
文字列とほぼ同じですが、シングルトンで辞書のキーなどに使われるそうです。
# 8. return 文を省略する。
サンプルコードでは return を省略しました。
def name
@name
end
return 文を省略すると最後に評価された式の値が返されます。
Ruby ではメソッドの最後の return は使っても使わなくても全く同じ 意味に書ける。... 理由は、まず簡潔さ。それから「Ruby ではすべての式が値を持つ」と いう基本思想を適切に表現するからである。 if も while もイテレータ もメソッドも同様に値を持つ。ならば書式も全部同じにするのが適切で ある。 あらゆる式の記述の中にオブジェクトの流れを見るのだ。
return - Ruby のコーディングスタイル (opens new window)
# 疑問: なんで関数定義文でメソッドを定義するの?
答え: 関数定義文は、メソッド定義文よりも汎用的な表現だから
もう一度、最初の疑問に戻ります。 メソッッドを定義する度に self をわざわざ書くのは可読性という実用性を損ねている気がします。 関数という一貫性を重視する必要性は、あったのでしょうか?
特別なケースは、一貫性を破るほど特別ではない。
Special cases aren't special enough to break the rules.Although practicality beats purity.
けれども実用性は、一貫性に勝る。
Python の開発者たちが出した答えは "はい" です。 そして Ruby の開発者たちが出した答えは "いいえ" です。 ここで Ruby を例にとって見てみたいと思います。 自分は Ruby を書いたことが無いので、間違っていたらご容赦ください。
関数をメソッドとして共有したり、親クラスのメソッドを読んだり、 クラスメソッド, スタティックメソッドするときについて考えて見ます。
Python では "新しい機能は不要" です。一貫性の高いコードでこれを実装することができます。 Ruby では "新しい機能が必要" になってきます。サンプルコードを比較しながら見ていきたいと思います。
# 例 1. 親クラスのメソッドを指定して呼ぶ
Python では、機能を追加しなくても、 属性参照で対応することができます。
class A(object):
def f(self):
return 'Hello, world!'
class B(A):
def f(self):
# 属性参照だけ
return A.f(self)
# Python にも super がありますが
# これは、ひし形継承されていた時に
# 祖先クラスのメソッドが
# 2度呼び出されるのを避けるためのものです
# Ruby の super_method と
# Python の super は目的としている機能が少し違います。
# return super().f()
# 1) alias
Ruby では alias
を追加して対応しています。
class A
def f
'Hello, world!'
end
end
class B < A
# alias という機能が必要
alias :super_f :f
def g
super_f
end
end
o = B.new
o.g
# 2) super_method
もし super_method
を使うなら class C は次のような具合になります。
class A
def f
'Hello, world!'
end
end
class B < A
def g
# super_method という機能が必要
method(:f).super_method.call
end
end
o = B.new
o.g
# 例 2. ある処理をメソッドとしても関数としても使いたい時
機能的に簡潔に実装する方法は見つけられませんでした。
@
を使っているので機能として提供することは文法の制約上難しいかなと思います。
煩雑ですが上で書いたの self 無しの
偽 Python のコードのように委譲すればできないこともないとは思います。
# メソッドとしても関数としても
# 使いたい処理
def print_name_(person)
puts person.name
end
class Person
attr_accessor :name
def initialize(name)
@name = name
end
# 委譲するしかない...
def print_name
print_name_(self)
end
end
person = Person.new('岩倉玲音')
person.print_name
# 例 3. スタティックメソッド、クラスメソッド
# 1) タティックメソッド
Python には、スタティックメソッドがあります。 Ruby では、スタティックメソッドがありません。
# 2) クラスメソッド
Ruby では、クラスメソッドを特異メソッドで対応しています。 Python では、デコレータで対応しています。
特異メソッドとは?単一のオブジェクトに特化したメソッドのことらしいです。
特異メソッドについて - Qiita (opens new window)
Ruby では、クラスメソッドはクラスオブジェクトからアクセスしなければなりません。 Python では、クラスメソッドにインスタンスオブジェクトから直接参照できます。
class Hoge:
@classmethod
def class_name(cls):
return cls.__name__
hoge = Hoge()
# クラスオブジェクトから呼び出し
Hoge.class_name()
# インスタンスオブジェクトから直接呼び出し -> できる
hoge.class_name()
class Hoge
# def self.メソッド名 という機能が必要
def self.class_name
self.name
end
end
hoge = Hoge.new
# クラスオブジェクトから呼び出し
Hoge.class_name
# インスタンスオブジェクトから呼び出し -> できない
hoge.class_name
Python は一見してデコレータ @
が必要そうですが、
デコレータ @
という機能がなくても、上記と同じものを実装できます。
class Hoge(object):
def class_name(cls):
return cls.__name__
# 代入だけ
class_name = classmethod(class_name)
hoge = Hoge()
# クラスオブジェクトから呼び出し
Hoge.class_name()
# インスタンスオブジェクトから直接呼び出し -> できる
hoge.class_name()
# 疑問: Python は Ruby よりも優れているのか?
答え: そういうわけではないかなと思ったりします。
Ruby に比べて Python の方が一貫性のある書き方ができそうです。ここで言う一貫性とは、新しい機能を追加しなくても3つの機能を実装できました、と言うことです。
だからと言って、Python の方が優れているとは言えないかなと思ったりします。実際 Python 以外で関数定義文でメソッドを定義する言語をあまり見たことがないので、必要性が薄かったりするのかなと思ったりもします。
第1の理由は、頻度の多寡です。上で見た3つのことをしようとする機会がそんなに多くありません。反対に self を書く機会は、ゲシュタルト崩壊を起こしかねないくらい、とても多いです。
第2の理由は、影響の大小です。一貫性のある書き方ではできませんが、それでもその機能が実装できない訳ではないからです。あるいはなかったとしても、そこまで困りません。影響について、3つの例を振り返って見ます。
# 例 1. 親クラスの関数を呼び出す時
この点において Python は Ruby よりも簡単に操作もできますし、 きっと実際に中身の実装もシンプルなものだと思います。
ただ、本来 実装による継承は避けられるべき と言うことを考えると、 実装による継承をしているということ自体、あまりよくないのかもしれません。
# 例 2. ある処理をメソッドとしても関数としても使いたい時
そんなことがしたい時は、恐らくそんなに無いかなと思います。
# 例 3. スタティックメソッド、クラスメソッド
あった方が可読性は良くなりますが無くても困らないかなと思います。 クラスメソッドについてはクラスオブジェクトを取得して self を使わなければ、それでことが済みます。 スタティックメソッドは self を使わなければ、それでことが済みます。 インスタンスを参照できてしまうので、正確には違いますが。
class Hoge
def class_method
# cls を取得して
# self を参照しなければいい
cls = self.class
cls
end
def static_method
# self を参照しなければいい
'Hello, world!'
end
end
hoge = Hoge.new
hoge.class_method
hoge.static_method
# ◯ 実用性と一貫性, Ruby と Python
Ruby は Python に比べて、一貫性よりも実用性を大事にしているのかもしれません。 例えば Python では文字列結合 join はリストのメソッドにはせず、 str のメソッドにしました。
Python では実用性(可読性)よりも、実装の一貫性が優先されていました。 Ruby では実用性(可読性)が、一貫性よりも優先されて実装されています。
# Python 読みにくい
", ".join(["hello", "world"])
# Ruby 読みやすい
["hello", "world"].join(", ")
一貫性 purity を大事にした Python がデータサイエンス系の分野で好まれて、 実用性 practicality を大事にした Ruby がウェブ系の分野で発展を遂げたのは、 ある意味そういうところにもあるのかなと思ったりもします。
もともと積極的に数値計算系のライブラリ NumPy などをサポートしていたという、 コミュニティの運営のされ方も大きいと思います。
Python の一貫性 purity は美しさであり、 Ruby の実用性 practicality は楽しさかなと思ったりもします。 自由な楽しさと言うのは新しいサービスを生み出すこととマッチしているような気もします。
Rubyの価値は「楽しいプログラミング」
Ruby誕生から25年、そしてこれからのRubyは––まつもとゆきひろ氏が描く未来 (opens new window)
Rails は、ベイエリアの求人において、もっとも人気のあるウェブフレームワークです。
Rails is the most popular web framework in the bay area by job openings.
2018/06/17 Demand for Ruby on Rails is Still Huge (opens new window)
# さらに他言語について
はじめて Python を見たとき、 僕は self さえなければ素晴らしい言語なのにと思っていました。 そしてつい最近までそう思っていました。
しかし気がついて見たら self を使い、
論理的に foo.meth(arg) == C.meth(foo, arg)
この式が正しいこと前提にして、
「関数とメソッドの違い」を説明していました。
また、近年生まれた Go, Rust は、 第一引数にインスタンスを代入する形式になっています。 さらに Go では、この考えを押し進めてクラス定義文がありません。 推し進めてとはどういうことでしょうか? 以下は Go のコードです。
package main
import (
"fmt"
"strings"
)
type Dog struct {
name string
}
// (インスタンス 型名) メソッド名(引数1 型1, ...) 返り値の型
// vvvv ここにインスタンスを書いています。
func (self *Dog) say(times int) string {
return self.name + strings.Repeat("ワン", times)
}
func main() {
dog := &Dog{name: "シロ"}
fmt.Println(dog.say(2))
}
Go はオブジェクト指向言語ですか? (opens new window)
Is Go an object-oriented language?さらには Go におけるメソッドは C++ または Java よりも汎用的です:
Moreover, methods in Go are more general than in C++ or Java:どのような種類のデータに対してもメソッドを定義することができます、plain や "unboxed" integer のような組み込み型に対してもです。
they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers.メソッドを定義する対象は struct で定義されたもの(Java, C++ で言えば class で定義されたもの)に限定されません。
They are not restricted to structs (classes).また継承ができないようにすることによって C++ や Java のような言語よりも、オブジェクトをより軽量なものにしています。
Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.
このようにしてクラス定義文をなくすことで int
, double
などの組み込み型に対しても、
メソッドを定義することができ、多態性が使えるようになります。
これの何が嬉しいのでしょうか?
例えば Java では int
などの型に
メソッドを持たせるために Int
型をわざわざ用意しています。
前者を基本形 primitive type と呼び後者を参照型 reference type と呼びます。
じゃあ Java みたいに別に新しく型を用意すればいいんじゃないのか?という感じなのですが、 この2つの型があるので、Java では色々と面倒なことが起こっているそうです。 これについては書籍 Effective Java - Item 61 Prefer primitive types to boxed primitives にとても丁寧に説明が載っています。
Rust も Python のクラス定義文とは少し変わった形式になっています。 何か意図があると思い頑張って読んでは見るんですけど、 完全に?って感じになります。メソッドのディスパッチとか、どうなってるんだろう...
Python よりあとに生まれた言語で、第一引数にインスタンスを代入しないのは、 Ruby, Java, Kotlin, JavaScript, TypeScript があります。 反対に第一引数にインスンタンスを代入するものに Go, Rust, Julia そして Python があります。
# おわりに
ここまで以下のような流れて見てきました。
Python では「なぜ、第一引数に明示的に self を書くのか?」は、 実は「なぜ、メソッドの定義を関数定義文で行なっているのか?」に言い換えることができます。
そして、「なぜ、メソッドの定義を関数定義文で行なっているのか?」に対する答えは 「関数定義文はメソッド定義文よりも汎用的な表現なので、より複雑なことをしようと思った時でも、 一貫性を崩すことなく(Python 本体に新しい機能を追加しなくても)実装することができるからです。 これは最終的に可読性の良さをもたらすから。」です。
Python は可読性、実用性を重視するが故に、私たちが思っている以上に、一貫性のある実装を優先しています。 このことが str.join メソッドや self を明示したりすることに対して感じた違和感の原因でした。
また、何でも一貫性を重視すればいいというものではなく、その辺りのバランスは物によりけりかなとも思います。 Ruby はウェブ系で、 Python はデータサイエンス系で発展しました。