さるへい備忘録

さるへいのやったことを綴っているブログです。基本的にテクノロジーの話題です。

デフォルト引数に関数を使う場合の注意点

Pythonの関数定義に欠かせないデフォルト引数ですが、とある罠にひっかかったので備忘録として残しておきます。

まずは、以下のようなコードを用意します

# coding: utf-8
import datetime
import time


def arg_test(message: str, now=datetime.datetime.now()):
    print(message)
    print(now)


if __name__ == "__main__":
    arg_test('デフォルト引数利用1')
    arg_test('デフォルト引数非利用1', datetime.datetime.now())

    time.sleep(1.5)

    arg_test('デフォルト引数利用2')
    arg_test('デフォルト引数非利用2', datetime.datetime.now())

datetime.datetime.now() は、現在時を取得するコード、 time.sleep(1.5)は1.5秒待機する簡単なコードです。

  1. デフォルト引数を利用、非利用で関数をそれぞれ1回実行。非利用時は、引数で現在時を入力。
  2. 1.5秒待機
  3. 1と同じことをする。

ここまでくればなんとなくわかるでしょう。
このデフォルト引数の関数が評価されるタイミングを僕は見誤っていたという話になります。
ちなみに、実行結果は以下のようになります。(実行時は 2021-12-20 14:38:16.655634

デフォルト引数利用1
2021-12-14 20:38:16.655634
デフォルト引数非利用1
2021-12-14 20:38:16.655696
待機開始
待機終了
デフォルト引数利用2
2021-12-14 20:38:16.655634
デフォルト引数非利用2
2021-12-14 20:38:18.156938

デフォルト引数を利用した方は、スリープしても表示される現在時が一切変わりません。
つまり、デフォルト引数に関数を渡した場合は、普通に引数に関数を渡した場合と関数の評価タイミングが異なるということになります。

Pythonの公式には以下のように記載されています。

docs.python.org

デフォルト引数値は関数定義が実行されるときに左から右へ評価されます。

とあります。つまり、関数を定義する際に引数の関数は実行され、 その時の値がひたすら使い回されるという形になります。
まだ単体のすぐ終わるスクリプトならいいですが、webフレームワークを利用してるときなどは、サーバ開始時の評価結果がひたすら入るので注意したほうがいいですね。

また、インスタンスなどを作成する際も以下のような事故は起こりそうで個人的には怖いなと思いました。

# coding: utf-8
from dataclasses import dataclass

outer_target = 4


@dataclass
class ArgTestClass():
    target_arg: int = 3

    def update_arg(self, num: int):
        self.target_arg = num

    def print_arg(self, target=target_arg):
        print(target)

    def print_outer_target(self, target=outer_target):
        print(target)


if __name__ == "__main__":
    ins: ArgTestClass = ArgTestClass()

    print('更新前')
    ins.print_arg()
    ins.print_outer_target()

    ins.update_arg(5)
    outer_target = 6

    print('更新後')
    ins.print_arg()
    ins.print_outer_target()

実行結果

更新前
3
4
更新後
3
4

こちらも定義後に値を更新してるけど更新されないといった形ですね。インスタンス変数をデフォルト値にいれるのはあんまりないかもしれませんが、外部のスコープの方はなんとなく使ってたらやらかしそうな感じがします。

僕は、デフォルト値にはできるだけ静的な値を使うを心がけて生きていこうと思います。