Python の関数コール (4) - らんだむな記憶 の続き。
前回までで CPython インタープリタの中からデフォルト引数の流れを追いかけてきた。今回はそれを踏まえて Python の世界からもう一度眺め直す。
まず最初に前回までで分かった内容をざっくりとまとめると
- 関数のオブジェクトを作成する際にデフォルト引数が tuple として構築され、同オブジェクトのメンバとしてバインドされる。
- 関数を実行する際に、関数のオブジェクトからデフォルト引数の tuple が取り出され、必要なものがリストに詰め直される。
- 関数のオブジェクトと詰め直されたデフォルト引数のリストが関数の評価器に渡され Python の関数として実行される。
ということであった(と思われる)。test.py
の main
を少し書き換えて test2.py
とする。Python 3.8 で f 文字列が更に拡張されているので使いやすい。
if __name__ == "__main__": os.system("ls") no_arg() print(f"{one_arg1.__defaults__=}") print(f"{one_arg2.__defaults__=}") one_arg1() print("***") one_arg2() print(f"{one_arg1.__defaults__=}") print(f"{one_arg2.__defaults__=}")
なお、急に __default__
を持ち出すのは https://github.com/python/cpython/blob/v3.9.3/Objects/funcobject.c#L452-L453 の実装を見ると func->func_defaults
に直結していて中身を確認するのに最適だからである。
さて、これを実行しよう。
./python test2.py ... ***** no_arg one_arg1.__defaults__=([],) one_arg2.__defaults__=(None,) *** one_arg1.__defaults__=([1],) one_arg2.__defaults__=(None,)
このことからもデフォルト引数は tuple で管理されていることが見える。そこに mutable なオブジェクトが入っている場合、実引数にそれが渡ってきてしまうので、破壊的メソッドを実行してしまうと tuple の中のオブジェクトの中身が変化してしまう。これが次回の関数呼び出しでデフォルト引数として ob_item
に渡ってきてしまうであろうことが推測できる。
以上が Python でデフォルト引数に mutable なオブジェクトを渡すことが嫌われる背景の下回りの動作の概観である。