JavaScriptを有効にしてください

【Python】ネストされたリスト・辞書とdeepcopy

 ·  ☕ 3 min read  ·  ✍️ Helve

はじめに

本記事ではリストおよび辞書をコピーする4つの方法について説明する。

Pythonでネストされたリストや辞書をコピーするとき、一方に加えた変更が他方に反映されないようにしたい場合は、copyモジュールのdeepcopy()関数を用いる。deepcopy()関数によって、リスト・辞書の参照先でなく、実体が全てコピーされる。

検証環境は以下の通り。

バージョン
Python 3.7.4

リスト・辞書をコピーする方法

Pythonでリスト・辞書をコピーする場合、以下の4つの方法がある(以降ではcopyモジュールのインポートを省略する)。

1
2
3
4
5
6
7
8
import copy

list0 = [0, 1, 2]

list1 = list0                # 1. 直接代入する
list2 = list0.copy()         # 2. copy()メソッド
list3 = copy.copy(list0)     # 3. copy()関数
list4 = copy.deepcopy(list0) # 4. deepcopy()関数

方法1. はデータの参照先のみがコピーされるため、ネストの有無に関わらず、片方へ変更を加えると他方に反映される。

方法2. と3. は等価であり、浅いコピー(shallow copy)と呼ばれる。浅いコピーでは、数値や文字列といったデータ型の実体はコピーされるが、一方、ネストの内側のリスト等は参照先のみコピーされる。(詳細は「ミュータブル」の概念を理解する必要があるが、本記事では扱わない)

そのため、リストまたは辞書がネストされていない場合、片方の変更は他方へ反映されない。しかし、ネストされている場合は、片方の変更が他方へ反映されていまう。

方法4. は深いコピー(deep copy)と呼ばれ、ネストの有無に関わらず、データの実体を全てコピーする。そのため、片方の変更は他方に反映されない。しかし、当然ながら実行速度は遅くなってしまうため、処理速度が重要な場合には浅いコピーで代用できないか考慮する必要がある。

ネストされた配列・辞書のコピー

ネストされた配列・辞書に対して、

  • 浅いコピー
  • 深いコピー

でそれぞれコピーを作成する。深いコピーであれば、片方の変更が他方に反映されないことを示す。

浅いコピー

既に上で述べたように、浅いコピーはcopyモジュールのcopy()関数や、copy()メソッドを使って行う。浅いコピーでPythonのネストされたリストや辞書をコピーする場合、一方のリスト(または辞書)に加えた変更が他方にも反映されてしまう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
list1 = [[0, 1], [2, 3]]
list2 = copy.copy(list1)

list1[0][0] = 4 # 0を4に変更
print(list1) # [[4, 1], [2, 3]]
print(list2) # [[4, 1], [2, 3]]
# list2の0も4に変更される

dict1 = {"k0": {"k00": "v00",
                "k01": "v01"},
         "k1": {"k10": "v10",
                "k11": "v11"}}
dict2 = dict1.copy()

dict1["k0"]["k00"] = "v22" # "v00"を変更
print(dict1)
# {'k0': {'k00': 'v22', 'k01': 'v01'}, 'k1': {'k10': 'v10', 'k11': 'v11'}}
print(dict2)
# {'k0': {'k00': 'v22', 'k01': 'v01'}, 'k1': {'k10': 'v10', 'k11': 'v11'}}
# dict2のv00もv22に変更される

深いコピー

最後に、copyモジュールのdeepcopy()関数を使い、深いコピーでネストされたリストと辞書をコピーする。片方への変更が、他方に反映されないことが分かる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
list1 = [[0, 1], [2, 3]]
list2 = copy.deepcopy(list1)

list1[0][0] = 4 # 0を4に変更
print(list1) # [[4, 1], [2, 3]]
print(list2) # [[0, 1], [2, 3]]
# list2は0のまま

dict1 = {"k0": {"k00": "v00",
                "k01": "v01"},
         "k1": {"k10": "v10",
                "k11": "v11"}}
dict2 = copy.deepcopy(dict1)

dict1["k0"]["k00"] = "v22" # "v00"を変更
print(dict1)
# {'k0': {'k00': 'v22', 'k01': 'v01'}, 'k1': {'k10': 'v10', 'k11': 'v11'}}
print(dict2)
# {'k0': {'k00': 'v00', 'k01': 'v01'}, 'k1': {'k10': 'v10', 'k11': 'v11'}}
# dict2はv00のまま

参考

以下の記事を参考にさせていただいた。組み込み関数のidを使うことによって、データの実体がコピーされたのか、参照先のみコピーされたのかを確認できる。
Pythonのcopyとdeepcopyについて - Qiita

シェアする
ブログランキングに参加中です。クリックして頂けると励みになります。

Helve
WRITTEN BY
Helve
関西在住、電機メーカ勤務のエンジニア。Twitterで新着記事を配信中です