Ccmmutty logo
Commutty IT
2 min read

Pythonでデフォルト引数にミュータブルなオブジェクトを使用する際の注意点

https://cdn.magicode.io/media/notebox/6acc119e-1391-4538-9886-aeeb424047ae.jpeg

はじめに

この記事では、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 {}
この実装では、各インスタンスが独自の辞書オブジェクトを持ち、データが互いに影響を与えないようになる。

まとめ

Pythonのデフォルト引数にミュータブルなオブジェクトを使用する際には注意が必要。
ミュータブルなオブジェクトをデフォルト引数に使用すると、予期しない挙動が発生することがあり、デフォルト引数をNoneに設定し、コンストラクタ内で新しいオブジェクトを作成するのが推奨される。これにより、各インスタンスが独自のオブジェクトを持ち、データが互いに影響を与えないようになる。

Discussion

コメントにはログインが必要です。