参照しているオブジェクト自体が変更されたからです.と言っても難しいと思うので,順に説明していきます.
ミュータブルとイミュータブル
Pythonのオブジェクトはイミュータブル(変更不可)かミュータブル(変更可)のどちらかです.
数値や文字列,タプルなどはイミュータブルです.
python
1s = "hoge"
2s[1] = "a" #=> エラー!
このように変更しようとするとエラーになります.
対してリストや辞書などはミュータブルです.
python
1l = ["h", "o", "g", "e"]
2l[1] = "a" #=> ['h', 'a', 'g', 'e']
エラー無く変更できますね.
代入
よく代入は「変数という箱にデータを入れる」と例えられますが,Pythonにおいては少し違います.
変数に入るのはデータ(オブジェクト)自体ではなく,その参照です.
参照をめちゃくちゃ簡単に言うと,「オブジェクトを指し示す矢印」です.
本題
次のプログラムは,質問者さんの「上の状況」を簡単にしたものです.
python
1a = 123
2b = a
3print(a) #=> 123
4print(b) #=> 123
5a = 987
6print(a) #=> 987
7print(b) #=> 123
8
このようなプログラムで何が起こっているかというと.
まず1行目で,123という数値オブジェクトが作成されて,これを指し示す矢印(参照)が変数aに入ります.
次に2行目で変数aに入っている参照が変数bに入ります.
つまり,123というオブジェクトはコピーされず1つのままで,その参照がコピーされているわけです.
なので,この時点では変数aも変数bも同じ1つのオブジェクトを参照しています.
最後に5行目で,987というオブジェクトが新たに作成され,その参照が変数aに入ります.
このとき,123というオブジェクト自体が変更されたわけではないので,変数bには123への参照が入ったままです.(そもそもイミュータブルだから変更できない)
次に「下の状況」を簡単にしたプログラムです.
python
1a = [1, 2, 3]
2b = a
3print(a) #=> [1, 2, 3]
4print(b) #=> [1, 2, 3]
5a[1] = 9
6print(a) #=> [1, 9, 3]
7print(b) #=> [1, 9, 3]
4行目までは同じですね.
リストオブジェクトを作成して,その参照を変数aと変数bに入れています.
5行目が異なります.
変数aが参照しているリストオブジェクトの要素を変更していますね.
リストはミュータブルなので問題なくオブジェクト自体を変更できます.
そして,変数bも同じオブジェクトを参照しているので,変数aと同じになります.
おしまい
ミュータブルなオブジェクトを代入したり関数の引数に渡したりするときは,間接的にオブジェクトの値が変更できてしまうので気をつけましょう.
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。