はじめに
「変数は箱のようなものです」
プログラミングの入門書で必ずと言っていいほど見かけるこの説明。
わかりやすくて、多くの人がこの理解でコードを書き始めます。
ただ、このままの理解だと思わぬバグを埋め込む可能性があります、、、
実務で事故らないために理解をアップデートするきっかけになればと思います。
余談ですが「変数は箱」の例だけでなく、
概念を理解するのにいくつかの側面がありますよね。
(こういう理解もできるけど、こうとも捉えられる)
変数=箱だと思っていたらハマる罠
この例がよくある例だと思います。
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4]
あれ?なんでbに追加したのにaにも追加されているの??
変数が箱ならaとbの箱ができてそれぞれに1,2,3が入っており、
bだけに4をいれたはずです。
しかし、結果としてaにも4が入っています。
変数が箱であるという考え方だとこの例が説明できません。
ここで、考え方をアップデートする必要が出てきます。
変数は”ラベル”である
そうです、「変数はラベル」なんです!
はじめてこの説明を聞いた時、
「そんな、、、嘘つかれた??笑」となりました。
こう考えてみましょう。
1,2,3が入っているオブジェクトに
aというラベルをつけます。
もちろん、aを出力すれば1,2,3が返ってきます。
b=aをすると1,2,3が入っているオブジェクトにbというラベルを貼ることになります。
つまり、1,2,3が入っているaとbの箱が2つあるのではなく、
1,2,3が入っているオブジェクトにaとbのラベルが2つついている状態です!
コードで確認してみましょう。
a = [1, 2, 3] # [1,2,3]にaというラベルをつける
b = a # [1,2,3]にbというラベルをつける
b.append(4) # bからアクセスできる[1,2,3]に4を追加する
print(a) # [1, 2, 3, 4]
このように考えると、aにも4が追加されている現象もしっくりくるのではないのでしょうか?
起こり得る問題
仕事の中で、既存の変数(正しくはオブジェクト)に
何か変更をしたい時ってあると思います。
その時に、変数は箱だからと新しい変数に代入して
変更を加える処理をしたとしましょう。
実際には同じ中身をいじっていることになるので、
他の処理でもその変数への処理が反映されて思わぬ挙動をしてしまいます。。。
具体例としては、
- 複数の設定オブジェクトをコピーして変更したつもりが、全インスタンスに影響
- APIレスポンスを一時加工したつもりが、元データも壊れる
- 複数ユーザーセッション間で同じリストが共有される
このようなバグが起きえます。
しかも、バグが起きている処理に原因があるわけでもないので、
デバックが大変になってしまいます。
解決策:copy()で別オブジェクトを作る
そのような思わぬバグを避けるためにどうすればいいのでしょうか?
結論は、**”コピーして他のオブジェクトを作りそのオブジェクトにラベルをつければいい”**です!
コードで見てみましょう。
a = [1, 2, 3] # [1,2,3]にaというラベルをつける
b = a.copy() # aのラベルがついているオブジェクトのコピーにbのラベルをつける
b.append(4) # bのラベルがついているオブジェクトに4を追加
print(a) # [1, 2, 3]
print(b) # [1, 2, 3, 4]
こうすることで安全に処理を進めることができます!
本当はこれだけでは不十分で、ネストした時にdeepcopyが必要などあるのですが
それはまた別でまとめようと思います。
まとめ
変数は箱という理解の仕方が間違っているわけではありません。
変数は箱であり、オブジェクトへのラベルでもあります。
間違っている、間違っていないというよりは
どちらの理解も成り立つという感じだと思います。
(説明する時、厳密にするよりは簡単な例で理解してもらうことのほうが重要ですよね。)
自分の理解に固執せず少し引いた目で理解をアップデートしていくスタンスでいくと勉強も進めやすいと思います。