1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Python / dict / deepcopy] 辞書型データの破壊を防ぎつつ、なるべく処理速度は維持したい

Posted at

1. 概要

この記事では以下のことについて記載しています。

  • copydeepcopy を使用した際に、どのような条件でどのような速度差が生じるか
  • 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}})

subdictype 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 の仕方でも元の辞書の破壊を防げることを覚えておくと、処理効率を維持しつつデータの破壊を防げるかもしれません。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?