可変なデフォルト引数
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
の呼び出しごとに、新しいリストが作成されます。これにより、得点が関数呼び出し間で共有されることはありません。