デフォルト引数の評価される順番
redmineのソースコードを読んでいたら、デフォルト引数の右辺に関数を使っていたので、気になって調べてみました。
個人的には発見した感がありますが、実際にはデフォルト引数の右辺に関数持ってくる実装は可読性を下げる可能性が高そうなので、やめておいた方がいいかと思います。
Pythonの場合
以下のコードを実行した時、標準出力されるのはどういう順番かと言う話です。
def func1():
print("begin func1")
return "func1"
def func2(a=func1()):
print("begin func2")
return "func2"
class ClassA():
print("begin classA")
def funcA(self, a=func2()):
print("begin funcA")
print("main")
print(func2())
a = ClassA()
a.funcA()
正解は、以下の通り。
begin func1
begin classA
begin func2
main
begin func2
func2
begin funcA
関数の宣言が読み込まれた時点で評価されています。
ちなみにclass直下のところで呼び出される関数もclassの宣言を読み込んだ時点で評価されるので、このような順番になっています。
Rubyの場合
同じ形でrubyも書いてみます。
def func1
p "begin func1"
"func1"
end
def func2 a=func1
p "begin func2"
"func2"
end
class ClassA
p "begin classA"
def funcA a=func2
p "begin funcA"
end
end
p "main"
p func2
a = ClassA.new
a.funcA
この実行結果は以下の通り。
"begin classA"
"main"
"begin func1"
"begin func2"
"func2"
"begin func1"
"begin func2"
"begin funcA"
pythonと違いデフォルト引数の右辺に来ている関数は、関数が実際に呼び出されたタイミングで評価されます。
それ以外の違いはないのですが、だからこそ見つけづらい言語の違いかなとおもいました。
さらにスコープについて
コメントで同じことを書いている記事をみていたらスコープについても検証されていたので確認してみました。
class ClassA
p "begin classA"
xx = 100
def funcA a=xx
p a
p "begin funcA"
end
end
a = ClassA.new
a.funcA
このようにするとxxは存在しないとなってrubyではエラーとなり、pythonでは普通に参照され100が表示されます。
これは、元々の概念に置き換えると納得できるスコープの考え方で、rubyではデフォルト引数の部分は関数の中で評価されることになるので、関数の中と同じスコープとなり、xxは存在しないことになります。
一方でpythonとrubyでそもそもclass直下の変数の意味が違っています。
pythonの場合は、クラス変数扱いになり、rubyはclassブロックの処理という扱い(こっちは自信なし)になります。
なので、以下のようにするとrubyでもエラーにならずに動きます。
(@@xxはクラス変数、CCはクラス定数となります)
class ClassA
p "begin classA"
@@xx = 100
CC = 10
def funcA a=@@xx
p a
p "begin funcA"
end
def funcB a=CC
p a
p "begin funcB"
end
end
a = ClassA.new
a.funcA
a.funcB
まとめ
書き出すと単純ですが以下のことが言えるかなと思います。
- rubyとpythonではデフォルト引数の右辺の評価タイミングが違う。
- rubyは関数が実行された時に実行(関数の中で実行されるのとイコール)
- pythonは関数の宣言が読み込まれた時に実行(関数の外で実行されるのとイコール)
- class直下の関数はpython、ruby共にclassの宣言を読み込んだ時点で実行される。
- スコープは評価と同じようにpythonは関数の宣言なので関数の外のスコープと一緒、rubyは関数の実行時に評価されるので関数の中と同じスコープ
この違いによりシンタックスエラーの見つかり方も若干変わってくるかなと思います。
pythonだと先に実行されるので、アプリケーション実行後すぐにエラーになるのに対して、rubyは関数が実行されるまでエラーにならないことになりますし、普通にエラーになりにくいのではないかと思います。
そして、この結果からさらに言えるのは、pythonではこの書き方は基本NGだと思います。moduleの読み込まれる順序に依存して関数の結果が変わる可能性があるため、実行結果が場合によって変わってしまいます。
一方で、rubyは可読性は下がるにせよ、それなりに安全にこの書き方を許容できるのではないかと思います。