概要

Pythonでは、関数定義時にデフォルト引数が評価され、その後もそのオブジェクトが再利用される仕様があります。特にリストや辞書のような可変オブジェクトがデフォルト引数に使用されると、関数を複数回呼び出すたびに予想外の動作を引き起こすことがあり、これを”Least Astonishment”問題と呼びます。

可変デフォルト引数の動作

Pythonでは、デフォルト引数は関数定義時に一度だけ評価され、その後の関数呼び出し時にもその同じオブジェクトが使用されます。例えば以下のコードを見てください。

def foo(a=[]):
    a.append(5)
    return a

この関数を複数回呼び出すと、次のような出力が得られます。

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]

期待される動作は、毎回新しいリスト[5]が返されることですが、実際には同じリストが再利用され続けるため、結果が蓄積されていきます。

背景

この動作は、Pythonの関数がファーストクラスオブジェクトであることに由来します。関数定義時にデフォルト引数のオブジェクトも関数の一部として評価されるため、後の呼び出しでも同じオブジェクトが使用されるのです。

解決策

この問題を回避するための最も一般的な方法は、デフォルト引数にNoneを使用し、関数内部で新しいオブジェクトを生成することです。次のように書くと、安全に可変デフォルト引数を扱うことができます。

def foo(a=None):
    if a is None:
        a = []
    a.append(5)
    return a

このように変更すると、毎回新しいリストが生成されるため、期待通りの動作が実現します。

他の利用法とベストプラクティス

一部のPython開発者は、この動作をメモ化やキャッシュとして利用する場合もあります。デフォルト引数に可変オブジェクトを使い、関数呼び出し間でその状態を保持したい場合には、この仕様が役立つこともあります。しかし、これを意図的に利用するには深い理解が必要であり、初心者には推奨されません。

まとめ

Pythonの可変デフォルト引数の問題は初心者にとって驚きの一因となりますが、この動作はPythonの内部設計に由来するもので、意図されたものです。可変引数が不適切に使用されることで、バグが発生することが多いため、Noneを使用する方法が広く推奨されています。