はじめに
この記事では、Pythonのデフォルト引数にミュータブルなオブジェクトを使用する際の注意点について説明する。具体的には、デフォルト引数に辞書を使用する際に生じる問題と、その解決策を示す。
ミュータブルなデフォルト引数の問題
Pythonでは、関数やメソッドのデフォルト引数は、関数が定義されるときに一度だけ評価される。そのため、ミュータブルなオブジェクト(例:リストや辞書)をデフォルト引数として使用すると、予期しない挙動が発生することがある。
以下に例を示す。
from typing import Dict
class MyDict():
def __init__(self, data: Dict[str, str] = {}):
self._data = data
def __str__(self) -> str:
return str(self._data)
def update(self, component: Dict[str, str]):
return self._data.update(component)
if __name__ == "__main__":
myd0 = MyDict()
myd0.update_a({"test0": "hoge"})
myd1 = MyDict()
myd1.update_a({"test1": "hoge"})
print(myd0)
print(myd1)
出力は下記のようにmyd0で追加した値がmyd1にも入ってしまっている。
どうもmyd1を生成する際のコンストラクタの引数にmyd0の値が入っているよう。
{'test0': 'hoge'}
{'test0': 'hoge', 'test1': 'hoge'}
原因
コンストラクタでデフォルト引数として空の辞書を設定していることが原因 。
Pythonでは、関数のデフォルト引数は関数が定義されるときに一度だけ評価されるため、すべてのインスタンスで同じ辞書オブジェクトが共有される。そのため、myd0で追加したデータがmyd1にも入ってしまう。
推奨される解決策
ミュータブルなオブジェクトをデフォルト引数として使用する際は、デフォルト引数をNoneに設定し、コンストラクタ内で新しいオブジェクトを作成する。
以下にサンプルを示す。
class MyDict():
def __init__(self, data: Dict[str, str] = None):
self._data = data if data is not None else {}
この実装では、各インスタンスが独自の辞書オブジェクトを持ち、データが互いに影響を与えないようになる。