メインコンテンツへスキップ

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

·1363 文字·3 分
目次

はじめに
#

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

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

検証環境はPython 3.7.4である。

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

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

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のネストされたリストや辞書をコピーする場合、一方のリスト(または辞書)に加えた変更が他方にも反映されてしまう。

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()関数を使い、深いコピーでネストされたリストと辞書をコピーする。片方への変更が、他方に反映されないことが分かる。

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を使うことによって、データの実体がコピーされたのか、参照先のみコピーされたのかを確認できる。

Helve
著者
Helve
関西在住、電機メーカ勤務のエンジニア。X(旧Twitter)で新着記事を配信中です

関連記事

PandasのTimestampでタイムゾーンを扱う
·1568 文字·4 分
PandasのTimestampオブジェクトにタイムゾーンを設定する方法や、異なるタイムゾーンに変換する方法について述べる。
PandasのTimestampで時刻を扱う
·2140 文字·5 分
PandasのTimestampを使った時刻の生成や、時刻オブジェクトからの属性の取得、任意形式の文字列での出力について述べる。
辞書内包表記でPandasのSeries, DataFrameを作成
·1056 文字·3 分
辞書内包表記を使って、PandasのSeries, DataFrameを少ないコード量で作成する。
Pythonのデコレータで関数に処理を追加する
·1104 文字·3 分
Pythonで関数の前後に処理を追加する、デコレータと呼ばれる機能について簡単にまとめた。
Pythonとseleniumを使ったブラウザ操作自動化
·2605 文字·6 分
ウェブUIのテストツールであるseleniumを使った、ブラウザ操作の自動化についてまとめた。
Pythonのreモジュールを使った正規表現の基本
·1821 文字·4 分
Pythonのreモジュールの基本的な使い方をまとめた。