1. 概要
この記事では以下のことについて記載しています。
-
copy
とdeepcopy
を使用した際に、どのような条件でどのような速度差が生じるか -
deepcopy
でないとデータが壊れる場面があるが、それはどのような場合か
2. 速度差 - copy VS deepcopy
まずは、 copy と deepcopy の速度差がどの程度のものか見てみましょう。
2-1. 対象辞書データ
# 比較に用いる辞書データ
dic = {
"a": {
"stations": {
"sendai": {"hoge": 2345, "huga": 6789},
"tokyo": {"hoge": 1234, "huga": 5678},
}
},
"b": {
"stations": {
"osaka": {"hoge": 9876, "huga": 5432},
"fukuoka": {"hoge": 8765, "huga": 4321},
}
}
}
2-2. 測定結果
int 型の 1
を2つの方法でコピーしてみる
%%timeit -n 1_000_000
copy(1)
60.9 ns ± 9.41 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%%timeit -n 1_000_000
deepcopy(1)
122 ns ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
「2-1. 対象辞書データ」で宣言した dic
をコピーしてみる
%%timeit -n 1_000_000
copy(dic)
73.4 ns ± 14.3 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%%timeit -n 1_000_000
deepcopy(dic)
6.51 μs ± 17.4 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
2-3. 結果の整理
メソッド | 対象 | 所要時間測定結果 | 所要時間倍率 |
---|---|---|---|
copy | 1 | 60.9 ns (± 9.41) | 1.0 |
deepcopy | 1 | 122 ns (± 12.9 ns) | 2.0 |
copy | 辞書型データ | 73.4 ns (± 14.3) | 1.2 |
deepcopy | 辞書型データ | 6.51 μs (± 17.4) | 106.9 |
数字の 1
のような単純なデータであれば deepcopy
しても所要時間は2倍程度にしかならないようですが、ごく簡単な辞書型データを deepcopy
すると、 copy と比較して90倍程度の所要時間がかかってしまうようです。
deepcopy
の利用は最小限にしたいですね( ˘ω˘)スヤァ...
3. データの破壊と破壊の防止策
3-1. 浅い辞書データ
3-1-1. 元データ生成
ネストのある辞書を宣言しておきます。
from copy import copy, deepcopy
dic = {"a": {"type": 1}, "b": {"type": 2} , "c": {"type": 3}}
dic
# 結果
{'a': {'type': 1}, 'b': {'type': 2}, 'c': {'type': 3}}
3-1-2. 破壊される操作
ネストのある辞書を破壊してみます。
subdic_raw = dic["c"]
subdic_raw["type"] = 4
subdic_raw, dic
# 結果 (元の辞書が壊れた! 'c' の値に入っている辞書が書き換わっている)
({'type': 4}, {'a': {'type': 1}, 'b': {'type': 2}, 'c': {'type': 4}})
subdic
の type
property だけに変更を加えたつもりなのに、元の辞書 dic
のデータも変更されていることがわかります。これを「破壊」と呼んでいます。
3-1-3. 破壊を防ぐ操作
この破壊を防いでみます。
ネストが浅く、キー c
で取得できるサブ辞書が最も最下層の辞書なので、これを copy
すれば破壊は防げます。
subdic = copy(dic["c"]) # copy で十分
subdic["type"] = 4
subdic, dic
# 結果 (元の辞書は維持されている)
({'type': 4}, {'a': {'type': 1}, 'b': {'type': 2}, 'c': {'type': 3}})
3-2. 深い辞書データ
今度はネストが深いですね。辞書のネストが4階層にわたっています。
dic = {
"a": {
"stations": {
"sendai": {"hoge": 2345, "huga": 6789},
"tokyo": {"hoge": 1234, "huga": 5678},
}
},
"b": {
"stations": {
"osaka": {"hoge": 9876, "huga": 5432},
"fukuoka": {"hoge": 8765, "huga": 4321},
}
}
}
dic
# 結果
{'a': {'stations': {'sendai': {'hoge': 2345, 'huga': 6789},
'tokyo': {'hoge': 1234, 'huga': 5678}}},
'b': {'stations': {'osaka': {'hoge': 9876, 'huga': 5432},
'fukuoka': {'hoge': 8765, 'huga': 4321}}}}
これが破壊される様子を確認したり、破壊を防ぐ方法を検討したりしていきます。
3-2-2. 破壊をもたらす操作
copy せずに中身の辞書を変更しようとすると、元の辞書も変更されます(破壊)。
dic_a_raw = dic["a"]
dic_a_raw["stations"]["sendai"]["hoge"] = -99999
dic
# 結果: 元の辞書に存在しなかった -99999 が出現
{'a': {'stations': {'sendai': {'hoge': -99999, 'huga': 6789},
'tokyo': {'hoge': 1234, 'huga': 5678}}},
'b': {'stations': {'osaka': {'hoge': 9876, 'huga': 5432},
'fukuoka': {'hoge': 8765, 'huga': 4321}}}}
3-2-3. copy しても破壊がもたらされる操作
以下のように、上の方の階層の辞書をコピーしても、深い階層に対して変更を加えると、破壊が発生します。
dic_a_copy = copy(dic["a"])
dic_a_copy["stations"]["sendai"]["hoge"] = -99999
dic
# 結果
{'a': {'stations': {'sendai': {'hoge': -99999, 'huga': 6789},
'tokyo': {'hoge': 1234, 'huga': 5678}}},
'b': {'stations': {'osaka': {'hoge': 9876, 'huga': 5432},
'fukuoka': {'hoge': 8765, 'huga': 4321}}}}
3-2-4. 破壊を防ぐ操作!
元の辞書の破壊を防ぐには、 deepcopy
が確実ですね。
dic_a_deepcopy = deepcopy(dic["a"])
dic_a_deepcopy["stations"]["sendai"]["hoge"] = -99999
dic
# 結果
{'a': {'stations': {'sendai': {'hoge': 2345, 'huga': 6789},
'tokyo': {'hoge': 1234, 'huga': 5678}}},
'b': {'stations': {'osaka': {'hoge': 9876, 'huga': 5432},
'fukuoka': {'hoge': 8765, 'huga': 4321}}}}
3-2-5. これでも破壊を防げるよ
実は、以下のように操作すれば copy でも破壊を防げます。
ポイントは、変更を加えたい層を copy することです。
具体的に操作を見てみてください。
dic_sendai_raw = copy(dic["a"]["stations"]["sendai"])
dic_sendai_raw["hoge"] = -99999
dic
# 結果
{'a': {'stations': {'sendai': {'hoge': 2345, 'huga': 6789},
'tokyo': {'hoge': 1234, 'huga': 5678}}},
'b': {'stations': {'osaka': {'hoge': 9876, 'huga': 5432},
'fukuoka': {'hoge': 8765, 'huga': 4321}}}}
これでも元の辞書の破壊を防げる!
4. まとめ
deepcopy はめちゃくちゃ処理時間が倍増してしまうので、膨大な多重ループの中で実行するような場合には、なるべく利用を避けたいです。
そのような場合には、「3-2-5.」のような copy の仕方でも元の辞書の破壊を防げることを覚えておくと、処理効率を維持しつつデータの破壊を防げるかもしれません。