関数書くときに
- 引数を受け取らなかった → 結果をlistにして返す
- 引数にlistを受け取った → 続きに書き足して返す
みたいな動作を書きたいことがあると思います
失敗した書き方
そこで、以下のように書いてみました
def func1(n, l=list()):
for k in range(n):
l.append(k)
return l
func1(3) # [0, 1, 2] が返ってきます
def func1(n, l=list()):
for k in range(n):
l.append(k)
return l
l1 = func1(3)
l2 = func1(3, l1)
l2 # [0, 1, 2, 0, 1, 2] が返ってきます
どうやら期待通り動いてんね、という事で以下を実行するとおかしなことになります
def func1(n, l=list()):
for k in range(n):
l.append(k)
return l
l1 = func1(3)
l2 = func1(3, l1) # l2は [0, 1, 2, 0, 1, 2] になっている
l3 = func1(2)
l4 = func1(2, l3) # l4は [0, 1, 0, 1] になっている筈が…
l4 # [0, 1, 2, 0, 1, 2, 0, 1, 0, 1] が返ってきます
…どうやら何か失敗しているようです
動作を確認する
上記のl1~l4のオブジェクトIDを確認してみます
>>> print(id(l1), id(l2), id(l3), id(l4))
2093705104064 2093705104064 2093705104064 2093705104064
引数を受け取らなかった場合は新しく空のlistを作ってそこに書き足していくという動作を期待していたので
- l1~l2が同じオブジェクト
- l3~l4が同じオブジェクト
- l1~l2とl3~l4は別のオブジェクト
というのを期待していたんですが全部同じIDになってます。これが意図しない動作の原因のようです。
考察
IDを見て考えた推測ですが、1行目の「def func1(n, l=list()):」が原因ではないかと思います。list()により作成されたlistオブジェクトが関数情報を保管するスコープに置かれていて、引数を省略したらその作成済みlistオブジェクト(上の例だとIDが2093705104064になっているlistオブジェクト)を引数として受け取るという事ではないでしょうか。インタプリタが関数をパースするときにlist()を普通に実行して格納しちゃってる感じかな?と思ってます。
対策
というわけでNoneのように判別がやりやすいやつをデフォルトに入れておいて、Noneだったら新しいlistを作るという書き方に変えればやりたかった動作が実現できます。
def func2(n, l=None):
if l is None:
l = list()
for k in range(n):
l.append(k)
return l
l1 = func2(3)
l2 = func2(3, l1)
l3 = func2(2)
l4 = func2(2, l3)
print(l4)
print(id(l1), id(l2), id(l3), id(l4))
関数が上から下に順番に実行されるものと考えているとハマりやすい罠ではないかと思います。ご注意ください。
試行環境
Windows10 + miniconda
python 3.8.8