2020年7月12日日曜日

【リスト】リストの複製

 リストをコピーする場合、そのやり方によってコピー元・先のオブジェクトが束縛される場合と別のオブジェクトとなる場合があり注意が必要。代入演算子で複製すると参照渡しとなり同じメモリ領域を参照するためコピー先のオブジェクトがコピー元のオブジェクトの値に影響される。コピー先のオブジェクトをコピー元のオブジェクトとリンクさせたくない場合はcopy()メソッドやcopyモジュールを使う、スライスによりコピーする等が必要となる。


1. 代入演算子=によるリストのコピー

 リストを代入演算子で複製すると参照渡しとなり、コピー先のオブジェクトとコピー元のオブジェクトが同じメモリ領域を参照することになる。このためどちらかの値を変更するともう一方の値も変更されてしまう。

代入演算子=によりyにリストxを代入した場合。代入後にxに4をappendするとyにも4がappendされる。idによりオブジェクトのidを確認するとx,yが同じidとなっており同じオブジェクトとして取り扱われていることが分かる。

x = [1, 2, 3]
y = x

print(x, y)

x.append(4)

print(x, y)

print(id(x), id(y))

実行結果

[1, 2, 3] [1, 2, 3]
[1, 2, 3, 4] [1, 2, 3, 4]
1706958004552 1706958004552


2. copyによるリストのコピー

 copyによりコピーするとコピー元、コピー先は別のオブジェクトとなるためお互いに影響を受けない。

x = [1, 2, 3]
y = x.copy()

print(x, y)

x.append(4)

print(x, y)

print(id(x), id(y))

実行結果

[1, 2, 3] [1, 2, 3]
[1, 2, 3, 4] [1, 2, 3]
1706949188168 1706928441736


 ただし、リストが要素としてリストを含む場合、copyによりコピーした場合も要素となっているリストを変更するとコピー先、コピー元両方のリストが影響を受ける。コピーしたリスト自体は参照されていないが、その要素であるリストは参照渡しとなっているためである(浅いコピー)。
リストa, bを要素として持つリストxをcopyでyにコピーし、aに要素を追加した場合。x, yのidは異なりx, yは参照関係に無いが、aは参照が維持されているためx, y両方の要素が変更される。これを避けるためにはcopy.deepcopyにより「深いコピー」を行う必要がある。

a = [1, 2]
b = [3]
x = [a, b]
y = x.copy()

print(x, y)

a.append(4)

print(x, y)

print(id(x), id(y))

実行結果

[[1, 2], [3]] [[1, 2], [3]]
[[1, 2, 4], [3]] [[1, 2, 4], [3]]
1706958007688 1706959483016


3. copy.copyによるリストのコピー

 copy.copyを用いるとcopy同様のコピーができる。 copy.copyを用いるためには標準ライブラリcopyのインポートが必要。

import copy

x = [1, 2, 3]
y = copy.copy(x)

print(x, y)

x.append(4)

print(x, y)

print(id(x), id(y))

実行結果

[1, 2, 3] [1, 2, 3]
[1, 2, 3, 4] [1, 2, 3]
2431241687240 2431241678344


 copy.copyによるコピーは浅いコピーであり要素としてリストを含むリストを複製した際の挙動はcopy()同様。

import copy

a = [1, 2]
b = [3]
x = [a, b]
y = copy.copy(x)

print(x, y)

a.append(4)

print(x, y)

print(id(x), id(y))

実行結果

[[1, 2], [3]] [[1, 2], [3]]
[[1, 2, 4], [3]] [[1, 2, 4], [3]]
2431242299848 2431242300808


4. copy.deepcopyによるリストのコピー(深いコピー)

 リストが要素としてリストを含む場合、copycopy.copyでコピーした場合、要素であるリストは参照渡しとなる。コピー先とコピー元をリンクさせたくない場合はcopy.deepcopyにより「深いコピー」を行う。
copy.deepcopyでリストxをリストyにコピーしたのち、xの要素であるリストaに値を追加する。xの要素は変更されるが、yはリンクが切れており要素が変更されない。

import copy

a = [1, 2]
b = [3]
x = [a, b]
y = copy.deepcopy(x)

print(x, y)

a.append(4)

print(x, y)

print(id(x), id(y))

実行結果

[[1, 2], [3]] [[1, 2], [3]]
[[1, 2, 4], [3]] [[1, 2], [3]]
1706959486728 1706948995784


5. スライスによるリストのコピー

 コピー元のリストの要素をスライシングにより取り出してコピー先のリストに代入することが可能。この場合リストを複製するわけではなくオブジェクトに要素を代入する形となるため、コピー元・先のリストはお互いに影響を受けずidも別となる。
リストxの要素をスライシングで取り出しyに代入する場合。xの変更はyに影響しない。

x = [1, 2, 3]
y = x[:]

print(x, y)

x.append(4)

print(x, y)

print(id(x), id(y))

実行結果

[1, 2, 3] [1, 2, 3]
[1, 2, 3, 4] [1, 2, 3]
1523996997256 1523996997448


 copycopy.copy同様、要素自体がリストの場合は要素のリストの変更の影響を受ける(浅いコピー)。

a = [1, 2]
b = [3]
x = [a, b]
y = x[:]

print(x, y)

a.append(4)

print(x, y)

print(id(x), id(y))

実行結果

[[1, 2], [3]] [[1, 2], [3]]
[[1, 2, 4], [3]] [[1, 2, 4], [3]]
1524005949064 1523996954952


6. リファレンス

Python 標準ライブラリ > データ型 > copy --- 浅いコピーおよび深いコピー操作

使用バージョン:Python 3.7.0

0 件のコメント:

コメントを投稿