リストのコピーでハマった話

#はじめに
リストを他の変数にコピーしようと思ったときに思うように行かず、無駄な時間を過ごしたので自戒の意味を込めて記事を書きます。

#やりたかったこと
```py
a = [0, 1, 2, 3, 4]
b = []
```

という変数があり、

1. bにaを追加して
2. aを変更
3. 1〜2を繰り返し

ということをしようとしました。

そのときのコードを簡略化したのが以下のもの。

```py
print(b)      # 変数に何も入っていないことの確認

b.append(a)   # bにaを追加(1回目)
print(b)

a[2] = 99     # aの[2]番目の要素を書き換え
b.append(a)   # bにaを追加(２回目)
print(b)
```
これを実行すると以下のように表示されます。

```py
[]
[[0, 1, 2, 3, 4]]
[[0, 1, 99, 3, 4], [0, 1, 99, 3, 4]]
```
結果の3行目に注目すると、1回目に代入した```[0, 1, 2, 3, 4]```というリストの[2]番目の要素が```a[2] = 99```の代入をしたことによって変化してしまいました。

この動作について調べると、IDの受け渡しが行われていることに起因するもののようです。

#ID
Pythonのオブジェクトには、固有の識別値が割り振られています。
例えば、

```py
print(id(a))
```
によって識別値を表示したとすると、私のPC上では、結果は以下のようになりました。

```py
4392823368
```
#appendの動作
appendを使ってリストに値を追加したとき、このIDがそのまま受け渡されています。
（後述しますが、代入の場合もIDが受け渡されています）

```py
a = [10]
print(id(a))      # ID: 4315448960

b = []
b.append(a)
print(id(b[0]))   # ID: 4315448960
```
ということで、appendによって追加されたb[0]はaを参照しているだけなのです。[^1]

appendをもう一度やってみると、

```py
a = [10]
id(a)             # ID: 4315448960

b = []
b.append(a)
print(id(b[0]))   # ID: 4315448960

b.append(a)
print(id(b[1]))   # ID: 4315448960
```
と、b[0]とb[1]のIDは同じものになります。
そしてbの中身は`[[10], [10]]`という状態

この状態で`a[0] = 100`と代入してみると、bの中身が`[[100], [100]]`に変化しています。
つまり、appendによって、aのIDのみが受け渡されているために、bの要素はaを参照しているに過ぎません。
なので、a自体を書き換えてしまうとbも変わってしまうという罠があるのです。

##ちなみに
代入するときも同じことが起きます。
例えば、

```py
a = [10]
b = a

a[0] = 1234
print(b)
```
とすると、bの値は`[1234]`と出てきます。
代入のときも同じようにIDの受け渡しが行われているからです。

これが普通の変数の場合だともちろん起きません。

```py
a = 10
b = a

a = 1234
print(b)      # 10と表示される
```
これは、通常の変数もIDを受け渡していますが、代入が行われるときに、IDの変更も同時に行われるためです。
（というか数字自体がIDを持っているっぽく、その数字のIDが代入されるらしい）

```py
print(id(1))      # ID: 4308878688
print(id(2))      # ID: 4308878720

a = 1
print(id(a))      # ID: 4308878688

b = a
print(id(b))      # ID: 4308878688

a = 2
print(id(a))      # ID: 4308878720
print(id(b))      # ID: 4308878688
```
##リストの場合
代入でもappendでもIDの受け渡しによってそれらが実現されていたことがわかりました。
しかし、リストになるとおかしなことが起こるのは、リストの代入やappendのときにはリスト自体のidが受け渡されてしまうからなのです。

```py
print(id(1))      # ID: 4308878688

a = [1]
print(id(a))      # ID: 4313304392（リスト自体のID）
print(id(a[0]))   # ID: 4308878688

b = a
print(id(b))      # ID: 4313304392
```
リスト自体のIDが受け渡されることによって、bはaを参照しています。
ここでaのリストの値を書き換えることで、aとともにbも変わってしまいます。

このようなことは、リスト型以外でもディクショナリ型、セット型で起こり得ます。（これらをミュータブルな型と言います）
逆に起きないのは文字列、数値、タプルなどのイミュータブル（不変という意味）な型の場合です。

#対策
代入やappendをする場合に、IDがそのまま受け渡されることが原因でした。
これはcopyモジュールのdeepcopy関数を使うことで回避することができます。

```py
import copy

a = [1]
b = []

b.append(copy.deepcopy(a))      # IDを変えたリストを返す

a[0] = 2
```
とすると、bの中身は`[2]`とならずに、`[1]`のままです。
deepcopy関数は、aの中身はそのままに、IDを変えたリストを返します。
これによって、リストをappendしていくことができるようになります。
代入するときも同様に`b = copy.deepcopy(a)`とすることでaへの代入の影響を受けなくなります。
#追記
@toritoritorinaさんからコピーの方法について教えていただいたので、追加です。ありがとうございます。
deepcopy以外にもまだまだ方法があったとは！
>#他のリストコピーの方法
>```py
>import copy
>a = [1]
>b = a[:] # [:]によるコピー
>b = a.copy() # リストのコピーメソッド
>b = copy.copy(a) # copyモジュールの浅いコピー
>b = copy.deepcopy(a) # copyモジュールの深いコピー
>```
>
>#深いコピー
>```py
>import copy
>a = [[1]]
>b1 = a[:] # [:]によるコピー
>b2 = a.copy() # リストのコピーメソッド
>b3 = copy.copy(a) # copyモジュールの浅いコピー
>b4 = copy.deepcopy(a) # copyモジュールの深いコピー
>a[0][0] = 2
>```
>リスト内にリストがあるような場合少し注意が必要です。
>`a[0][0] = 2`とすると、b1, b2, b3は影響を受けます。これは中身のリスト..`a[0]`が同一のリストを指しているためです。
>この場合は、b4のようにdeepcopy()を使うのがベターです。

#おわりに
ミュータブルな型の場合の代入やappendにはくれぐれも気をつけましょう。

※間違いがあった場合はご指摘ください

#参考
[Python のリストの扱いで注意すること](http://qiita.com/utgwkk/items/5ad2527f19150ae33322)
[Pythonの変数とオブジェクトについて]
(https://qiita.com/makotoo2/items/35f8c2abf3248816f0e4)