Python

【Python】ネストされたリストをコピーする

MAX

Pythonではネストされたミュータブルなオブジェクトをコピーする際は、浅いコピーと深いコピーを意識してコピーする必要がある。

浅いコピーの場合、ネストされたオブジェクトはコピー元とコピー先で共有される。

深いコピーの場合、ネストされたオブジェクトも全て別物となる。

スポンサーリンク

リストのコピー

ネストされていないリストのコピーはlist.copy()でコピーされる。

=を使う場合、list_b = list_aとしただけだと、同じオブジェクトを指すことになるので注意。

list_b = list_a[:]とすると、list_a[:]でlist_aのコピーを返すことになるため、list_bとlist_aが別オブジェクトになる。

1# リストのコピー
2list_a = [1, 2, 3]
3# =で代入。
4# これだとlist_bはlist_aを指しているだけになるため、list_bを更新するとlist_aも更新される
5list_b = list_a
6# list.copy()でコピー
7list_c = list_a.copy()
8# [:]でコピー
9# list[:]でlistのコピーが返されるため、コピーを代入することになる。
10list_d = list_a[:]
11print(f"{id(list_a)=}")
12print(f"{id(list_b)=}")
13print(f"{id(list_c)=}")
14print(f"{id(list_d)=}")
15# id(list_a)=4398808000
16# id(list_b)=4398808000
17# id(list_c)=4398797440
18# id(list_d)=4398631552

=で代入したlist_bはlist_aとIDが同じ、つまりlist_aと同じオブジェクトを指すことになる。

list.copy()でコピーしたlist_cはIDが異なっている。

この状態でlist_bを更新すると、同じオブジェクトを指すlist_aも更新される。

1# list_bとlist_aは同じオブジェクトなので、list_bを更新するとlist_aも更新される
2list_b.append(4)
3print(f"{list_b=}")
4print(f"{list_a=}")
5# list_b=[1, 2, 3, 4]
6# list_a=[1, 2, 3, 4]
7
8# list_cとlist_aは別のオブジェクトなのでlist_cを変更してもlist_aは更新されない
9list_c.append(5)
10print(f"{list_c=}")
11print(f"{list_a=}")
12# list_c=[1, 2, 3, 5]
13# list_a=[1, 2, 3, 4]

list_cとlist_aはオブジェクトが異なるので、片方を更新しても、もう片方には影響がない。

mutable-sequence-types

ネストされたリストのコピー

浅いコピー

list.copy()list[:]でのコピーは浅いコピーとなる。

また、後述するcopy.deepcopy()のcopyにはcopy.copy()も存在し、copy.copy()によるコピーも浅いコピーとなる。

浅いコピーの場合、ネストされていないリストは別のIDとなるが、ネストされたリストはコピー元とコピー先で同じオブジェクトを指すことになる

1# ネストされたリストの作成
2list_a = [[1, 2, 3], [4, 5, 6]]
3
4# 浅いコピー
5list_b = list_a.copy()
6print(f"{id(list_a)=}")
7print(f"{id(list_b)=}")
8# id(list_a)=4398744192
9# id(list_b)=4399195776
10
11# list.coopy()でリストをコピーする場合、ネストされたリストは、
12# コピー元とコピー先で同じオブジェクトを指すことになる
13print(f"{id(list_a[0])=}")
14print(f"{id(list_b[0])=}")
15# id(list_a[0])=4399180800
16# id(list_b[0])=4399180800

片方のネストされたリストを更新すれば、もう片方も更新される。

ネストされていない部分は、お互いに影響しない。

1# ネストされていない部分への変更は、list_aとlist_bは別のオブジェクトなので、
2# 片方を更新してももう片方には影響がない
3list_b.append(3)
4print(f"{list_b=}")
5print(f"{list_a=}")
6# list_b=[[1, 2, 3], [4, 5, 6], 3]
7# list_a=[[1, 2, 3], [4, 5, 6]]
8
9# list_bのネストされたリストに対してappendをすると、list_aも更新される
10list_b[0].append(4)
11print(f"{list_b=}")
12print(f"{list_a=}")
13# list_b=[[1, 2, 3, 4], [4, 5, 6], 3]
14# list_a=[[1, 2, 3, 4], [4, 5, 6]]

コピー元とコピー先でネスト部分を共有したい場合は、これで良いが、意識せずに使っているとバグの温床となるため注意した方がいい。

深いコピー

copy.deepcopy()を使うことで、ネストされた部分のリストについてもコピー元とコピー先で別物としてコピーできる。

1from copy import deepcopy
2
3list_a = [[1, 2, 3], [4, 5, 6]]
4# deepcopy()を使うことで、ネストされたリストを全て別のオブジェクトとしてコピーできる。
5list_b = deepcopy(list_a)
6print(f"{id(list_a)=}")
7print(f"{id(list_b)=}")
8# ネストされたリストもIDが異なる
9print(f"{id(list_a[0])=}")
10print(f"{id(list_b[0])=}")
11# id(list_a)=4398631872
12# id(list_b)=4398667648
13# id(list_a[0])=4398797440
14# id(list_b[0])=4398631552

ネストされたリストを更新しても、もう片方には影響がない。

1# list_aのネストされたリストに値を追加してもlist_bには影響がない
2list_a[0].append(4)
3print(f"{list_a=}")
4print(f"{list_b=}")
5# list_a=[[1, 2, 3, 4], [4, 5, 6]]
6# list_b=[[1, 2, 3], [4, 5, 6]]

copy.deepcopy

まとめ

リストが入れ子になっていない場合は、コピー方法の違いは、意識しなくていい。

ネストされたリストをコピーする場合、目的に合わせてコピー方法を選択する必要がある。

ネストされたリスト部分を共有したい場合は浅いコピー。

ネストされたリスト部分を共有したくない場合は深いコピー。

1# リストのコピー
2list_a = [1, 2, 3]
3
4# list.copy()でコピー
5list_c = list_a.copy()
6# [:]でコピー
7# list[:]でlistのコピーが返されるため、コピーを代入することになる。
8list_d = list_a[:]
9
10
11# ネストされたリストのコピー
12list_a = [[1, 2, 3], [4, 5, 6]]
13# 浅いコピー
14list_b = list_a.copy()
15# ネストされたリストは、コピー元とコピー先で同じオブジェクトを共有する
16print(f"{id(list_a[0])=}")
17print(f"{id(list_b[0])=}")
18# id(list_a[0])=4399180800
19# id(list_b[0])=4399180800
20
21# 深いコピー
22from copy import deepcopy
23
24list_b = deepcopy(list_a)
25# ネストされたリストもIDが異なる
26print(f"{id(list_a[0])=}")
27print(f"{id(list_b[0])=}")
28# id(list_a[0])=4398797440
29# id(list_b[0])=4398631552

なお個人的にはリストでネスト部分を共有するような構造は、クラスのインスタンスをリストに入れている場合に、共有したくなる時があるかもしれないなー、ぐらいかなと思っている。

ミュータブルなデータ構造の共有や取り扱いには十分にご注意を。

スポンサーリンク
ABOUT ME
MAX
MAX
ITエンジニア、データサイエンティスト
新卒でSIerに入社し、フリーランスになってWEB系へ転向。
その後AIの世界へ足を踏み入れ、正社員に戻る。 テーブルデータの分析がメイン。
スポンサーリンク
記事URLをコピーしました