Pythonでは、デフォルト引数は関数が定義される時にのみ一度だけ評価され、呼び出されるたびには評価されません。これは、デフォルト値として可変オブジェクト(例えばリストや辞書)が使用される場合、それがその引数が明示的に指定されていないすべての関数呼び出しで共有されることを意味します。
例:
def append_item(item, items=[]): items.append(item) return items print(append_item(1)) # [1] print(append_item(2)) # [1, 2]、期待されるのは [2] である
正しい方法:
def append_item(item, items=None): if items is None: items = [] items.append(item) return items
これで、各呼び出しがそれぞれのリストを取得します。
質問: デフォルト値として可変オブジェクトを持つ関数を複数回呼び出した場合、何が起こりますか?
答え: 同じオブジェクトが毎回変更されます。上記の例がこれを示しています — リストはすべての値を蓄積します。
物語 大規模なウェブアプリケーションでデータをキャッシュする際、デフォルト引数に辞書を持つ関数が使用されました。その結果、異なるユーザー間でデータが「漏れ」合い、誰かが自分のプロフィールを変更すると、その変更が時折他のユーザーに表示されることがありました。
物語 テストで、デフォルト値としての可変リストを持つ関数が統計を集めるために使用されました。一つのテストのデータが別のテストに「流れ込む」ことがあり、予期しないクラッシュやバグの再現困難に繋がり、デバッグが難しくなりました。
物語 マイクロサービスでログの集約を行うために、デフォルト引数がリストである関数を使ってイベントの蓄積が行われました。その結果、古いリクエストからの一時的な蓄積が新しいクライアントに転送され、調査に何時間もかかり、データが失われました。