Pythonの関数の引数は基本参照渡しと聞いています。Cプログラムしか書いてきてないので、関数内で値を壊さぬよう細心の注意を払ってこのnumpy arrayを扱ってきました。基本的にこの考え方でこれまで大きな罠にはまった事はないのですが、改めて動きを確認したらちょっとギョッとしたのでメモを残します。
恐怖したコード
a = np.array([0,1,2,3,4,5]) b = a a += 1 print(b) ---- [1 2 3 4 5 6]
まぁこれはなんとなくよしとしましょう。参照なのでこうなります。さあ次の場合どうなるでしょう…。
a = np.array([0,1,2,3,4,5]) b = a a = a + 1 print(b) ---- [0 1 2 3 4 5]
な…。a += 1 と a = a + 1 で答えが違う…。もちろん *= でも -= でも。
考察
個人の見解であり正しいかどうかわかりません。
a = a + 1 では右辺の a + 1 した結果新しいオブジェクトが生成され、その参照としてaという配列ができた。と解釈できます。b = a + 1 のbの変わりがaというわけでオブジェクトが違う。という解釈です。
に対して a += 1 はaという配列の中身を書き換えただけで新しいオブジェクトが生成されているわけではない。という理解。
ひとまずこの理解で自分を納得させました。
検証
a = np.array([0,1,2,3,4,5]) b = a a = a + 1 if a is b: print("same") else: print("not same") print(b) ---- not same [0 1 2 3 4 5]
でした。違うオブジェクト。is 演算子は値の比較ではなく同じオブジェクトか否かの判定です。ではこちらでは。
a = np.array([0,1,2,3,4,5]) b = a a += 1 if a is b: print("same") else: print("not same") print(b) ---- same [1 2 3 4 5 6]
というわけで同じオブジェクト。ではこれは…。
a = np.array([0,1,2,3,4,5]) b = a[:] a += 1 if a is b: print("same") else: print("not same") print(b) ---- not same [1 2 3 4 5 6]
値はaとb同じですがオブジェクトは違う…。b += 1を行うとaもしっかり変わるのでメモリは共有…。
b = a.copy()
これはオブジェクトもメモリも別。
b = np.array([0,0,0,0,0,0]) b[:] = a
これもオブジェクトもメモリも別。まとめます。それぞれ違う。
b = a | 同じオブジェクト | 同じメモリ |
b = a[:] | 違うオブジェクト | 同じメモリ |
b[:] = a | 違うオブジェクト | 違うメモリ |
b = a.copy() | 違うオブジェクト | 違うメモリ |
このあたり理解が適当だと痛い目を見そうです。
関数への引数
関数の引数も参照なので同じような事が起こります。
def func(c): b = c # 退避になってねー b += 1 a = np.array([0,1,2,3,4,5]) func(a) print(a) ---- [1 2 3 4 5 6]
関数の冒頭で値を逃がしているつもりでもしっかり値を壊してくれています。同じ関数でこれをintを引数に呼ぶと
a = 1 # np.array([0,1,2,3,4,5]) func(a) print(a) ---- 1
値が壊れておりません。コピーが渡されているように振る舞います。これはどうやらmutableかimmutableかで振る舞いが違うようです。numpy arrayやlistはmutable。intはimmutableなのでこの違いが起こるようです。このあたりはググってみてください。
まとめ
今回痛い目を見たわけではないですが、直感的では無かったのでメモに残します。ちゃんと理解しとかないとあぶねーな…。参照って仕組みをちゃんと勉強する必要がありそうです。
コメント