関数の引数でつまづいたのでメモします
結論
- デフォルト引数は初期化時のみ読み込まれる
- 関数の引数は共有渡しのため、可変値を取るときは注意
デフォルト引数
注意点
以下の2つのprint文は、それぞれlistの要素が一つであることを予想します
def pick(member, team=[]):
team.append(member)
return team
print(pick('Jill'))
print(pick('Bob'))
結果は予想外のものになります
>>> ['Jill']
>>> ['Jill', 'Bob']
これはpick関数のteamのデフォルト引数が一度しか読み込まれていないためです
つまり、teamがデフォルト値の時は同じオブジェクトを参照していることになります
解決策
pythonチュートリアルにも記載ある通り、関数のデフォル値が可変の場合は一旦Noneにします
def pick(member, team=None):
if team is None:
team = []
team.append(member)
return team
print(pick('Jill'))
print(pick('Bob'))
結果は期待通りになります
>>> ['Jill']
>>> ['Bob']
引数に可変値を取るとき
注意点
無事にデフォルト引数による驚きを回避しましたが、通常の可変値を関数の引数に取る場合も注意が必要です
以下のようにあらかじめリストを用意した後に関数を実行します
team_a = ['Jill', 'Bob']
def pick(member, team=None):
if team is None:
team = []
team.append(member)
return team
team_b = pick('Sam', team=team_a)
print(team_a)
print(team_b)
team_aにSamを加える動作を実行しました
team_bにはSamが存在し、team_aには存在しない事が期待値です
結果は以下のようになります
>>> ['Jill', 'Bob', 'Sam']
>>> ['Jill', 'Bob', 'Sam']
これは関数の引数が共有渡しである事が原因になります
同じオブジェクトを参照しに行くので、破壊的変更を加えると元の引数に影響が及びます
解決策
この事象の回避策は引数に不変値を用いるか、コピーを作成する事です
先ほどのpick関数は以下のようになります
team_a = ['Jill', 'Bob']
def pick(member, team=None):
if team is None:
team = []
else:
# teamのコピー作成
team = list(team)
team.append(member)
return team
team_b = pick('Sam', team=team_a)
print(team_a)
print(team_b)
結果は期待通りになります
>>> ['Jill', 'Bob']
>>> ['Jill', 'Bob', 'Sam']
関数に限らずエイリアスなのかコピーなのか気をつけていきたいです