可変デフォルト引数が呼び出しをまたいで共有される
リストや辞書をデフォルト引数にすると、その1つのオブジェクトが全ての呼び出しで共有されます。呼び出すたびに新しく作られるわけではありません。
なぜエラーが出ないのか
出力: ['a']
(エラーなし)- Pythonは何も報告しません。文法として正しいためです
出力: ['a']- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 def add_item(item, items=[] ): ^ 2 if items is None: 3 items = [] 4 items.append(item) 5 return items 6 7 print(add_item("a")) 8 print(add_item("b"))
出力: ['a']
['a', 'b']期待: ['a'] / ['b'] (エラーにはなりません)
デフォルト引数の[]は関数定義時に一度だけ作られ、呼び出しをまたいで共有されます。Noneを既定値にして、関数内で空リストを作り直すのが定石です。
直し方: [] を None にします。
広告
広告スロット(未設定)
パターン2
1 def record(name, log={} ): ^ 2 if log is None: 3 log = {} 4 log[name] = True 5 return log 6 7 print(record("a")) 8 print(record("b"))
出力: {'a': True}
{'a': True, 'b': True}期待: {'a': True} / {'b': True} (エラーにはなりません)
辞書のデフォルト引数も同じです。共有された1つの辞書に、呼び出しのたびに項目が積み上がっていきます。
直し方: {} を None にします。
パターン3
1 def visit(page, history=[] ): ^ 2 if history is None: 3 history = [] 4 history.append(page) 5 return history 6 7 print(visit("home")) 8 print(visit("about"))
出力: ['home']
['home', 'about']期待: ['home'] / ['about'] (エラーにはなりません)
履歴を記録するような関数でも同じ罠にかかります。呼び出し履歴だけのつもりが、他の呼び出しの記録まで混ざります。
直し方: [] を None にします。
よくある誤解
デフォルト引数は呼び出しのたびに新しく作られると誤解されがちですが、実際には関数定義時に一度だけ作られます。可変オブジェクトを既定値にすると、前回の呼び出しの変更が残ったまま次の呼び出しに引き継がれます。
まとめ
可変デフォルト引数が呼び出しをまたいで共有されるは上級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。