🐡
Pythonのデフォルト引数で沼った
Python でデフォルト引数を使ったらめっちゃ沼ったので備忘録
問題のコード
datetimeモジュールを使って、10 分後を算出する関数を実装していたときのコード。
これを Flask アプリケーションの中で使おうとしていた。
from datetime import datetime, timedelta
def calculate_ten_minutes_later(current_time=datetime.now()) -> datetime:
"""現在時刻から10分後の時刻を計算する
args:
current_time (datetime): 現在時刻。テストコード用に引数化している
returns:
datetime: 現在時刻から10分後の時刻
"""
return current_time + timedelta(minutes=10)
def main():
ten_minutes_later = calculate_ten_minutes_later()
log(f"10分後の時刻は: {ten_minutes_later}")
return ten_minutes_later
Pytest でテストコードも通ったし、よし!
import pytest
from datetime import datetime, timedelta
def test_calculate_ten_minutes_later():
fixed_time = datetime(2024, 1, 1, 12, 0, 0)
expected_time = fixed_time + timedelta(minutes=10)
assert calculate_ten_minutes_later(fixed_time) == expected_time
いざ Flask アプリケーションで動かしてみると…
なんか知らんけど、想定通りに動かない。
ログがずっと同じ時刻を指している。
原因の特定
原因は、デフォルト引数の評価タイミングにあった。
Python においてデフォルト引数は、関数が定義された時点で一度だけ評価される。
Flask アプリケーションが起動したときに datetime.now() が評価され、その後はずっと同じ値が使われ続けていた。
解決策&まとめ
デフォルト引数に動的な値を使う場合は、None をデフォルト値にして、関数内で評価するように変更する。
from datetime import datetime, timedelta
def calculate_ten_minutes_later(current_time=None) -> datetime:
"""現在時刻から10分後の時刻を計算する
args:
current_time (datetime): 現在時刻。テストコード用に引数化している
returns:
datetime: 現在時刻から10分後の時刻
"""
if current_time is None:
current_time = datetime.now()
return current_time + timedelta(minutes=10)
ステートフルなデフォルト引数はバグの温床になるので注意!
Discussion