LoginSignup
162
81

More than 3 years have passed since last update.

⚠️Pythonのデフォルト引数値に気をつけましょう⚠️

Last updated at Posted at 2020-06-16

ちゃお・・・†

最近Pythonのデフォルト引数値による意図せぬ挙動に悩まされたので、同じ過ちを繰り返さぬようここで情報を共用したいと思います。こちらの環境はPython 3.7.7と3.8.3です。

デフォルト引数値とは

下記コードのうち dt=datetime.now()を指します。

from datetime import datetime


def show_second(dt=datetime.now()):
    print(dt.second)

Pythonのデフォルト引数値の罠

先ほどのコードを、 show_second 関数を一度呼び出し、三秒後にまた show_second 関数を呼び出すようにいじってみました。

import time
from datetime import datetime


def show_second(dt=datetime.now()):
    print(dt.second)

show_second()  #=> 23
time.sleep(3)
show_second()  #=> 23

すると、なんということでしょう!三秒間sleepしたにも関わらず二度目の show_second 関数呼び出し時にプリントされる値が三秒前と同じでした…!?時が止まった?ザ・ワールド???新手のスタンド使いか???

Pythonのデフォルト引数値の挙動

さて、これはどういうことかとPythonのドキュメントを読んでみたところ、下記の記述がありました。

デフォルト引数値は関数定義が実行されるときに左から右へ評価されます。 これは、デフォルト引数の式は関数が定義されるときにただ一度だけ評価され、同じ "計算済みの" 値が呼び出しのたびに使用されることを意味します。
from: https://docs.python.org/ja/3/reference/compound_stmts.html#function-definitions

つまり、関数定義時にデフォルト引数値は一度評価され、その結果がメモリーに保存されて、以降はその関数を何回呼び出してもデフォルト引数値を利用する場合は関数定義時の評価結果を使用するという仕組みになっているようです。

なので、datetime.now() のようなcallする度に違う結果を出すもの and/or リアルタイム性が求められるものをデフォルト引数値として用いるのは危険です⚠️

同様にデフォルト引数値にリストや辞書を指定したときも気をつける必要があるようです。(Pythonの関数でのデフォルト引数の使い方と注意点 | note.nkmk.me)

Pythonのデフォルト引数値の対策

それではどうしたらいいかというと、デフォルト引数値に None を設定して、None であれば本来デフォルト引数値として設定したかった値を代入するという風にするのが良いそうです。

以下example codeです。

import time
from datetime import datetime


def show_second(dt=None):
    if dt is None:
        dt = datetime.now()
    print(dt.second)

show_second()  #=> 23
time.sleep(3)
show_second()  #=> 26

そんなわけでPythonのデフォルト引数値に気をつけようというお話でした。

162
81
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
162
81