可変なデフォルト引数

Pythonで可変な引数をデフォルト引数として使用すると、予期しない結果を招くことがあります。
簡単な例で説明しましょう。あなたは教師で、生徒のさまざまな課題の得点を記録する関数を作りたいとします。これらの得点を管理するためにリストを使用したいと思っています。そこで、最初は次のように書くかもしれません。
def record_score(new_score, score_list=[]):
    score_list.append(new_score)
    return score_list

print(record_score(95))  # [95]
print(record_score(85))  # [95, 85]
一見すると、これは正常に動作するように思えます。関数record_scoreは得点とリストを受け取ります。もしリストが提供されなければ、空のリストを使用します(関数定義のscore_list=[])。
record_score(95)を呼び出すと、95の得点が入ったリストが返ってきます。これは正しく動いているように見えます。しかし、次にrecord_score(85)を呼び出すと、95と85の両方が入ったリストが返されます。まるで関数が前回の呼び出し時のリストを覚えているかのようです。
これは不思議に思うかもしれません。ほとんどのプログラミング言語では、関数が終了すると、そのローカル変数(例えばscore_list)は破棄され、忘れられます。次に関数が呼ばれるときは、新しい状態から始まります。
しかし、Pythonでは、可変オブジェクトをデフォルト引数として使用すると挙動が異なります。Pythonは関数が定義されたときにデフォルト引数を一度だけ作成します。したがって、リストを提供せずにrecord_scoreを呼び出すたびに、Pythonは関数が最初に定義されたときに作成した同じリストを使用します。そのリストを変更すると(得点を追加するなど)、その変更は次回関数が呼ばれたときにも残ります。
つまり、この関数では、score_listはリストを渡さない全ての関数呼び出しで共有されています。これが、例で得点が積み重なっていく理由です。
これは私たちが望んだ動作ではありません!自分のリストを提供しない限り、record_scoreの各呼び出しは空のリストから始めたいのです。
これを避けるための一般的な方法は、可変になる可能性のある引数のデフォルト値としてNoneを使用することです。そして、関数内で引数がNoneの場合、新しい可変オブジェクトを作成します。問題を回避するために、record_score関数を次のように書くことができます。
def record_score(new_score, score_list=None):
    if score_list is None:  # リストが提供されていなければ、
        score_list = []     # 新しいリストを作成する
    score_list.append(new_score)
    return score_list

print(record_score(95))  # [95]
print(record_score(85))  # [85]
この修正版では、自分のリストを提供しないrecord_scoreの呼び出しごとに、新しいリストが作成されます。これにより、得点が関数呼び出し間で共有されることはありません。
💡
つまり、重要なポイントは次のとおりです: Pythonでは、可変なデフォルト引数は一度だけ作成され、関数が呼ばれるたびに同じオブジェクトが使用されるため、予期しない挙動を引き起こす可能性があります。特にその可変オブジェクトを変更する場合、これは望ましくない結果を招くかもしれません。この問題を避けるために、デフォルト値としてNoneを使用し、必要に応じて関数内で新しいオブジェクトを作成することができます。
 
To check your solution you need to sign in
Sign in to continue